옵셔널 체이닝(Optional Chaining)
옵셔널 체이닝은 옵셔널 요소 내부의 프로퍼티로 또 다시 옵셔널이 연속적으로 연결되는 경우에 유용하게 사용할 수 있다.
사용
class Person {
var name: String
var job: String?
var home: Apartment?
init(name: String) {
self.name = name
}
}
class Apartment {
var buildingNumber: String
var roomNumber: String
var `guard`: Person?
var owner: Person?
init(dong: String, ho: String) {
buildingNumber = dong
roomNumber = ho
}
}
let yagom: Person? = Person(name: "yagom")
let apart: Apartment? = Apartment(dong: "101", ho: "202")
let superman: Person? = Person(name: "superman")
// 옵셔널 체이닝이 실행 후 결과값이 nil일 수 있으므로
// 결과 타입도 옵셔널입니다
// 만약 우리집의 경비원의 직업이 궁금하다면..?
// 옵셔널 체이닝을 사용하지 않는다면...
func guardJob(owner: Person?) {
if let owner = owner {
if let home = owner.home {
if let `guard` = home.guard {
if let guardJob = `guard`.job {
print("우리집 경비원의 직업은 \(guardJob)입니다")
} else {
print("우리집 경비원은 직업이 없어요")
}
}
}
}
}
guardJob(owner: yagom)
// 옵셔널 체이닝을 사용한다면
func guardJobWithOptionalChaining(owner: Person?) {
if let guardJob = owner?.home?.guard?.job {
print("우리집 경비원의 직업은 \(guardJob)입니다")
} else {
print("우리집 경비원은 직업이 없어요")
}
}
guardJobWithOptionalChaining(owner: yagom)
// 우리집 경비원은 직업이 없어요
yagom?.home?.guard?.job // nil
yagom?.home = apart
yagom?.home // Optional(Apartment)
yagom?.home?.guard // nil
yagom?.home?.guard = superman
yagom?.home?.guard // Optional(Person)
yagom?.home?.guard?.name // superman
yagom?.home?.guard?.job // nil
yagom?.home?.guard?.job = "경비원"
nil 병합 연산자(nil-coalescing operator)
옵셔널의 값이 nil일 경우 ?? 다음의 값을 반환한다. 띄어쓰기에 주의해야 한다.
var guardJob: String
guardJob = yagom?.home?.guard?.job ?? "슈퍼맨"
print(guardJob) // 경비원
yagom?.home?.guard?.job = nil
guardJob = yagom?.home?.guard?.job ?? "슈퍼맨"
print(guardJob) // 슈퍼맨
타입 캐스팅
인스턴스의 타입을 확인하는 용도나 클래스의 인스턴스를 부모나 자식 클래스의 타입으로 사용할 수 있는지 확인하는 용도로 사용한다.
is , as를 사용한다.
타입 확인
is를 사용하여 타입 확인
//클래스 정의
class Person {
var name: String = ""
func breath() {
print("숨을 쉽니다")
}
}
class Student: Person {
var school: String = ""
func goToSchool() {
print("등교를 합니다")
}
}
class UniversityStudent: Student {
var major: String = ""
func goToMT() {
print("멤버쉽 트레이닝을 갑니다 신남!")
}
}
// 인스턴스 생성
var yagom: Person = Person()
var hana: Student = Student()
var jason: UniversityStudent = UniversityStudent()
//is를 사용하여 확인
var result: Bool
result = yagom is Person // true
result = yagom is Student // false
result = yagom is UniversityStudent // false
result = hana is Person // true
result = hana is Student // true
result = hana is UniversityStudent // false
result = jason is Person // true
result = jason is Student // true
result = jason is UniversityStudent // true
if yagom is UniversityStudent {
print("yagom은 대학생입니다")
} else if yagom is Student {
print("yagom은 학생입니다")
} else if yagom is Person {
print("yagom은 사람입니다")
} // yagom은 사람입니다
switch jason {
case is Person:
print("jason은 사람입니다")
case is Student:
print("jason은 학생입니다")
case is UniversityStudent:
print("jason은 대학생입니다")
default:
print("jason은 사람도, 학생도, 대학생도 아닙니다")
} // jason은 사람입니다
switch jason {
case is UniversityStudent:
print("jason은 대학생입니다")
case is Student:
print("jason은 학생입니다")
case is Person:
print("jason은 사람입니다")
default:
print("jason은 사람도, 학생도, 대학생도 아닙니다")
} // jason은 대학생입니다
업 캐스팅
- as를 사용하여 부모 클래스의 인스턴스로 사용할 수 있도록 컴파일러에게 타입 정보를 전환해준다.
- Any, AnyObject로도 타입 정보를 변활할 수 있다.
- 암시적으로 처리되기 때문에 꼭 필요한 경우가 아니라면 생략 가능하다.
// UniversityStudent 인스턴스를 생성하여 Person 행세를 할 수 있도록 업 캐스팅
var mike: Person = UniversityStudent() as Person
var jenny: Student = Student()
//var jina: UniversityStudent = Person() as UniversityStudent // 컴파일 오류
// UniversityStudent 인스턴스를 생성하여 Any 행세를 할 수 있도록 업 캐스팅
var jina: Any = Person() // as Any 생략가능
다운 캐스팅
as! 혹은 as? 를 사용하여 자식 클래스의 인스턴스로 사용할 수 있도록 컴파일러에게 인스턴스의 타입 정보를 전환해준다.
조건부 다운 캐스팅
as? 를 사용한다. 캐스팅에 실패하면 nil을 반환하기 때문에 결과값 타입은 옵셔널 타입이다.
var optionalCasted: Student?
optionalCasted = mike as? UniversityStudent
optionalCasted = jenny as? UniversityStudent // nil
optionalCasted = jina as? UniversityStudent // nil
optionalCasted = jina as? Student // nil
강제 다운 캐스팅
as! 를 사용한다. 캐스팅에 실패하면 런타임 오류가 발생하고, 성공하면 일반 타입을 반환한다.
var forcedCasted: Student
forcedCasted = mike as! UniversityStudent
//forcedCasted = jenny as! UniversityStudent // 런타임 오류
//forcedCasted = jina as! UniversityStudent // 런타임 오류
//forcedCasted = jina as! Student // 런타임 오류
활용
func doSomethingWithSwitch(someone: Person) {
switch someone {
case is UniversityStudent:
(someone as! UniversityStudent).goToMT()
case is Student:
(someone as! Student).goToSchool()
case is Person:
(someone as! Person).breath()
}
}
doSomethingWithSwitch(someone: mike as Person) // 멤버쉽 트레이닝을 갑니다 신남!
doSomethingWithSwitch(someone: mike) // 멤버쉽 트레이닝을 갑니다 신남!
doSomethingWithSwitch(someone: jenny) // 등교를 합니다
doSomethingWithSwitch(someone: yagom) // 숨을 쉽니다
func doSomething(someone: Person) {
if let universityStudent = someone as? UniversityStudent {
universityStudent.goToMT()
} else if let student = someone as? Student {
student.goToSchool()
} else if let person = someone as? Person {
person.breath()
}
}
doSomething(someone: mike as Person) // 멤버쉽 트레이닝을 갑니다 신남!
doSomething(someone: mike) // 멤버쉽 트레이닝을 갑니다 신남!
doSomething(someone: jenny) // 등교를 합니다
doSomething(someone: yagom) // 숨을 쉽니다
assert와 guard
애플리케이션이 동작 도중에 생성하는 다양한 연산 결과값을 동적으로 확인하고 안전하게 처리할 수 있도록 확인하고 빠르게 처리할 수 있다.
assertion
- assert(::file:line::) 함수를 사용한다. assert 함수는 디버깅 모드에서만 동작한다.
- 배포하는 어플리케이션에서는 제외된다. 주로 디버깅 중에 조건의 검증을 위하여 사용한다.
var someInt: Int = 0
// 검증 조건에 부합하므로 지나갑니다
assert(someInt == 0, "someInt != 0")
someInt = 1
//assert(someInt == 0) // 동작 중지, 검증 실패
//assert(someInt == 0, "someInt != 0") // 동작 중지, 검증 실패
// assertion failed: someInt != 0: file guard_assert.swift, line 26
func functionWithAssert(age: Int?) {
assert(age != nil, "age == nil")
assert((age! >= 0) && (age! <= 130), "나이값 입력이 잘못되었습니다")
print("당신의 나이는 \(age!)세입니다")
}
functionWithAssert(age: 50)
//functionWithAssert(age: -1) // 동작 중지, 검증 실패
//functionWithAssert(age: nil) // 동작 중지, 검증 실패
빠른 종료(Early Exit)
- guard를 사용하여 잘못된 값의 전달 시 특정 실행구문을 빠르게 종료한다. 디버깅 모드뿐만 아니라 어떤 조건에서도 동작한다.
- guard의 else 블록 내부에는 특정 코드블록을 종료하는 지시어(return, break 등)가 꼭 있어야 한다.
- 타입 캐스팅, 옵셔널과도 자주 사용되며 그 외에도 단순 조건 판단 후 빠르게 종료할 때도 용이하다.
func functionWithGuard(age: Int?) {
guard let unwrappedAge = age,
unwrappedAge < 130,
unwrappedAge >= 0 else {
print("나이값 입력이 잘못되었습니다")
return
}
print("당신의 나이는 \(unwrappedAge)세입니다")
}
var count = 1
while true {
guard count < 3 else {
break
}
print(count)
count += 1
}
// 1
// 2
func someFunction(info: [String: Any]) {
guard let name = info["name"] as? String else {
return
}
guard let age = info["age"] as? Int, age >= 0 else {
return
}
print("\(name): \(age)")
}
someFunction(info: ["name": "jenny", "age": "10"])
someFunction(info: ["name": "mike"])
someFunction(info: ["name": "yagom", "age": 10]) // yagom: 10
'iOS-Study' 카테고리의 다른 글
iOS Study : 6주차 - 타입 확장(프로토콜, 익스텐션) (0) | 2022.12.10 |
---|---|
iOS Study : 5주차 - SwiftUI (0) | 2022.12.03 |
iOS Study : 5주차 - 타입 심화(프로퍼티, 상속) (0) | 2022.12.03 |
iOS Study : 4주차 - SwiftUI (0) | 2022.11.27 |
iOS Study : 4주차 - 사용자 정의 타입, 클로저 (0) | 2022.11.27 |