momodudu.zip

#5 Swift - Optional chaining 본문

ios/Swift

#5 Swift - Optional chaining

ally10 2019. 11. 27. 14:23

Optional chaining 이란, 사용하고자 하는 property, method, subscript등이 사용이 가능한지? 를 묻는거라고 보면 된다.

처음에 swift 가이드를 읽었을 땐 번역만으로 봤을때는 진짜 잘 안와닿는 개념이었다.

 

예제를 이해하고 나면, 진짜 그냥 그 변수를 쓸수 있는지 없는지를 체크하는거라고 보면 된다.

class Person{
    var residence: Residence?
}

class Residence{
    var numberOfRooms = 1
}


 

위와 같이 정의된 클래스가 있다고 했을 때,

 

var john = Person()

 이렇게 john이라는 instance를 사용하고자 한다. Person class는 Residence를 optional 형태로 가지고 있다.

그래서 따로 Residence를 할당해주지 않는 이상 john의 residence는 nil값임을 짐작할 수 있다.

그렇지만 이걸 code상에서 효율적으로 체크할 수 있을까?

 

먼저 john의 residence의 numOfRooms에 접근할 수 있는 방법은 두가지가 있다.

이전에 optional 포스팅에서 언급했던 forced unwrapping 방법이 있을것이다.

var roomCount = john.residence!.numberOfRooms

 

위와 같이 john이 가진 Optional Residence 껍데기를 강제로 벗기고, numOfRooms 를 가져오는 방식이다.

하지만 이전 포스팅에서도 말했듯이 forced unwrapping은 해당 값이 nil이 아닌게 개런티가 될 때 사용해야된다.

즉, 위의 코드에서는 아직 john.residence에 값을 넣어주지 않았으므로, 이 껍데기를 벗겨내도 nil 이 나온다는 말이다.

즉, 이 코드는 Runtime error를 유발시킨다.

 

그래서 이 대안으로 나온것이 바로 Optional chaining이다.

optional 값, 혹은 옵셔널이 물고있는 값에 대해 일종의 chain을 엮어서 그 값이 사용가능한지를 check 해준다.

if let roomCount = john.residence?.numberOfRooms {
    print(roomCount)
}
else {
    print("Unable to retrive numberOfRoos")
}

 

위 코드에서 if 에 걸리는 condition이 optional chaining이라고 보면 된다.

Optional binding과 비슷하지만, 다른점은 Optional binding은 check 자체를 optional 로 해야했었다.

이 케이스의 경우, if let residence = john.residence { ... } 의 형태로 이루어져 있지만,

chaining의 경우는 위와 같이 타고 들어가면서, optional인 값에 ?를 붙여주면 된다.

 

그래서 위 케이스의 경우 residence가 아직 할당되지 않았으므로 else fallback으로 빠지게 된다.

 

 

이제 좀 더 복잡한 Swift language guide 예제에 대해서 살펴보자.

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

 

클래스가 하나 더 생기고 조금 복잡해졌다.

Residence class에 Room type의 array property를 추가하고, 이 개수를 counting하기 위해 numOfRooms property를

stored property가 아닌 computed property로 변경하엿다.

그리고 room[index] 와 같이 사용하기 위해 subscript를 정의하고, Address class도 새로 추가되었다.

 

optional chaining을 통해서, 아래와 같은 값 assign도 가능하다.

let john = Person()

let someAddr = Address()
someAddr.buildingName = "aa"
someAddr.buildingName = "3"

john.residence?.address = someAddr

john의 residence는 optional로 residence가 들어가있다. 현재는 residence에 값이 없는 상태이다.

이때 = operator 의 동작을 살펴보면, optional chaining에서 nil 을 리턴하면 

= operator의 오른쪽 동작은 무력화된다.

 

위처럼 직접 instance를 assign하는 문장에서는 감이 잘 안오는데, 아래와 같이 함수를 짜서 보면 명확해진다.

let john = Person()
//john.residence = Residence()

func createAddress() -> Address{
    print("Function is called")
    
    let someAddress = Address()
    someAddress.buildingName = "aa"
    someAddress.buildingNumber = "1"
    
    return someAddress
}

john.residence?.address = createAddress()

address에 인스턴스를 직접 assign하는 대신에 함수를 하나 새로 짜서, 새롭게 생성된 instance를 리턴하는 함수를 짰다.

함수 도입부에 print를 넣어보면 확인할 수 있다.

 

주석을 그대로 놔두면 print 구문이 호출되지 않고, 주석을 풀면 print가 출력된다.

즉, optional chaining에 실패하면 함수조차 호출되지 않는다.

 

이렇게 Optional chaning을 통해서 property를 접근하는것 외에도, method를 optional chaining으로 호출 가능하다.

method가 return type이 없어도 곧 void type이므로, Optional chaining 으로 void? type을 리턴하게 된다.

 

john.residence?[0] = Room(name:"aa")

 

 위와 같은 방식으로 subscript 접근도 가능하다.

물론 지금 상태에서는 residence가 생성되지 않은 상태이므로 assign은 실패한다.

 

 

if let johnsStreet = john.residence?.address?.street {
    print("John's street \(johnsStreet)")
} else{
    print("")
}

if let buildingId = john.residence?.address?.buildingIdentifier() {
    // ...
} else {
    // ...
}

if let beginsWithThe = john.residence?.address?.buildingIdentifier()?.hasPrefix("The") {
    
}

 

 

optional chaining은 위와 같은 코드처럼 method, property 등에 대해 multipul 로 chaining도 가능하다.

'ios > Swift' 카테고리의 다른 글

#7 Swift - Protocol(1)  (0) 2019.11.28
#6 Swift - Extension  (0) 2019.11.28
#4 Swift - Initialization  (0) 2019.11.26
#3 Swift - Properties  (0) 2019.11.26
#2 Swift - Functions and Closures  (0) 2019.11.25