Swift란?
Swift는 Apple이 개발한 현대적 프로그래밍 언어입니다. iOS, macOS, watchOS, tvOS 앱 개발은 물론 서버 사이드 개발에도 사용됩니다. 강력한 타입 시스템, 옵셔널을 통한 null 안전성, 간결한 문법이 특징입니다.
이 글에서는 Swift의 기본 문법부터 옵셔널 처리까지 핵심 내용을 정리합니다.
변수와 상수
Swift에서는 let으로 상수(불변), var로 변수(가변)를 선언합니다. 타입 추론이 강력하여 대부분의 경우 타입을 명시하지 않아도 됩니다.
import Foundation
// let — 상수 (불변, 권장)
let name = "홍길동" // String으로 추론
let age: Int = 25 // 명시적 타입 선언
let pi = 3.14159 // Double로 추론
// name = "김영희" // 컴파일 에러! let은 재할당 불가
// var — 변수 (가변)
var score = 85
score = 92 // 재할당 가능
// 문자열 보간 (String Interpolation)
print("\(name) (\(age)세), 점수: \(score)")
// 실행 결과: 홍길동 (25세), 점수: 92
// 여러 줄 문자열
let message = """
안녕하세요,
Swift를 시작합니다.
현재 점수는 \(score)점입니다.
"""
print(message)
// 실행 결과:
// 안녕하세요,
// Swift를 시작합니다.
// 현재 점수는 92점입니다.
// 컬렉션 타입
let fruits = ["사과", "바나나", "포도"] // Array
let scoreMap = ["국어": 95, "영어": 88] // Dictionary
let uniqueNums: Set = [1, 2, 3, 2, 1] // Set (중복 제거)
print("과일: \(fruits)")
print("국어 점수: \(scoreMap["국어"] ?? 0)")
print("고유 숫자: \(uniqueNums)")
// 실행 결과:
// 과일: ["사과", "바나나", "포도"]
// 국어 점수: 95
// 고유 숫자: [1, 2, 3]
Swift에서는 let을 기본으로 사용하고, 값을 변경해야 할 때만 var을 사용하는 것이 권장됩니다.
옵셔널 (Optional)
옵셔널은 Swift의 핵심 개념입니다. 값이 있을 수도 있고 없을 수도 있는(nil) 상황을 타입으로 표현합니다. 옵셔널이 아닌 타입에는 nil을 대입할 수 없으므로, 컴파일 타임에 null 참조 오류를 방지합니다.
import Foundation
// 옵셔널 선언 — 타입 뒤에 ? 붙임
var nickname: String? = "길동이"
var phone: String? = nil // 값 없음
// if let — 옵셔널 바인딩 (안전한 언래핑)
if let unwrapped = nickname {
print("닉네임: \(unwrapped)") // nil이 아닐 때만 실행
} else {
print("닉네임 없음")
}
// 실행 결과: 닉네임: 길동이
// guard let — 조기 반환 패턴
func greet(name: String?) {
guard let validName = name else {
print("이름이 없습니다.")
return // nil이면 여기서 반환
}
// 이 이후로 validName은 String (옵셔널 아님)
print("안녕하세요, \(validName)님!")
}
greet(name: "김영희")
// 실행 결과: 안녕하세요, 김영희님!
greet(name: nil)
// 실행 결과: 이름이 없습니다.
// nil 병합 연산자 (??) — 기본값 제공
let displayName = nickname ?? "이름 없음"
let displayPhone = phone ?? "번호 없음"
print("표시명: \(displayName), 전화: \(displayPhone)")
// 실행 결과: 표시명: 길동이, 전화: 번호 없음
// 옵셔널 체이닝 (?.) — 안전한 프로퍼티/메서드 접근
let uppercased = nickname?.uppercased()
print("대문자: \(uppercased ?? "nil")")
// 실행 결과: 대문자: 길동이
// map으로 옵셔널 변환
let length: Int? = nickname.map { $0.count }
print("닉네임 길이: \(length ?? 0)")
// 실행 결과: 닉네임 길이: 3
!(강제 언래핑)는 nil일 때 런타임 크래시를 일으키므로 가능한 사용을 피합니다. if let, guard let, ??를 조합하여 안전하게 처리합니다.
함수와 클로저
Swift의 함수는 매개변수 레이블(argument label)을 지원하여 호출 시 가독성이 높습니다.
import Foundation
// 함수 — 매개변수 레이블 사용
func calculateBMI(weight: Double, height: Double) -> Double {
return weight / (height * height)
}
// 외부/내부 매개변수 이름 분리
func greetUser(to name: String, with title: String = "님") -> String {
return "안녕하세요, \(name)\(title)!"
}
// 가변 매개변수
func average(of numbers: Double...) -> Double {
guard !numbers.isEmpty else { return 0 }
return numbers.reduce(0, +) / Double(numbers.count)
}
print(calculateBMI(weight: 70, height: 1.75))
// 실행 결과: 22.857142857142858
print(greetUser(to: "홍길동"))
// 실행 결과: 안녕하세요, 홍길동님!
print(greetUser(to: "홍길동", with: " 선생님"))
// 실행 결과: 안녕하세요, 홍길동 선생님!
print(average(of: 85, 92, 78, 90))
// 실행 결과: 86.25
// 클로저 — 일급 함수
let numbers = [5, 3, 8, 1, 9, 2, 7]
// 정렬 — 클로저 사용
let sorted = numbers.sorted { $0 < $1 } // 약식 인자 이름
print("정렬: \(sorted)")
// 실행 결과: 정렬: [1, 2, 3, 5, 7, 8, 9]
// map, filter, reduce 체이닝
let result = numbers
.filter { $0 > 3 } // 3 초과
.map { $0 * 2 } // 2배
.reduce(0, +) // 합계
print("결과: \(result)")
// 실행 결과: 결과: 62
// 고차 함수 정의
func transform(_ items: [Int], using operation: (Int) -> Int) -> [Int] {
return items.map(operation)
}
let doubled = transform(numbers, using: { $0 * 2 })
print("2배: \(doubled)")
// 실행 결과: 2배: [10, 6, 16, 2, 18, 4, 14]
열거형과 패턴 매칭
Swift의 열거형(enum)은 연관 값(Associated Values)을 가질 수 있어 매우 강력합니다.
import Foundation
// 연관 값이 있는 열거형
enum NetworkResult {
case success(data: String, statusCode: Int)
case failure(error: String, statusCode: Int)
case loading
}
// 패턴 매칭 — switch
func handleResult(_ result: NetworkResult) {
switch result {
case .success(let data, let code):
print("성공 [\(code)]: \(data)")
case .failure(let error, let code) where code >= 500:
print("서버 에러 [\(code)]: \(error)")
case .failure(let error, let code):
print("클라이언트 에러 [\(code)]: \(error)")
case .loading:
print("로딩 중...")
}
}
handleResult(.success(data: "사용자 데이터", statusCode: 200))
// 실행 결과: 성공 [200]: 사용자 데이터
handleResult(.failure(error: "서버 점검", statusCode: 503))
// 실행 결과: 서버 에러 [503]: 서버 점검
handleResult(.failure(error: "인증 실패", statusCode: 401))
// 실행 결과: 클라이언트 에러 [401]: 인증 실패
handleResult(.loading)
// 실행 결과: 로딩 중...
// if case let — 특정 케이스만 추출
let results: [NetworkResult] = [
.success(data: "A", statusCode: 200),
.failure(error: "B", statusCode: 404),
.success(data: "C", statusCode: 201)
]
for case .success(let data, _) in results {
print("성공 데이터: \(data)")
}
// 실행 결과:
// 성공 데이터: A
// 성공 데이터: C
정리
Swift의 핵심 특징을 요약하면 다음과 같습니다.
- let/var: 불변/가변 구분으로 안전한 코드를 작성합니다.
let을 기본으로 사용합니다. - 옵셔널:
?로 nil 가능성을 타입에 명시하고,if let,guard let,??로 안전하게 처리합니다. - 매개변수 레이블: 함수 호출 시 자연어에 가까운 가독성을 제공합니다.
- 클로저: 일급 함수와 약식 인자 이름($0, $1)으로 간결한 함수형 코드를 작성합니다.
- 열거형: 연관 값과 패턴 매칭으로 상태를 안전하게 모델링합니다.
- 강제 언래핑(
!) 사용 자제: 런타임 크래시를 방지하기 위해 안전한 언래핑 방식을 사용합니다.