Swift 3.0.1 가이드에 대응하는 정리글을 작성하였습니다!!!
Optional Chaining 정리 최신버전 > http://wlaxhrl.tistory.com/52
Apple 제공 Swift 프로그래밍 가이드(2.2)의 Optional Chaining 부분을 공부하며 정리한 글입니다. 개인적인 생각도 조금 들어가있습니다.
들어가며
옵셔널 체이닝Optional chaining은 nil이 될 수도 있는 옵션을 가진 프로퍼티, 메서드, 서브스크립트에 대고 질의querying하거나 호출하는 프로세스를 가리킨다. 옵셔널이 값을 가지고 있다면 호출은 성공하고, 옵셔널이 nil이라면 호출은 nil을 리턴한다. 여러개의 쿼리가 한 번에 묶일chained 수 있으며 전체 체인 중 하나라도 nil이 되면 전체 체인이 실패한다.
<note> Swift에서의 옵셔널 체이닝은 Objective-C에서 nil에 메세지를 보내는 것과 유사하다. 다른 점은 Swift에서는 모든 타입에 대해 가능하며, 성공과 실패 여부를 확인할 수 있다는 것이다.
강제 언랩핑의 대안이 되는 옵셔널 체이닝
옵셔널 타입의 프로퍼티, 메서드, 스크립트를 non-nil로 간주할 경우, 호출 시에 뒤에 물음표(?)를 붙여서 옵셔널 체인을 명시할 수 있다. 이것은 느낌표(!)를 붙여서 강제 언랩핑 하는 것과 유사하다. 그러나 옵셔널 체이닝의 경우에는 만약 옵셔널 값이 nil이라도 그냥 nil을 리턴하며 실패만 할 뿐, 런타임 에러를 내지 않는다.
옵셔널 체이닝 호출의 결과는 항상 옵셔널 타입의 값이다. 기대되는 리턴 타입과 동일한 타입인데, 단지 옵셔널로 감싸져있을 뿐이다. nil에 대고 호출했을 때 nil이 리턴되는 과정을 생각해보면 옵셔널 타입이 되는 것이 당연하다. (이 옵셔널 값이 nil이라면 옵셔널 체이닝 호출이 실패한 것이고, 값이 들어있다면 성공한 것이다.)
그럼 예제를 통해 살펴보자.
class Person {
var residence: Residence?
}
class Residence {
var numberOfRooms = 1
}
let john = Person() // 이때 john.residence 는 nil
let roomCount = john.residence!.numberOfRooms // 강제언랩핑. runtime error
if let roomCount = john.residence?.numberOfRooms {
// numberOfRooms는 옵셔널 타입이 아니지만
// 옵셔널 체이닝을 사용하고 있으므로
// john.residence?.numberOfRooms 의 리턴값은 옵셔널 Int.
print("john의 주거지에는 \(roomCount)개 방이 있다")
} else {
print("Jone의 주거지의 방 개수를 얻어올 수 없다")
}
let john = Person()
john.residence = Residence()
// 이제 john.residence?.numberOfRooms 의 리턴값은
// 1이라는 값을 가지는 옵셔널 Int이다.
if let roomCount = john.residence?.numberOfRooms {
print("john의 주거지에는 \(roomCount)개 방이 있다")
} else {
print("Jone의 주거지의 방 개수를 얻어올 수 없다")
}
옵셔널 체이닝을 위한 모델 클래스를 정의해보자.
프로퍼티, 메서드, 서브스크립트를 한 단계 더 깊게 호출할 수 있는 옵셔널 체이닝을 사용할 수 있다. 이것을 이용하면 서로 연관되어 있는 복잡한 모델들에서, 각 서브프로퍼티의 프로퍼티, 메서드, 서브스크립트에 접근할 수 있는지 체크해볼 수 있을 것이다.
예제를 통해 알아보자.
Person 클래스 : 위에서 정의한 것과 같다. Residence 인스턴스를 가진다.
Room 클래스 : 방 이름을 가진다.
Address 클래스 : 옵셔널 스트링 타입의 빌딩이름, 빌딩번호, 도로이름을 가지고 있다.
Residence 클래스 : Room 인스턴스들의 배열을 가지고 있다. 인덱스를 통해 해당 Room 인스턴스에 접근할 수 있는 서브크립트를 구현하고 있다.
class Person {
var residence: Residence?
}
class Residence {
var rooms = [Room]()
var address: Address?
var numberOfRooms: Int {
return rooms.count
}
subscript(i: Int) -> Room {
get {
return rooms[i]
}
set {
rooms[i] = newValue
}
}
func printNumberOfRooms() {
print("\(numberOfRooms)개")
}
}
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 buildingName != nil {
return buildingName
} else if buildingNumber != nil && street != nil {
return "\(buildingNumber) \(street)"
} else {
return nil
}
}
}
옵셔널 체이닝을 통해 프로퍼티에 접근해보자.
let john = Person() // 이때 john.residence 는 nil이다
if let roomCount = john.residence?.numberOfRooms {
print("john의 거주지에는 \(roomCount)개의 방이...")
} else {
print("방 개수를 알 수 없음")
}
// 옵셔널 체이닝을 통해 프로퍼티의 값을 set하려고 "시도"할 수도 있다
let someAddress = Address()
someAddress.buildingNumber = "29"
someAddress.street = "Acacia Road"
john.residence?.address = someAddress // residence가 nil이니까 실패한다
func createAddress() -> Address {
print("function was called")
let someAddress = Address()
someAddress.buildingNumber = "29"
someAddress.street = "Acacia Road"
return someAddress
}
john.residence?.address = createAddress()
// 아무것도 print되지 않는다.
// john.residence?.address의 리턴값이 nil이기 때문에
// createAddress() 메서드 자체가 호출될 필요가 없었다.
옵셔널 체이닝을 통해 메서드를 호출해보자.
옵셔널 체이닝을 통해 메서드를 호출하면 성공/실패 여부를 알 수 있다. 해당 메서드가 리턴 타입이 없더라도 말이다. (리턴 타입이 없는 메서드는 사실은 Void 리턴 타입을 가진다. 리턴하는 것은 빈 튜플이다)
예제에서는 Residence 클래스에 다음과 같은 리턴 타입 없는 메서드가 있다.
func printNumberOfRooms() {
print("\(numberOfRooms)개")
}
옵셔널 체이닝을 통해 이 메서드를 호출할 경우 리턴 타입은 Void가 아니라 Void? 가 될 것이다.
if john.residence?.printNumberOfRooms() != nil { // nil이 리턴된다
print("방 개수를 알 수 있음")
} else {
print("방 개수를 알 수 없음")
}
// 옵셔널 체이닝을 통해 프로퍼티를 set하려고 "시도"할 때도
// 성공하든 실패하든 Void? 가 리턴된다.
// 따라서 다음과 같이 프로퍼티 셋팅이 성공했는지를 체크할 수 있다
if (john.residence?.address = someAddress) != nil {
print("프로퍼티 셋팅 성공")
} else {
// john.residence가 nil이라면 실패해서 여기로 들어올 것이다
print("프로퍼티 셋팅 실패")
}
옵셔널 체이닝을 통해 서브스크립트에 접근해보자.
서브스크립트의 경우에도 크게 다르지 않다. 옵셔널체이닝을 통해 get/set을 시도할 수 있고 성공/실패 여부를 체크해볼 수 있다.
// get 시도
// 물음표(?)가 위치할 곳은 항상 옵셔널 표현식의 뒤다.
if let firstRoomName = john.residence?[0].name {
print("첫 번째 방은 \(firstRoomName)")
} else {
print("첫 번째 방 가져올 수 없음")
}
// set 시도
john.residence?[0] = Room(name: "욕실")
// 이 시점에서는 john.residence 가 nil이므로 시도는 둘 다 실패한다
let johnHouse = Residence()
johnHouse.rooms.append(Room(name: "거실"))
john.residence = johnHouse
// get 시도가 이때는 성공한다
if let firstRoomName = john.residence?[0].name {
print("첫 번째 방은 \(firstRoomName)")
} else {
print("첫 번째 방 가져올 수 없음")
}
<옵셔널 타입의 서브스크립트에 접근해보자>
만약 서브스크립트가 옵셔널 타입을 리턴한다면(ex: 딕셔너리의 key를 받아서 value를 돌려주는 서브스크립트), 물음표(?)를 서브스크립트의 괄호 뒤에 위치시켜서 옵셔널 체이닝을 해보자.
var testScores = ["Dave": [86, 82, 84], "Bev": [79, 94, 81]]
testScores["Dave"]?[0] = 91
testScores["Bev"]?[0] += 1
testScores["Brian"]?[0] = 72 // 이 경우는 실패하지만 crash는 나지 않는다. nil만 리턴한다.
체이닝을 여러 레벨로 연결하기Linking Multiple Levels of Chaining
옵셔널 체이닝을 여러 개 연결할 수 있다. 그러나 여러 개 연결한다고 해서 옵셔널 타입이 겹겹이 감싸지는 건 아니다. 옵셔널이 아닌 값은 옵셔널로 반환이 되고, 이미 옵셔널인 값은 그냥 그대로 옵셔널로 반환이 된다. (옵셔널을 한 번 더 옵셔널로 감싸서 반환되는 게 아니라는 말)
if let johnsStreet = john.residence?.address?.street {
// 두 번 옵셔널 체이닝이 걸렸더라도
// john.residence?.address?.street 의 반환값은 옵셔널 String
print("john이 사는 거리이름은 \(johnsStreet)")
} else {
print("거리이름 가져올 수 없음")
}
옵셔널 리턴 값을 이용해서 메서드 체이닝를 해보자Chaining on Methods with Optional Return Values
if let buildingIdentifier = john.residence?.address?.buildingIdentifier() {
// john.residence?.address?.buildingIdentifier() 의 리턴 값은
// 옵셔널 String이다.
// 이 리턴값에 대해 또 한번 옵셔널 체이닝을 걸고 싶다면 아래처럼 하면 된다
print("John의 빌딩 identifier는 \(buildingIdentifier)")
}
if let buildingIdentifier = john.residence?.address?.buildingIdentifier()?.hasPrefix("The") {
print("John의 빌딩 identifier는 \(buildingIdentifier)")
}
// 만약 buildingIdentifier()가 반환하는 값이 nil이 될 경우에
// (빌딩 이름도 번호도 없으면 nil 반환한다)
// 다음과 같이 강제 언랩핑을 하면 런타임 에러가 날 것이기 때문에
// 위와 같이 하는 것임
if let buildingIdentifier = john.residence?.address?.buildingIdentifier()!.hasPrefix("The") {
print("John의 빌딩 identifier는 \(buildingIdentifier)")
}
'Swift 공식 가이드 > Swift 2' 카테고리의 다른 글
Type Casting (0) | 2016.06.07 |
---|---|
Error Handling (0) | 2016.06.07 |
Automatic Reference Counting (ARC) (0) | 2016.05.23 |
Deinitialization (0) | 2016.05.08 |
Initialization (3/3) (0) | 2016.05.07 |