🔥 모델 클래스 정의
옵셔널 체이닝을 통해 복잡한 모델의 깊숙 곳에 정의된 속성에 접근하는 방법에 대해 알아보겠습니다. 우선 Person, Residence, Room, 그리고 Address라는 4개의 모델 클래스를 정의해 보겠습니다. 이 클래스들은 서로 연관되어 있으며, 다음 예제에서 다단계 옵셔널 체이닝을 시연하는 데 사용될 거예요.
Person 클래스
Person 클래스는 이전과 동일하게 정의됩니다:
class Person { var residence: Residence? }swift
Residence 클래스
이번에는 Residence 클래스가 좀 더 복잡해집니다. rooms라는 [Room] 타입의 변수 속성을 정의하고, 빈 배열로 초기화해요:
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("The number of rooms is \(numberOfRooms)") } var address: Address? }swift
이 버전의 Residence는 Room 인스턴스의 배열을 저장하기 때문에, numberOfRooms 속성은 저장 속성이 아닌 계산 속성으로 구현됩니다. 계산된 numberOfRooms 속성은 단순히 rooms 배열의 count 속성 값을 반환하죠.
rooms 배열에 접근하는 지름길로, 이 Residence 버전은 rooms 배열에서 요청된 인덱스에 있는 방에 접근할 수 있는 읽기-쓰기 서브스크립트를 제공합니다.
또한 이 Residence 버전은 printNumberOfRooms라는 메서드를 제공하는데, 이 메서드는 거주지의 방 수를 출력해 줍니다.
마지막으로 Residence는 Address? 타입의 address라는 옵셔널 속성을 정의합니다. 이 속성에 사용되는 Address 클래스 타입은 아래에 정의되어 있어요.
Room 클래스
rooms 배열에 사용되는 Room 클래스는 name이라는 하나의 속성과, 그 속성을 적절한 방 이름으로 설정하는 이니셜라이저를 가진 간단한 클래스입니다:
class Room { let name: String init(name: String) { self.name = name } }swift
Address 클래스
이 모델의 마지막 클래스는 Address라고 불립니다. 이 클래스에는 String? 타입의 세 개의 옵셔널 속성이 있어요.
첫 번째 두 속성인 buildingName과 buildingNumber는 주소의 일부로 특정 건물을 식별하는 대체 방법이에요. 세 번째 속성인 street은 해당 주소의 거리 이름을 나타내죠:
class Address { var buildingName: String? var buildingNumber: String? var street: String? func buildingIdentifier() -> String? { if let buildingNumber = buildingNumber, let street = street { return "\(buildingNumber) \(street)" } else if buildingName != nil { return buildingName } else { return nil } } }swift
Address 클래스는 또한 buildingIdentifier()라는 메서드를 제공하는데, 이 메서드의 반환 타입은 String?입니다.
이 메서드는 주소의 속성을 확인하고, buildingName이 값을 가지면 해당 값을 반환하고, buildingNumber와 street 둘 다 값을 가지면 이들을 연결한 문자열을 반환하며, 그 외의 경우에는 nil을 반환합니다.
이렇게 연관된 모델 클래스들을 정의했으니, 이제 옵셔널 체이닝을 사용해서 이들의 깊숙한 속성에 접근해 볼 수 있겠죠?
예를 들어, 다음과 같은 관계를 생각해 볼 수 있습니다:

이제 Person 인스턴스에서 시작해서, 옵셔널 체이닝을 통해 Residence, Room, 그리고 Address의 속성과 메서드에 접근할 수 있게 되었어요.
옵셔널 체이닝을 사용하면 중간에 어떤 속성이 nil이더라도 안전하게 접근을 시도할 수 있습니다. nil을 만나면 그냥 nil을 반환하겠죠.
예를 들면 다음과 같은 식으로 사용할 수 있습니다:
let john = Person() if let roomCount = john.residence?.numberOfRooms { print("John's residence has \(roomCount) room(s).") } else { print("Unable to retrieve the number of rooms.") } // Prints "Unable to retrieve the number of rooms."swift
여기서는 john.residence가 nil이기 때문에 numberOfRooms에 접근할 수 없어요. 따라서 else 블록이 실행되는 거죠.
반면에 Residence 인스턴스가 존재한다면, 옵셔널 체이닝을 통해 그 속성에 접근할 수 있습니다:
let someAddress = Address() someAddress.buildingNumber = "29" someAddress.street = "Acacia Road" john.residence?.address = someAddressswift
이렇게 하면 john.residence가 nil이 아닌 경우 address 속성에 someAddress가 할당됩니다. nil인 경우에는 할당이 무시되죠.
이처럼 옵셔널 체이닝을 사용하면 복잡하게 연관된 모델 타입 간에 안전하게 조건부로 접근할 수 있답니다. 중간에 nil이 있다면 문제 없이 처리할 수 있으니 정말 유용한 기능이죠? 이어지는 장에서 옵셔널 체이닝을 통한 속성 접근을 좀 더 자세히 알아 보겠습니다.