Diana의 iOS 개발일기

[스위프트 프로그래밍 3판] - 14. 옵셔널 체이닝과 빠른 종료 본문

Swift/책 정리

[스위프트 프로그래밍 3판] - 14. 옵셔널 체이닝과 빠른 종료

Diana_iOS 2021. 3. 17. 15:14

옵셔널은 스위프트에서 굉장히 중요한 개념으로 통합니다.

옵셔널로 지정된 프로퍼티는 어떤 객체든 nil 을 가질 수 있는 Objective-C 와의 상호작용성이 확보되지요.

이러한 옵셔널에서 자주 사용되는 것이 옵셔널 체이닝으로 옵셔널 체이닝은 사용에 있어 실행 플로우를 이해하는 것이 가장 중요합니다.


[옵셔널 체이닝(Optional Chaining)]

옵셔널 체이닝은 옵셔널의 가능성을 포함하고 있는, nil 일지도 모르는 프로퍼티, 메서드, 서브스크립션 등을 가져오거나 호출할 때 사용할 수 있는 일련의 과정입니다.

옵셔널 체이닝은 옵셔널을 반복 사용하여 코드 상에서는 옵셔널이 체인처럼 연결되어 있는 형태이므로 옵셔널 체이닝이라고 부릅니다.

옵셔널 체이닝은 여러 값들이 서로서로 연관된, 즉 중첩된 형태를 띄고 있으며 중첩된 값들 중 단 하나라도 nil 을 가진다면 결과 값으로 nil 을 반환하는 구조를 가지고 있습니다.

 

옵셔널 체이닝은 아래와 같이 세 가지 경우에서 사용이 가능합니다.

 

1. 값 호출

class Room { // 호실
    var number: Int

    init(number: Int){
        self.number = number
    }
}

class Building { // 건물
    var name: String
    var room: Room?
    
    init(name: String) {
        self.name = name
    }
}

struct Address { // 주소
    var province: String
    var city: String
    var street: String
    var building: Building?
    var detailAddress: String?
}

class Person { // 사람
    var name: String
    var address: Address?
    
    init(name: String){
        self.name = name
    }
}

옵셔널 체이닝은 위에서도 언급했다싶이 여러 값들이 중첩되었을 때 비로소 빛을 발하기 때문에 위의 예제에서는 중첩된 여러 값들을 선언해놓았습니다. 좀더 수월한 이해를 위해 이미지를 추가하자면 해당 값들은 아래와 같은 형식을 띄고 있습니다.

이제 순서대로 인스턴스를 생성하고 옵셔널 체이닝을 사용해보겠습니다.

let yagom: Person = Person(name: "yagom")

let yagomRoomViaOptionalChaining: Int? = yagom.address?.building?.room?.number //nil

let yagomRoomViaOptionalUnwarping: Int = yagom.address!.building!.room!.number //오류

위의 예제에서는 상수 yagomRoomViaOptionalChaining와 yagomRoomViaOptionalUnwarping 에서는 yagom의 address에 우선 접근합니다.

하지만 yagom의 address는 현재 nil 이므로 address가 옵셔널 처리가 된 상수 yagomRoomViaOptionalChaining는 에러를 발생하지 않고 이후 building에 접근하지만 강제 언래핑 처리가 된 상수 yagomRoomViaOptionalUnwarping는 에러를 발생시키게 됩니다.

 

실행 플로우는 아래와 같습니다. 

 

2. 값 할당

옵셔널 체이닝은 위와 같이 값을 받아오는데도 사용할 수 있지만 값을 할당하는 것도 가능합니다.

yagom.address = Address(province: "충청북도", city: "청주시 청원구", street: "충청대로", building: nil, detailAddress: nil)
yagom.address?.building = Building(name: "곰굴")
yagom.address?.building?.room = Room(number: 0)
yagom.address?.building?.room?.number = 505

print(yagom.address?.building?.room?.number) //Optional(505)

값을 할당하는 경우 옵셔널 체이닝에 존재하는 모든 프로퍼티에 값을 할당해주어야 에러가 발생하지 않고 이후 프로퍼티에도 값 할당이 가능해집니다.

 

3. 메서드와 서브스크립트 호출

옵셔널 체이닝을 사용하면 메서드와 서브스크립트의 호출도 가능하며 형식은 프로퍼티 호출과 동일합니다.

우선 메서드를 호출해보겠습니다. (아래 메서드는 생략된 부분이 존재합니다)

struct Address { //제일 바깥의 구조체 //주소
    var province: String
    var city: String
    var street: String
    var building: Building? // Building은 변수 name과 room을 가지고 있습니다
    var detailAddress: String?
    
    init(province: String, city: String, street: String){ //옵셔널이 지정된 변수는 초기화하지 않았습니다
        self.province = province
        self.city = city
        self.street = street
    }
    
    func fullAddress() -> String? { //메서드에 옵셔널을 설정해줍니다
        var restAddress: String? = nil
        
        if let buildingInfo: Building = self.building { //name과 room이 nil이 아닐 때 실행
            restAddress = buildingInfo.name //Building의 name을 restAddress에 넣어줍니다
        } else if let detail = self.detailAddress { 
            restAddress = detail
        }
        
        if let rest: String = restAddress {
            var fullAddress: String = self.province
            
            fullAddress += "" + self.city
            fullAddress += "" + self.street
            fullAddress += "" + rest
            
            return fullAddress
        } else {
            return nil
        }
    } //메서드1 종료
    
    func printAddress() {
        if let address: String = self.fullAddress() { //fullAddress메서드가 nil이 아닐 때 address를 출력합니다
            print(address)
        }
    } //메서드2 종료
    
} //구조체의 끝

yagom.address?.fullAddress()?.isEmpty //fullAddress() 호출 -> false
yagom.address?.printAddress() //printAddress() 호출 -> 충청북도 청주시 청원구 충청대로 곰굴

 

서브스크립트의 호출은 대괄호([ ])보다 앞에 옵셔널을 표기해주어야 합니다. 이는 서브스크립트 외에도 언제나 옵셔널 체이닝을 사용할 떄의 규칙으로 예제는 아래와 같습니다.

let optionalArray: [Int]? = [1, 2, 3]
optionalArray?[1] //2

var optionalDictionary: [Stirng: [Int]]? = [String: [Int]]()
optionalDictionary?["numberArray"] = optionalArray //key값인 numberArray에 optionalArray를 value로 할당
optionalDictionary?["numberArray"]?[2] //["numberArray : [1, 2, 3]]의 형태

 


[빠른 종료(Early Exit)]

빠른 종료는 Bool타입의 값으로 동작하며 guard 키워드를 사용합니다. if 구문과 비슷하다고 생각할 수 있는데 차이점이 있다면 if 구문과 다르게 guard 구문은 반드시 else 구문이 뒤에 따라와야 합니다.

그리고 빠른 종료의 else 구문은 블록 내부에 반드시 자신보다 상위 코드 블록을 종료하는 코드를 가지고 있어야 하며 이를 위해 return, break, continue, throw 등의 제어문 전환 명령을 사용하거나 fatalError()와 같은 비반환 함수나 메서드를 호출합니다. 반대로 이러한 제어문 전환 명령을 사용할 수 없는 상황에서는 guard 구문은 사용 불가합니다.

 

형식은 아래와 같습니다.

guard Bool 타입 값 else {
    예외사항 실행문
    제어문 전환 명령어
}
//--------------------------------
for i in 0...3{
    guard i == 2 else { //조건은 쉼표로 추가 나열할 수 있습니다
        continue
    }
    print(i)
}

guard 구문은 다수의 조건을 나열할 경우 쉼표(,) 나열해줄 수 있으며 이처럼 조건을 만족했을 때보다 조건을 만족하지 못한 예외사항 처리에 초점을 맞추고 있음을 알 수 있습니다.

 

해당 글은 야곰님의 스위프트 프로그래밍 3판 을 기반으로 한 정리 글이며 문제가 있을 시 삭제하도록 하겠습니다.

스위프트 프로그래밍 3판 eBook 구매 링크: www.yes24.com/Product/Goods/81530016

 

스위프트 프로그래밍 (3판)

문법을 넘어 프로그래밍 패러다임도 익히는 스위프트 5스위프트 5의 핵심 키워드는 ‘안정화’다. ABI 안정화 덕분에 버전과 환경에 크게 영향받지 않고 더 유연하게 스위프트를 사용할 수 있게

www.yes24.com

야곰 님의 블로그: blog.yagom.net

 

yagom's blog

야곰의 프로그래밍 블로그입니다. iOS, Swift, Objective-C, C에 대해 이야기합니다.

blog.yagom.net