Swift

Swift | Optional Chaining

iOSDEv 2023. 12. 13. 20:41
Optional Chaining
현재 nil일 수 있는 옵셔널을 호출하기 위한 과정

 

이부분을 남기는 이유는 함수 자체가 존재하지 않거나

클래스가 존재하지 않을 수 있다라는 것을 배웠기 때문에

이러한 내용을 남기고자 한다

 

옵셔널 체이닝을 배우기 전까지 값의 유무에 대한 옵셔널만 존재할거라고 생각했지만

함수자체나 클래스 딕셔너리 등등이 존재하지 않을 수도 있다를 배울 수 있었다

 

그리고 언래핑은 앞에서 배운 3가지 방법과 동일하게 강제 언래핑, if let, 닐 코어레싱 연산자를 통해서

벗겨낼 수 있다는건 동일하였다

 

옵셔널 체이닝 문법
//옵셔널타입에 대해 접근연산자를 사용하는 방법
class Food {
    var name: String?
    var price : Int
    
    init(name: String? = nil, price: Int) {
        self.name = name
        self.price = price
    }
    
    func eat() {
        print("\(self.name)을 먹자!")
    }
    
    func menu() {
        print("메뉴판을 살펴보세요")
    }
}


//Food를 옵셔널로 선언함
//초기화 하지 않아서 nil로 초기화 된다
class Company {
    var food: Food?
}

//옵셔널타입을 "접근 연산자"를 사용할 떄, ?(물음표)를 붙여서 앞의 타입의 값이 nil을 가질 수도 있다를 알려줌

//생성하고 선언한 후 피자로 저장하기
var pizza = Food(name: "pizza", price: 20000)
pizza.name
print(pizza.name)               //Optional("pizza")
pizza.menu()                    //메뉴판을 살펴보세요

var chicken: Food? = Food(name: "chicken", price: 18000)

chicken?.name = "bbq 황금 올리브"
chicken?.menu()                 //메뉴판을 살펴보세요


//이미 company에 선언된 food변수의 type이 옵셔널 타입이기 때문에 물음표를 반드시 붙여줘야한다
var company = Company()

company.food = pizza

//마지막 부분은 물음표 붙이지 않아도 된다
//name도 optional이지만 ?붙이지 않아도됨
//만약 맨 마지막에 ?를 붙인다면 오류가 나며 호출과 멤버 접근이나 서브스크립트가 붙어야한다라는 오류가 난다
company.food?.name
print(company.food?.name)   //Optional("pizza")

//옵셔널로 company2를 선언했기 때문에 이름까지 접근하려면 옵셔널을 두 번 사용해야함
var company2: Company? = Company()
company2?.food = pizza
company2?.food?.name
print(company2?.food?.name) //Optional("pizza")

/**============================================================================
- 옵셔널 체이닝 (옵셔널 타입에 대해, 접근연산자 호출하는 방법)
- 1) 옵셔널체이닝의 결과는 항상 옵셔널이다.
- 2) 옵셔널체이닝에 값 중에서 하나라도 nil을 리턴한다면, 이어지는 표현식을 평가하지 않고 nil을 리턴
===============================================================================**/

 

apple 공식 문서

 

여기에서 중요한건 옵셔널로 정의된 변수를 가지고 있는 클래스를 호출하면 클래스가 초기화됨에 따라 옵셔널 규칙에 따라 nil로 초기화됨

class Residence {
    var numberOfRooms = 1
}

class Person {
    var residence: Residence?
}

//초기화됨에따라 옵셔널 규칙에 따라 nil로 초기화됨
let john = Person()

//강제 언래핑하게 되면 에러남
//이유 : residence에 값이 없기 때문이다
//let roomCount = john.residence?.numberOfRoom

//if let으로 이용해서 error 방지
if let roomCount = john.residence?.numberOfRooms {
    print("John's residence has \(roomCount) room(s).")
} else {
    print("Unable to retrieve the number of rooms.")
}
//Unable to retrieve the number of rooms.


//nil을 갖지 않게 하기 위해서는?
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.")
}
//John's residence has 1 room(s).

 

옵셔널 체이닝 언래핑 방법
3가지가 있다
1. ! 붙여서 강제 언래핑
2.if let
3.닐 코어레싱을 사용 ??

 

//1. 앞에 옵셔널타입에 값이 있다는 것이 확실한 경우 (nil이 아닌경우)
//   => !(느낌표)로 강제 언래핑한다

//이경우 name변수 자체가 optional 타입이기 때문에 옵셔널 타입으로 나온다
print(company2!.food!.name)     //Optional("pizza")
print(company2!.food!.name!)    //pizza

//가격 자체는 옵셔널 타입이 아니기 때문에 20000이 출력된다
print(company2!.food!.price)    //20000

//2. if let 바인딩
if let name = company2?.food?.name {
    print(name)                 //pizza
}


//3. Nil-Coalescing 연산자

var originalName = company2?.food?.name ?? "먹을거 없음"
print(originalName)

 

헷갈리는 부분
?()와 ?()?. 는 뭘까?
//헷갈리는 부분 정리
class CheeseBall {
    var name: String?   //문자열 옵셔널
    
    //void 형으로 반환값은 옵셔널bbq
    //mymaster 함수 자체가 옵셔널 클로저
    var myMaster: (()->BBQ?)?  //함수 옵셔널 클로저
    
    init(myMaster: @escaping () -> BBQ?) {
        self.myMaster = myMaster
    }
}

class BBQ {
    var name: String?
}

func chocoCheeseball() -> BBQ? {
    let chocoball = BBQ()
    chocoball.name = "bbq 신상 초코 치즈볼"
    return chocoball
}

var cheesball: CheeseBall? = CheeseBall(myMaster: chocoCheeseball) //정의한 함수를 할당

var chocoball: CheeseBall? = CheeseBall(myMaster: chocoCheeseball)

//mymaster 소괄호 앞뒤로 붙어있는데 의미하는 바가 다름
//mymaster 앞에 있는 ? : 함수가 없을 수도 있다라는 뜻임
//함수가 옵셔널로 선언되어있기에 mymaster함수가 없을 수도 있는 Nil을 뜻함
//뒤에 있는 물음표는 함수의 결과값이 없을 수도 있다라는 말임
let choconame = cheesball?.myMaster?()?.name


//?()는 없을 수 없다라는 뜻
var name = cheesball?.myMaster?()?.name
print(name)         //Optional("bbq 신상 초코 치즈볼")

if var n = cheesball?.myMaster?()?.name {
    print(n)        //bbq 신상 초코 치즈볼
}

 

옵셔널 체이닝 딕셔너리 관련 표기법
//optional dictionary
class Library1 {
    var books: [String: BBQ]?
}

var bbq1 = BBQ()
bbq1.name = "bbq 제주에일 맥주"
print(bbq1.name)        //Optional("bbq 제주에일 맥주")

var bbq2 = BBQ()
bbq2.name = "bbq 서울한강 맥주"
print(bbq2.name)        //Optional("bbq 서울한강 맥주")


var library = Library1()
library.books = ["제주": bbq1, "서울": bbq2]

//books?  ====> 딕셔너리 자체가 없을 수 있음을 의미
//books?["제주"]?  ====> 딕셔너리의 결과값이 없을 수 있음을 의미
library.books?["제주"]?.name


//사용하려면
if let jeju = library.books?["제주"]?.name {
    print("이름 \(jeju)")     //이름 bbq 제주에일 맥주
}


//optional에서 함수 실행은?
var pasta: Food? = Food(name: "pasta", price: 10000)

pasta?.menu()       //메뉴판을 살펴보세요
pasta?.eat()        //Optional("pasta")을 먹자!
//확신이 들면 !를 쓰면됨
//이렇게 사용할 필요가 없음
//파스타의 값이 있으면 그대로 사용함
//그렇기 때문에 메서드를 사용하는 것들은 위 처럼 상관없이 써도 됨
if let p = pasta {
    p.menu()
    p.eat()
}

pasta = nil
pasta?.eat()        // 타입의 값이 nil이면, 함수가 실행이 되지 않고 nil을 반환

print(pasta?.eat())
// 사실 결론은 간단. 옵셔널 체이닝에서 함수(메서드 실행)의 경우 크게 신경을 쓰지 않고 호출하면 됨
// (옵셔널이라는 것은 변수 안의 값에 대한 문제일 뿐)

 

 

애플 참조 문서

 

 

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 Person {
    var residence: Residence?
}

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, let street {
                return "\(buildingNumber) \(street)"
            } else if buildingName != nil {
                return buildingName
            } else {
                return nil
            }
        }
}


/*===============================================
 옵셔널 체이닝을 통해 프로퍼티 접근 
 (Accessing Properties Through Optional Chaining)
 ===============================================*/

let john = Person()
//초기화 되지 않았기에 nil을 반환하여 if let 바인딩을 해도 else로 빠진다
print("\(john.residence?.numberOfRooms) 방의 갯수 입니다")
if let roomCount = john.residence?.numberOfRooms {
    print("John's residence has \(roomCount) room(s).")
} else {
    print("Unable to retrieve the number of rooms.")
}
//"Unable to retrieve the number of rooms."


//옵셔널 체이닝을 통해 프로퍼티의 값을 설정할 수 있다
//하지만 실패할거임
//상수에 접근하는 것은 어떠한 영향도 없을 거기 때문임
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()

/*===============================================
 옵셔널 체이닝을 통한 함수 호출
 (Calling Methods Through Optional Chaining)
 ===============================================*/

//john.residence -> residence == nil이기 때문에 둘다 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.")
}
// "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.")
}
// "It was not possible to set the address."

/*===============================================
 옵셔널 체이닝을 통한 서브 스크립트 접근
 (Accessing Subscripts Through Optional Chaining)
 ===============================================*/
 
//john.residence -> residence == nil이기 때문에 둘다 nil로 반환될것이다

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

//서브스크립트를 통해서 새로운 값을 설정할 수 있지만
//residence가 nil이므로 서브 스크립트 설정을 실패할 것이다
john.residence?[0] = Room(name: "Bathroom")

//john.residence에 실제 residence인스턴스를 생성하고 할당하게되면 옵셔널 체이닝으로
//residence 서브스크립트를 사용하여 rooms 배열의 항목에 접근이 가능
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.")
}
// "The first room name is Living Room."

/*===============================================
 옵셔널 타입에 서브 스크립트 접근
 (Accessing Subscripts of Optional Type)
 ===============================================*/
 
let johnsAddress = Address()
johnsAddress.buildingName = "The Larches"
johnsAddress.street = "Laurel Street"
john.residence?.address = johnsAddress

//john.residence.address 에 대한 값으로 Address 인스턴스를 설정하고 주소의 
//street 프로퍼티에 대해 값을 설정하면 여러 수준의 옵셔널 체이닝을 통해 street 프로퍼티의 값에 접근
if let johnsStreet = john.residence?.address?.street {
    print("John's street name is \(johnsStreet).")
} else {
    print("Unable to retrieve the address.")
}
//"John's street name is Laurel Street."


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


/*===============================================
 옵셔널 타입에 서브 스크립트 접근
 (Accessing Subscripts of Optional Type)
 ===============================================*/
 
 //앞의 엘런 강의에서 보다시피 저 물음표의 의미는 딕셔너리가 없을 수 있다라는 말임
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]

 

엘런 강의 참조

애플문서 참조