[Swift 공식문서 읽기]Optional Chaining

llim🧚🏻‍♀️·2021년 8월 23일
0

Swift

목록 보기
16/26
post-thumbnail

안녕하세요. 엘림입니다🙇🏻‍♀️

Swift 공식 문서를 정독하기 시리즈입니다!

제 스타일대로 정리했으니 추가적으로 더 필요한 정보는
공식문서 링크를 눌러 확인해주세용!

좀 더 편하게 보기위해 한국어로 번역된 사이트를 함께 확인했습니다!ㅎㅎ

자, 그럼 시작해볼까요

이 글은 공부하면서 작성한 글이기 때문에 잘못된 정보가 있을 수 있습니다.🥺
금방 잊어버릴... 미래의 저에게 다시 알려주기 위한 글이다보니
혹시라도 틀린 부분이 있다면, 댓글로 친절하게 알려주시길 부탁드립니다.🙏


옵셔널

어느 하나라도 nil이면 전체 결과는 nil이 됩니다.

강제 언래핑 대체

옵셔널 체이닝은 옵셔널 값 뒤에 물음표(?)를 붙여서 표현 가능합니다. 옵셔널을 사용할 수 있는 값에는 프로퍼티, 메소드 그리고 서브스크립트가 포함 됩니다. 옵셔널 값을 강제 언래핑 하기위해서 뒤에 느낌표(!)를 붙이는 것과 문법이 비슷한데, 가장 큰 차이는 강제 언레핑을 했는데 만약 그 값이 없으면 런타임 에러가 발생하지만, 옵셔널 체이닝을 사용하면 런타임 에러 대신 nil이 반환 된다는 것입니다.

옵셔널 체이닝에 의해 nil 값이 호출 될 수 있기 때문에 옵셔널 체이닝의 값은 항상 옵셔널 값이 됩니다. 옵셔널 값을 반환하지 않는 프로퍼티, 메소드 혹은 서브스크립트를 호출하더라도 옵셔널 체이닝에 의해 옵셔널 값이 반환 됩니다. 이 옵셔널 리턴 값을 이용해 옵셔널 체이닝이 성공적으로 실행 됐는지 아니면 nil을 반환 했는지 확인할 수 있습니다.

보다 구체적으로는 옵셔널 체이닝에 의해 호출되면 반환 값과 같은 타입에 옵셔널이 붙어 반환 됩니다. 예를 들어, Int를 반환하는 메소드 경우 옵셔널 체이닝이 성공적으로 실행되면 Int?를 반환합니다.

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."

john.residence = Residence()

if let roomCount = john.residence?.numberOfRooms {
    print("John's residence has \(roomCount) room(s).")
} else {
    print("Unable to retrieve the number of rooms.")
}
// Prints "John's residence has 1 room(s)."

옵셔널 체이닝을 사용한 프로퍼티 접근

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?
}

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 = buildingNumber, let street = street {
            return "\(buildingNumber) \(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.")
}
// Prints "Unable to retrieve the number of rooms."

let someAddress = Address()
someAddress.buildingNumber = "29"
someAddress.street = "Acacia Road"
john.residence?.address = someAddress

let someAddress = Address()
someAddress.buildingNumber = "29"
someAddress.street = "Acacia Road"
john.residence?.address = someAddress

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

    return someAddress
}
john.residence?.address = createAddress()
// nil

john.residence = Residence()
john.residence?.address = createAddress()
// "Function was called.

체이닝에서 앞쪽이 nil인 경우 결과로 nil을 호출하게 됩니다.
옵셔널 체이닝을 값을 할당하는데 사용할 수도 있습니다. 만약 앞쪽이 nil이라면, 할당은 실패합니다.
(할당도 할당받는 왼쪽 항이 nil이면 아예 오른쪽 항이 실행되지 않습니다.)

옵셔널 체이닝을 사용한 메소드 접근

func printNumberOfRooms() {
    print("The number of rooms is \(numberOfRooms)")
}

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."

위 메소드는 리턴 값이 명시되지 않았습니다. 하지만 함수나 메소드는 리턴 값이 없는 경우암시적으로 Void라는 값을 갖습니다. 그래서 이 메소드가 옵셔널 체이닝에서 호출되면 반환 값은 Void가 아니라 Void? 가 타입이 반환됩니다.

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

옵셔널 값을 서브스크립트로 접근 하기 위해서는 [] 괄호 전에 물음표(?) 기호를 붙여서 사용합니다.

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."

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."

옵셔널 타입의 서브스크립트 접근

만약 Swift의 사전 타입(Dictionary Type)같이 서브스크립트의 결과로 옵셔널을 반환 한다면 그 뒤에 물음표(?)를 붙여 줍니다. 사전 타입은 key-value로 동작하기 때문에 항상 그 사전에 key가 존재한다는 보장이 없기 때문에 옵셔널 값을 반환합니다.

var testScores = ["Dave": [86, 82, 84], "Bev": [79, 94, 81]]
testScores["Dave"]?[0] = 91
testScores["Bev"]?[0] += 1
testScores["Brian"]?[0] = 72
// the "Dave" array is now [91, 82, 84] and the "Bev" array is now [80, 94, 81]

체이닝의 다중 레벨 연결

옵셔널 체이닝이 여러 단계에 걸쳐 연결 될 수 있습니다.
옵셔널 체이닝의 상위 레벨 값이 옵셔널인 경우 현재 값이 옵셔널이 아니더라도 그 값은 옵셔널값이 됩니다.
상위 값이 옵셔널 값인 경우 하위도 옵셔널 값이라고 해서 더 옵셔널 해지진 않습니다. 옵셔널은 옵셔널입니다.
옵셔널 체이닝을 통해 값을 검색하거나 메소드를 호출하면 몇 단계를 거치는지 상관없이 옵셔널을 반환합니다.

if let johnsStreet = john.residence?.address?.street {
    print("John's street name is \(johnsStreet).")
} else {
    print("Unable to retrieve the address.")
}
// Prints "Unable to retrieve the address."

let johnsAddress = Address()
johnsAddress.buildingName = "The Larches"
johnsAddress.street = "Laurel Street"
john.residence?.address = johnsAddress

if let johnsStreet = john.residence?.address?.street {
    print("John's street name is \(johnsStreet).")
} else {
    print("Unable to retrieve the address.")
}
// Prints "John's street name is Laurel Street."

체이닝에서 옵셔널 값을 반환하는 메소드

만약 메소드의 반환 값을 갖고 추가적인 행동을 더 하고 싶다면 메소드 호출 표현 뒤에 물음표(?)를 붙이고 그 행동을 적어주시면 됩니다. 앞에서 언급 했던 것 처럼 옵셔널 체이닝에 물려 있기 때문에 메소드의 반환 값도 옵셔널이 돼서 그 표시를 해줘야 합니다.

옵셔널이 반환 값에 걸려있고 메소드 자체에 걸려 있는 것이 아니기 때문에 메소드 괄호 뒤에 “?”를 붙여 줍니다.

if let buildingIdentifier = john.residence?.address?.buildingIdentifier() {
    print("John's building identifier is \(buildingIdentifier).")
}
// Prints "John's building identifier is The Larches."

if let beginsWithThe =
    john.residence?.address?.buildingIdentifier()?.hasPrefix("The") {
    if beginsWithThe {
        print("John's building identifier begins with \"The\".")
    } else {
        print("John's building identifier does not begin with \"The\".")
}
}
// Prints "John's building identifier begins with "The"."

오늘도 스위프트 공식문서를 정리해보았군욥~
다음편도 힘내보겠습니다!

감사합니다🙇🏻‍♀️

profile
한달 차 iOS 개발자입니다🐥

0개의 댓글