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]
엘런 강의 참조
애플문서 참조