[7편] 옵셔널 — nil을 타입 시스템으로 다루기

🤖 이 글은 Claude Code(AI)가 작성합니다. | 시리즈 목차 | 이전: 6편

Swift를 처음 배우는 사람이 가장 자주 막히는 지점이 옵셔널입니다. String?String과 다른 타입이라는 것, 값을 꺼내려면 반드시 언래핑해야 한다는 것, 곳곳에 붙어 있는 ?!. 처음에는 번잡하게 느껴지지만, 이것이 Swift의 안전성을 뒷받침하는 핵심 메커니즘입니다.


왜 null 대신 Optional인가

Tony Hoare는 1965년 ALGOL에 null 참조를 도입하며 나중에 이를 “10억 달러짜리 실수“라고 불렀습니다. null은 어떤 변수에든 조용히 들어올 수 있고, 그 변수를 아무 경고 없이 사용하다가 런타임에 프로그램이 터집니다.

// Java / JavaScript 스타일 (개념 설명용)
String name = null;
int length = name.length();  // NullPointerException — 실행 중에 터짐

Swift는 이 문제를 타입 시스템으로 해결합니다. “값이 없을 수도 있다”는 가능성을 타입에 명시합니다.

var name: String = "철수"   // 반드시 String 값이 있어야 함
var name: String? = nil     // String이거나 nil일 수 있음

String?Optional<String>의 축약 표현입니다. 두 가지 상태를 가질 수 있는 열거형입니다.

// Swift 내부 구현 (개념 이해용)
enum Optional<Wrapped> {
    case some(Wrapped)  // 값이 있는 경우
    case none           // 값이 없는 경우 (nil)
}

이 설계의 핵심은 nil이 들어올 수 있는 변수와 그렇지 않은 변수를 컴파일 타임에 구분한다는 것입니다. String 타입 변수에는 절대 nil이 들어올 수 없고, 컴파일러가 이를 보장합니다.


옵셔널 바인딩 — if let

옵셔널에서 값을 꺼내는 가장 기본적인 방법입니다. 값이 있을 때만 내부 블록이 실행됩니다.

let input: String? = "42"

if let number = Int(input ?? "") {
    print("숫자: \(number)")
} else {
    print("변환 실패")
}

Swift 5.7부터는 같은 이름으로 간단하게 쓸 수 있습니다.

var username: String? = "Alice"

if let username {           // if let username = username 의 축약
    print("환영합니다, \(username)")
}

여러 옵셔널을 한 번에 바인딩할 수도 있습니다. 하나라도 nil이면 전체 블록이 실행되지 않습니다.

let a: Int? = 3
let b: Int? = 4

if let a, let b {
    print(a + b)  // 7
}

guard let — 조기 탈출과 함께

4편에서 배운 guard가 옵셔널 바인딩과 자주 짝을 이룹니다. nil이면 탈출, 그렇지 않으면 이하 코드에서 언래핑된 값을 바로 씁니다.

func process(input: String?) {
    guard let input else {
        print("입력값 없음")
        return
    }
    // 여기서부터 input은 String (옵셔널 아님)
    print("처리 중: \(input)")
}

if let은 블록 안에서만 언래핑된 값을 쓸 수 있지만, guard let은 이후 모든 코드에서 쓸 수 있습니다. 함수 초반부에 전제 조건을 검사하는 패턴에 자연스럽게 어울립니다.


nil 합병 연산자 ??

옵셔널 값이 nil일 때 대신 쓸 기본값을 지정합니다.

let nickname: String? = nil
let displayName = nickname ?? "익명"
print(displayName)  // 익명

let score: Int? = 85
let finalScore = score ?? 0
print(finalScore)  // 85

중첩해서 쓸 수도 있습니다.

let a: String? = nil
let b: String? = nil
let c: String? = "값"

let result = a ?? b ?? c ?? "기본값"
print(result)  // 값

옵셔널 체이닝 ?.

옵셔널 값의 프로퍼티나 메서드에 접근할 때 ?.을 씁니다. 체인 중 하나라도 nil이면 전체 표현식이 nil이 됩니다.

struct Address {
    var city: String
}

struct Person {
    var name: String
    var address: Address?
}

let person = Person(name: "철수", address: Address(city: "서울"))
let city = person.address?.city  // Optional("서울")

let person2 = Person(name: "영희", address: nil)
let city2 = person2.address?.city  // nil

체이닝 결과는 항상 옵셔널입니다. 마지막에 ?? "기본값"으로 처리하는 패턴이 자주 쓰입니다.

let displayCity = person2.address?.city ?? "주소 없음"
print(displayCity)  // 주소 없음

강제 언래핑 ! — 언제 써도 되는가

옵셔널에서 값을 강제로 꺼냅니다. nil이면 런타임 크래시가 납니다.

let value: String? = "안녕"
print(value!)  // 안녕

let nothing: String? = nil
print(nothing!)  // 런타임 크래시

강제 언래핑은 “이 값이 nil일 리 없다”는 개발자의 단언입니다. 틀리면 앱이 죽으므로, 신중하게 써야 합니다. 실제로 쓸 수 있는 경우는 한정적입니다.

  • 바로 위에서 nil 확인을 마쳤고, 이후 코드에서 쓸 때
  • 테스트 코드에서 실패 시 즉시 알 수 있도록 의도적으로 쓸 때
  • 앱 번들에 항상 존재하는 리소스를 로드할 때

프로덕션 코드에서 !가 많이 보인다면 설계를 다시 검토하는 것이 좋습니다.


암묵적 언래핑 옵셔널 String!

타입 뒤에 !를 붙이면 접근할 때마다 자동으로 언래핑하는 옵셔널입니다.

var label: UILabel!   // 접근 시 자동 언래핑

주로 Interface Builder(Storyboard)와 연결된 아울렛(outlet)에서 쓰입니다. “초기에는 nil이지만, 이후엔 반드시 값이 있다”는 의도를 표현합니다. 일반 코드에서는 가급적 피하는 것이 좋습니다.


다른 언어와 비교

Python TypeScript Swift
null 표현 None null / undefined nil
null 가능 표시 없음 (타입 힌트 Optional[str]) string | null String?
null 안전 접근 없음 obj?.prop obj?.prop
기본값 연산자 x or "기본값" x ?? "기본값" x ?? "기본값"
null 검사 강제 여부 없음 strictNullChecks 옵션 항상 강제

핵심 요약

  • 옵셔널(String?)은 값이 있을 수도, nil일 수도 있다는 것을 타입으로 표현한다. nil 가능성을 컴파일 타임에 추적한다.
  • if let은 값이 있을 때만 블록을 실행하며 언래핑된 값을 블록 안에서 쓴다.
  • guard let은 nil이면 탈출하고, 이후 코드에서 언래핑된 값을 쓴다.
  • ??는 nil일 때 대신 쓸 기본값을 지정한다.
  • ?.은 체인 중 nil이 있으면 전체를 nil로 만든다.
  • 강제 언래핑 !은 nil이면 크래시난다. 꼭 필요한 경우에만 쓴다.

다음 편은 8편 — 구조체와 클래스: 값 타입 vs 참조 타입입니다. Swift가 클래스보다 구조체를 선호하는 이유, 그리고 둘을 언제 나눠 쓰는지를 다룹니다.

🤖 Generated with Claude Code

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 항목은 *(으)로 표시합니다