λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°
πŸ₯– Bread Basics/Swift

Swift 곡식 λ¬Έμ„œ 정리 - μ˜΅μ…”λ„ 체이닝 (Optional Chaining)

by BreadDev 2025. 4. 11.
728x90

μ•ˆλ…•ν•˜μ„Έμš”. μ˜€λŠ˜μ€ Swift의 'μ˜΅μ…”λ„ 체이닝(Optional Chaining)'에 λŒ€ν•΄ μ•Œμ•„λ³΄κ² μŠ΅λ‹ˆλ‹€. 

πŸ“Œ μ˜΅μ…”λ„ μ²΄μ΄λ‹μ΄λž€?

μ˜΅μ…”λ„ 체이닝은 ν˜„μž¬ nil일 μˆ˜λ„ μžˆλŠ” μ˜΅μ…”λ„ κ°’μ˜ ν”„λ‘œνΌν‹°, λ©”μ„œλ“œ, μ„œλΈŒμŠ€ν¬λ¦½νŠΈμ— μ•ˆμ „ν•˜κ²Œ μ ‘κ·Όν•˜λŠ” ν”„λ‘œμ„ΈμŠ€μž…λ‹ˆλ‹€. μ˜΅μ…”λ„ 값이 μ‹€μ œ 값을 κ°€μ§€κ³  있으면 호좜이 μ„±κ³΅ν•˜μ§€λ§Œ, nil이면 전체 ν‘œν˜„μ‹μ€ nil을 λ°˜ν™˜ν•©λ‹ˆλ‹€.

μ˜΅μ…”λ„ 체이닝(Optional Chaining): μ—¬λŸ¬ μ˜΅μ…”λ„ 값을 μ—°κ²°ν•΄μ„œ μ ‘κ·Όν•  λ•Œ, 쀑간에 ν•˜λ‚˜λΌλ„ nil이면 μ•ˆμ „ν•˜κ²Œ μ‹€νŒ¨ν•˜λŠ” μ ‘κ·Ό 방식

πŸ“Œ κ°•μ œ μ–Έλž˜ν•‘ λŒ€μ‹  μ˜΅μ…”λ„ 체이닝 μ‚¬μš©ν•˜κΈ°

μ˜΅μ…”λ„ 체이닝은 κ°•μ œ μ–Έλž˜ν•‘μ˜ μ•ˆμ „ν•œ λŒ€μ•ˆμž…λ‹ˆλ‹€. κ°•μ œ μ–Έλž˜ν•‘(!)은 μ˜΅μ…”λ„μ΄ nil일 경우 λŸ°νƒ€μž„ 였λ₯˜λ₯Ό λ°œμƒμ‹œν‚€μ§€λ§Œ, μ˜΅μ…”λ„ 체이닝(?)은 λ‹¨μˆœνžˆ nil을 λ°˜ν™˜ν•©λ‹ˆλ‹€.

κΈ°λ³Έ 문법 비ꡐ

// κ°•μ œ μ–Έλž˜ν•‘ - nil일 경우 ν¬λž˜μ‹œ λ°œμƒ
let roomCount = john.residence!.numberOfRooms

// μ˜΅μ…”λ„ 체이닝 - nil일 경우 μ•ˆμ „ν•˜κ²Œ nil λ°˜ν™˜
let roomCount = john.residence?.numberOfRooms

κ°„λ‹¨ν•œ 예제λ₯Ό 톡해 이해해 λ΄…μ‹œλ‹€:

class Person {
    var residence: Residence?
}

class Residence {
    var numberOfRooms = 1
}

let john = Person()  // residenceλŠ” 기본적으둜 nil

// κ°•μ œ μ–Έλž˜ν•‘: λŸ°νƒ€μž„ 였λ₯˜ λ°œμƒ!
// let roomCount = john.residence!.numberOfRooms

// μ˜΅μ…”λ„ 체이닝: μ•ˆμ „ν•˜κ²Œ nil λ°˜ν™˜
if let roomCount = john.residence?.numberOfRooms {
    print("John의 집은 \(roomCount)개의 방이 μžˆμŠ΅λ‹ˆλ‹€.")
} else {
    print("방의 개수λ₯Ό 확인할 수 μ—†μŠ΅λ‹ˆλ‹€.")
}
// 좜λ ₯: "방의 개수λ₯Ό 확인할 수 μ—†μŠ΅λ‹ˆλ‹€."

μ€‘μš”: μ˜΅μ…”λ„ 체이닝은 ν•΄λ‹Ή ν”„λ‘œνΌν‹°κ°€ μ›λž˜ μ˜΅μ…”λ„μ΄ μ•„λ‹ˆλ”λΌλ„ κ²°κ³Όλ₯Ό 항상 μ˜΅μ…”λ„λ‘œ λž˜ν•‘ν•©λ‹ˆλ‹€. 예λ₯Ό λ“€μ–΄, numberOfRoomsλŠ” Int νƒ€μž…μ΄μ§€λ§Œ residence?.numberOfRooms의 κ²°κ³ΌλŠ” Int? νƒ€μž…μž…λ‹ˆλ‹€.

πŸ“Œ μ˜΅μ…”λ„ 체이닝 λͺ¨λΈ 예제

λ‹€μ–‘ν•œ μ˜΅μ…”λ„ 체이닝 μ‹œλ‚˜λ¦¬μ˜€λ₯Ό 더 잘 μ΄ν•΄ν•˜κΈ° μœ„ν•΄, μ—¬λŸ¬ 관계λ₯Ό κ°€μ§„ 클래슀 λͺ¨λΈμ„ μ‚΄νŽ΄λ΄…μ‹œλ‹€:

class Person {
    var residence: Residence?
}

class Residence {
    var rooms: [Room] = []
    var numberOfRooms: Int {
        return rooms.count
    }
    
    subscript(i: Int) -> Room {
        get {
            return rooms[i]
        }
        set {
            rooms[i] = newValue
        }
    }
    
    func printNumberOfRooms() {
        print("방의 κ°œμˆ˜λŠ” \(numberOfRooms)개 μž…λ‹ˆλ‹€")
    }
    
    var address: Address?
}

class Room {
    let name: String
    init(name: String) { self.name = name }
}

class Address {
    var buildingName: String?
    var buildingNumber: String?
    var street: String?
    
    func buildingIdentifier() -> String? {
        if let buildingNumber, let street {
            return "\(buildingNumber) \(street)"
        } else if buildingName != nil {
            return buildingName
        } else {
            return nil
        }
    }
}

이 λͺ¨λΈμ„ μ‚¬μš©ν•˜μ—¬ μ˜΅μ…”λ„ μ²΄μ΄λ‹μ˜ λ‹€μ–‘ν•œ 츑면을 μ‚΄νŽ΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

πŸ“Œ μ˜΅μ…”λ„ 체이닝을 ν†΅ν•œ ν”„λ‘œνΌν‹° μ ‘κ·Ό

μ˜΅μ…”λ„ κ°’μ˜ ν”„λ‘œνΌν‹°μ— μ ‘κ·Όν•  λ•Œ μ˜΅μ…”λ„ 체이닝을 μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€:

let john = Person()

// ν”„λ‘œνΌν‹° 읽기
if let roomCount = john.residence?.numberOfRooms {
    print("John의 집은 \(roomCount)개의 방이 μžˆμŠ΅λ‹ˆλ‹€.")
} else {
    print("방의 개수λ₯Ό 확인할 수 μ—†μŠ΅λ‹ˆλ‹€.")
}
// 좜λ ₯: "방의 개수λ₯Ό 확인할 수 μ—†μŠ΅λ‹ˆλ‹€."

// ν”„λ‘œνΌν‹° μ„€μ •ν•˜κΈ°
let someAddress = Address()
someAddress.buildingNumber = "29"
someAddress.street = "Acacia Road"

john.residence?.address = someAddress  // residenceκ°€ nilμ΄λ―€λ‘œ 이 할당은 λ¬΄μ‹œλ¨

주의: μ˜΅μ…”λ„ μ²΄μ΄λ‹μœΌλ‘œ ν”„λ‘œνΌν‹° 값을 μ„€μ •ν•˜λ €κ³  ν•  λ•Œ, 체인의 μ–΄λŠ 뢀뢄이든 nil이면 전체 ν‘œν˜„μ‹μ€ ν‰κ°€λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. μ΄λŠ” 였λ₯Έμͺ½ ν‘œν˜„μ‹(ν• λ‹Ήν•  κ°’)μ‘°μ°¨ ν‰κ°€λ˜μ§€ μ•ŠμŒμ„ μ˜λ―Έν•©λ‹ˆλ‹€.

πŸ“Œ μ˜΅μ…”λ„ 체이닝을 ν†΅ν•œ λ©”μ„œλ“œ 호좜

μ˜΅μ…”λ„ 체이닝을 톡해 λ©”μ„œλ“œλ„ ν˜ΈμΆœν•  수 μžˆμŠ΅λ‹ˆλ‹€. λ©”μ„œλ“œκ°€ λ°˜ν™˜κ°’μ΄ 없더라도 호좜의 성곡 μ—¬λΆ€λ₯Ό 확인할 수 μžˆμŠ΅λ‹ˆλ‹€:

// λ©”μ„œλ“œ 호좜 μ‹œλ„
if john.residence?.printNumberOfRooms() != nil {
    print("방의 개수λ₯Ό 좜λ ₯ν•  수 μžˆμŠ΅λ‹ˆλ‹€.")
} else {
    print("방의 개수λ₯Ό 좜λ ₯ν•  수 μ—†μŠ΅λ‹ˆλ‹€.")
}
// 좜λ ₯: "방의 개수λ₯Ό 좜λ ₯ν•  수 μ—†μŠ΅λ‹ˆλ‹€."

μ°Έκ³ : λ°˜ν™˜κ°’μ΄ μ—†λŠ” λ©”μ„œλ“œ(Void λ°˜ν™˜)도 μ˜΅μ…”λ„ 체이닝을 톡해 ν˜ΈμΆœν•˜λ©΄ Void?κ°€ λ°˜ν™˜λ©λ‹ˆλ‹€. 이λ₯Ό 톡해 λ©”μ„œλ“œ 호좜의 성곡 μ—¬λΆ€λ₯Ό 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.

πŸ“Œ μ˜΅μ…”λ„ 체이닝을 ν†΅ν•œ μ„œλΈŒμŠ€ν¬λ¦½νŠΈ μ ‘κ·Ό

λ°°μ—΄μ΄λ‚˜ λ”•μ…”λ„ˆλ¦¬μ˜ μ„œλΈŒμŠ€ν¬λ¦½νŠΈμ— μ˜΅μ…”λ„ 체이닝을 μ μš©ν•  μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€:

// λ°°μ—΄ μ„œλΈŒμŠ€ν¬λ¦½νŠΈ μ ‘κ·Ό
if let firstRoomName = john.residence?[0].name {
    print("첫 번째 방의 이름은 \(firstRoomName)μž…λ‹ˆλ‹€.")
} else {
    print("첫 번째 방의 이름을 확인할 수 μ—†μŠ΅λ‹ˆλ‹€.")
}
// 좜λ ₯: "첫 번째 방의 이름을 확인할 수 μ—†μŠ΅λ‹ˆλ‹€."

// μœ νš¨ν•œ residence μ„€μ • ν›„ λ‹€μ‹œ μ‹œλ„
let johnsHouse = Residence()
johnsHouse.rooms.append(Room(name: "κ±°μ‹€"))
johnsHouse.rooms.append(Room(name: "μ£Όλ°©"))
john.residence = johnsHouse

if let firstRoomName = john.residence?[0].name {
    print("첫 번째 방의 이름은 \(firstRoomName)μž…λ‹ˆλ‹€.")
} else {
    print("첫 번째 방의 이름을 확인할 수 μ—†μŠ΅λ‹ˆλ‹€.")
}
// 좜λ ₯: "첫 번째 방의 이름은 κ±°μ‹€μž…λ‹ˆλ‹€."

문법 μ°Έκ³ : μ„œλΈŒμŠ€ν¬λ¦½νŠΈ μ‚¬μš© μ‹œ μ˜΅μ…”λ„ μ²΄μ΄λ‹μ˜ λ¬ΌμŒν‘œ(?)λŠ” μ„œλΈŒμŠ€ν¬λ¦½νŠΈμ˜ λŒ€κ΄„ν˜Έ([]) μ•žμ— μœ„μΉ˜ν•©λ‹ˆλ‹€.

μ˜΅μ…”λ„ νƒ€μž…μ˜ μ„œλΈŒμŠ€ν¬λ¦½νŠΈ 처리

λ”•μ…”λ„ˆλ¦¬μ²˜λŸΌ μ„œλΈŒμŠ€ν¬λ¦½νŠΈκ°€ 이미 μ˜΅μ…”λ„ 값을 λ°˜ν™˜ν•˜λŠ” 경우, λŒ€κ΄„ν˜Έ 뒀에 λ¬ΌμŒν‘œλ₯Ό λΆ™μž…λ‹ˆλ‹€:

var testScores = ["Dave": [86, 82, 84], "Bev": [79, 94, 81]]
testScores["Dave"]?[0] = 91
testScores["Bev"]?[0] += 1
testScores["Brian"]?[0] = 72  // "Brian" ν‚€κ°€ μ—†μœΌλ―€λ‘œ λ¬΄μ‹œλ¨

πŸ“Œ μ—¬λŸ¬ μˆ˜μ€€μ˜ 체인 μ—°κ²°

μ˜΅μ…”λ„ 체이닝은 μ—¬λŸ¬ μˆ˜μ€€μœΌλ‘œ μ—°κ²°ν•  수 μžˆμŠ΅λ‹ˆλ‹€:

// μ—¬λŸ¬ μˆ˜μ€€μ˜ ν”„λ‘œνΌν‹° 체이닝
if let johnsStreet = john.residence?.address?.street {
    print("John의 거리 이름은 \(johnsStreet)μž…λ‹ˆλ‹€.")
} else {
    print("μ£Όμ†Œλ₯Ό 확인할 수 μ—†μŠ΅λ‹ˆλ‹€.")
}
// 좜λ ₯: "μ£Όμ†Œλ₯Ό 확인할 수 μ—†μŠ΅λ‹ˆλ‹€." (addressκ°€ 아직 nilμž„)

// μ£Όμ†Œ μ„€μ • ν›„ λ‹€μ‹œ μ‹œλ„
let johnsAddress = Address()
johnsAddress.buildingName = "The Larches"
johnsAddress.street = "Laurel Street"
john.residence?.address = johnsAddress

if let johnsStreet = john.residence?.address?.street {
    print("John의 거리 이름은 \(johnsStreet)μž…λ‹ˆλ‹€.")
} else {
    print("μ£Όμ†Œλ₯Ό 확인할 수 μ—†μŠ΅λ‹ˆλ‹€.")
}
// 좜λ ₯: "John의 거리 이름은 Laurel Streetμž…λ‹ˆλ‹€."

μ€‘μš”: μ—¬λŸ¬ μˆ˜μ€€μ˜ μ˜΅μ…”λ„ 체이닝을 μ‚¬μš©ν•΄λ„ λ°˜ν™˜ κ°’μ˜ μ˜΅μ…”λ„ μˆ˜μ€€μ΄ λŠ˜μ–΄λ‚˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. 즉, μ›λž˜ λ°˜ν™˜ νƒ€μž…μ΄ Intλ©΄ κ²°κ³ΌλŠ” Int?이고, μ›λž˜ λ°˜ν™˜ νƒ€μž…μ΄ Int?λ©΄ 결과도 Int?μž…λ‹ˆλ‹€.

πŸ“Œ μ˜΅μ…”λ„ λ°˜ν™˜κ°’μ„ κ°€μ§„ λ©”μ„œλ“œ 체이닝

μ˜΅μ…”λ„ 값을 λ°˜ν™˜ν•˜λŠ” λ©”μ„œλ“œ 호좜 결과에도 체이닝을 μ—°κ²°ν•  수 μžˆμŠ΅λ‹ˆλ‹€:

// μ˜΅μ…”λ„ λ°˜ν™˜κ°’μ„ κ°€μ§„ λ©”μ„œλ“œ 호좜
if let buildingIdentifier = john.residence?.address?.buildingIdentifier() {
    print("John의 건물 μ‹λ³„μžλŠ” \(buildingIdentifier)μž…λ‹ˆλ‹€.")
}
// 좜λ ₯: "John의 건물 μ‹λ³„μžλŠ” The Larchesμž…λ‹ˆλ‹€."

// λ©”μ„œλ“œ λ°˜ν™˜κ°’μ— μΆ”κ°€ 체이닝
if let beginsWithThe = john.residence?.address?.buildingIdentifier()?.hasPrefix("The") {
    if beginsWithThe {
        print("John의 건물 μ‹λ³„μžλŠ” \"The\"둜 μ‹œμž‘ν•©λ‹ˆλ‹€.")
    } else {
        print("John의 건물 μ‹λ³„μžλŠ” \"The\"둜 μ‹œμž‘ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.")
    }
}
// 좜λ ₯: "John의 건물 μ‹λ³„μžλŠ” "The"둜 μ‹œμž‘ν•©λ‹ˆλ‹€."

문법 μ°Έκ³ : λ©”μ„œλ“œ λ°˜ν™˜κ°’μ— 체이닝을 κ³„μ†ν•˜λ €λ©΄ λ©”μ„œλ“œμ˜ μ†Œκ΄„ν˜Έ() 뒀에 λ¬ΌμŒν‘œλ₯Ό λΆ™μž…λ‹ˆλ‹€.

πŸ“Œ μ˜΅μ…”λ„ 체이닝 ν™œμš© 팁

1. μ˜΅μ…”λ„ 바인딩과 ν•¨κ»˜ μ‚¬μš©ν•˜κΈ°

κ°€μž₯ 일반적인 νŒ¨ν„΄μ€ μ˜΅μ…”λ„ 체이닝과 μ˜΅μ…”λ„ 바인딩(if let)을 ν•¨κ»˜ μ‚¬μš©ν•˜λŠ” κ²ƒμž…λ‹ˆλ‹€:

if let roomCount = john.residence?.numberOfRooms {
    // roomCountλ₯Ό μ•ˆμ „ν•˜κ²Œ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€
}

2. κ°€λ“œ ꡬ문과 ν•¨κ»˜ μ‚¬μš©ν•˜κΈ°

μ‘°κΈ° λ°˜ν™˜μ΄ ν•„μš”ν•œ 경우 guard letκ³Ό ν•¨κ»˜ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€:

guard let roomCount = john.residence?.numberOfRooms else {
    print("방의 개수λ₯Ό 확인할 수 μ—†μŠ΅λ‹ˆλ‹€.")
    return
}
// μ—¬κΈ°μ„œλΆ€ν„°λŠ” roomCountλ₯Ό μ•ˆμ „ν•˜κ²Œ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€

3. 짧은 μ‘°κ±΄λ¬Έμ—μ„œ ν™œμš©ν•˜κΈ°

nil 병합 μ—°μ‚°μž(??)와 ν•¨κ»˜ μ‚¬μš©ν•˜λ©΄ 기본값을 μ œκ³΅ν•  수 μžˆμŠ΅λ‹ˆλ‹€:

let roomCount = john.residence?.numberOfRooms ?? 0

4. μ»¬λ ‰μ…˜κ³Ό ν•¨κ»˜ μ‚¬μš©ν•˜κΈ°

λ°°μ—΄, λ”•μ…”λ„ˆλ¦¬ λ“±μ˜ μ»¬λ ‰μ…˜μ—μ„œ 특히 μœ μš©ν•©λ‹ˆλ‹€:

// λ°°μ—΄μ˜ 첫 번째 μš”μ†Œμ— μ•ˆμ „ν•˜κ²Œ μ ‘κ·Ό
let firstRoom = john.residence?.rooms.first?.name

// λ”•μ…”λ„ˆλ¦¬ 값에 μ•ˆμ „ν•˜κ²Œ μ ‘κ·Ό
let daveScore = testScores["Dave"]?[0]

πŸ“Œ 정리

μ˜΅μ…”λ„ 체이닝은 Swift의 μ•ˆμ „μ„± 철학을 잘 λ³΄μ—¬μ£ΌλŠ” κΈ°λŠ₯μž…λ‹ˆλ‹€. 이λ₯Ό 톡해:

  1. nil λ•Œλ¬Έμ— λ°œμƒν•˜λŠ” λŸ°νƒ€μž„ ν¬λž˜μ‹œλ₯Ό λ°©μ§€ν•  수 μžˆμŠ΅λ‹ˆλ‹€
  2. 연쇄적인 μ˜΅μ…”λ„ κ°’ 처리λ₯Ό κ°„κ²°ν•˜κ²Œ ν‘œν˜„ν•  수 μžˆμŠ΅λ‹ˆλ‹€
  3. μ½”λ“œμ˜ 방어적 ν”„λ‘œκ·Έλž˜λ°μ΄ μžμ—°μŠ€λŸ½κ²Œ μ΄λ£¨μ–΄μ§‘λ‹ˆλ‹€

μ˜΅μ…”λ„ 체이닝을 효과적으둜 ν™œμš©ν•˜λ©΄ μ½”λ“œκ°€ 더 μ•ˆμ „ν•˜κ³  κ°„κ²°ν•΄μ§ˆ 뿐만 μ•„λ‹ˆλΌ, 가독성도 ν–₯μƒλ©λ‹ˆλ‹€.