티스토리 뷰

옵셔널 체이닝은 nil일 수도 있는 프로퍼티나, 메서드 그리고 서브스크립트에 질의를 하는 과정을 말한다. 

만약 옵셔널이 프로퍼티나 메소드 혹은 서브스크립트에 대한 값을 갖고 있다면, 그 값을 반환하고 값이 nil이면 nil을 반환한다.

 

이전에 처음 옵셔널이라는 개념을 공부하면서 슬쩍 접했던 내용이기는 하다.

 

그 문법과 활용을 자세히 알아보도록 하자.


1. 강제 언래핑의 대체로써의 옵셔널 체이닝

옵셔널 체이닝은 옵셔널 값 뒤에 물음표를 붙여 표현이 가능하다. 옵셔널 값을 강제 언래핑 하기 위해서는 뒤에 느낌표를 붙이는 것과 문법이 비슷하지만, 언래핑을 !로 했는데 만약 그 값이 nil 이라면 런타임 에러를 발생시키지만, 옵셔널 체이닝을 활용하면 런타임 에러 대신 nil이 반환 된다는 것이다.

 

아래 예제는 옵셔널 체이닝과 강제 언래핑이 어떻게 다른 지 확인할 수 있는 예제이다.

class Person {
    var residence: Residence?
}

class Residence {
    var numberOfRooms = 1
}

let john = Person()

///// 강제 언래핑 /////
let roomCount = john.residence!.numberOfRooms
// this triggers a runtime error


///// 옵셔널 체이닝 /////
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."

2. 옵셔널 체이닝을 위한 모델 클래스 정의

옵셔널 체이닝을 프로퍼티, 메서드 그리고 서브스크립트에서도 사용할 수 있는데, 한 단계가 아닌 여러 level로 multilevel optional chaining 을 사용할 수 있다.

 

이번에는 위 예제를 확장하여 Room과 Address 클래스를 추가한 4가지 모델을 정의해보자.

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("The number of rooms is \(numberOfRooms)")
    }
    var address: Address?
}

Residence 클래스는 이전 보다 조금 더 복잡하게 빈 Room 배열과 배열 엘리먼트를 set, get 할 서브스크립트와 메서드를 추가했다.

 

그리고 아래에 클래스 이름 하나를 인자로 받아 초기화 하는 Room 객체와 3개의 String? 옵셔널 프로퍼티를 갖는 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 buildingName = buildingName, let street = street {
            return "\(buildingName) \(street)"
        } else if buildingName != nil {
            return buildingName
        } else {
            return 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.")
}

let someAddress = Address()
someAddress.buildingNumber = "29"
someAddress.street = "Acacia Road"

john.residence?.address = someAddress

위 코드의 경우 john 이라는 Person 인스턴스의 프로퍼티인 residence가 그 어디에도 할당되지 않았기 때문에 nil이며, someAddress 할당도 실패하지만 에러는 나지 않는다.

 

func createAddress() -> Address {
    print("Function was called")
    let someAddress = Address()
    someAddress.buildingNumber = "29"
    someAddress.street = "Acacia Road"
        
    return someAddress
}

john.residence?.address = createAddress()

위 코드를 실행하면, Function was called 구문이 아예 출력이 되지 않는다. john.residence? 가 nil 이기 때문에 할당에 실패하며, 그럼 우변 또한 실행이 되지 않기 때문이다.

 

- 옵셔널 체이닝을 통한 메서드 호출

마찬가지로 옵셔널 체이닝을 이용한 메서드 호출 또한 가능하다.

 

위 residence 클래스의 printNumberOfRooms() 메서드의 반환 타입이 nil 인지 판단하여 다른 출력 값을 내보내는 예제이다.

if john.residence?.printNumberOfRooms() != nil {
    print("It was possible to print the number of rooms.")
} else {
    print("It was not possible to print the number of rooms.")
}
// Prints "It was not possible to print the number of rooms."


if (john.residence?.address = someAddress) != nil {
    print("It was possible to set the address.")
} else {
    print("It was not possible to set the address.")
}
// Prints "It was not possible to set the address."

 

- 옵셔널 체이닝을 이용한 서브스크립트 접근

같은 맥락이다. 옵셔널 체이닝에 [] 을 붙여 서브스크립트를 호출할 수 있다.

if let firstRoomName = john.residence?[0].name {
    print("The first room name is \(firstRoomName).")
} else {
    print("Unable to retrieve the first room name.")
}
// Prints "Unable to retrieve the first room name."

위 예제는 마찬가지로 resience가 nil이기 때문에 할당에 실패한다.

 

residence 인스턴스를 아래와 같이 할당해준다면, residence 서브스크립트를 사용할 수 있다.

let johnsHouse = Residence()
johnsHouse.rooms.append(Room(name: "Living Room"))
johnsHouse.rooms.append(Room(name: "Kitchen"))
john.residence = johnsHouse

 if let firstRoomName = john.residence?[0].name {
    print("The first room name is \(firstRoomName).")
} else {
    print("Unable to retrieve the first room name.")
}
// Prints "The first room name is Living Room."

 

Comments