Day 12 : 옵셔널

@ Day 12

# 옵셔널(Optionals)

  • 값이 있을 수도 있고, 없을 수도 있는(nil) 타입을 나타내기 위해 사용
  • 타입 뒤에 물음표를 붙여 type? 과 같이 선언
var age: Int? = nil  // 옵셔널 Int
var height: Int? = 200  // 옵셔널 Int

# 옵셔널 추출(Unwrapping optionals)

옵셔널은 값이 있을수도, 없을수도 있으므로 추출을 통해 값이 실제로 있는지 확인 필요

if-let 구문

  • 값이 있으면 변수에 저장, 없으면 다른 코드 실행
if let unwrapped = name {  // 값이 있다면 unwrapped에 값이 저장됨
    print("\(unwrapped.count) letters")
} else {  // 값이 없다면
    print("Missing name.")
}

guard-let 구문

  • 옵셔널에 값이 없으면 추출을 시도한 함수 / 반복문 / 조건문을 바로 탈출 하게 함
  • if-let 구문 과 달리 guard-let 구문은 구문 이후에도 추출된 옵셔널 사용 가능
func greet(_ name: String?) {
    guard let unwrapped = name else {
        print("You didn't provide a name!")
        return
    }
    
    print("Hello, \(unwrapped)!")  // 추출된 옵셔널 사용 가능
}

greet(nil)  // You didn't provide a name
greet("sun")  // Hello, sun!

# 강제 추출(Forced Unwrapping)

  • 강제추출 시 옵셔널 타입을 비-옵셔널 타입으로 변환할 수 있음
  • 타입 뒤에 ! 를 붙여서 사용
  • 그러나 옵셔널에 값이 없는 경우 런타임 에러가 발생하므로 주의
let str = "5"

let num = Int(str)
print(num) // Optional(5)

let num2 = Int(str)!  // 강제 추출
print(num2)  // 5

# Implicitly Unwrapped Optionals

  • 일반 옵셔널과 달리 암시적으로 언래핑된 옵셔널은 추출 과정이 불필요
  • 대신 옵셔널에 값이 없는 경우 런타임 에러 발생
  • 타입 뒤에 ! 를 붙여서 선언
let str1 = "5"
let str2 = nil

let age = Int(str1)! // 5
let age2 = Int(str2)!  // KO : Error

# 닐 병합 연산(Nil Coalescing)

  • 닐 병합 연산자 ?? 은 옵셔널을 추출해서 값이 있으면 해당 값을 반환하고 값이 없으면 디폴트 값을 반환
func username(for id: Int) -> String? {
    if id == 1 {
        return "Taylor Swift"
    } else {
        return nil
    }
}

let user = username(for: 15) ?? "Anonymous"  // Anonymous
  • 딕셔너리 키를 읽는 경우 항상 옵셔널을 반환하므로 닐 병합 연산 활용 가능
let scores = ["Sun": 100, "Moon": 80, "Star": 120]

let cookieRunScore = scores["Planet"] ?? 0  // nil coalescing
let cookieKingdomScore = scores["Planet", default: 0]   

# 옵셔널 체이닝

  • 코드에 옵셔널이 중첩되어 존재할 때, 각각을 확인하여 하나라도 nil 인 순간 그 이후는 무시되고, 전체가 nil 이 됨
let names = ["Vincent": "van Gogh", "Pablo": "Picasso", "Claude": "Monet"]
let surnameLetter = names["Vincent"]?.first?.uppercased()

# 옵셔널 트라이 2가지

try?

  • throwing function옵셔널을 반환 하는 함수로 변환
  • 함수가 에러를 던지면 nil 이 반환되고, 정상 작동하면 옵셔널 반환
enum PasswordError: Error {
    case obvious
}

func checkPassword(_ password: String) throws -> Bool {
    if password == "password" {
        throw PasswordError.obvious
    }
    
    return true
}

if let result = try? checkPassword("password") {  // 정상 작동
    print("Result was \(result)")
} else {  // 에러 발생
    print("Too EZ")
}

try!

  • 옵셔널 추출 과정을 생략할 수 있으나, 함수가 에러를 던지는 경우 런타임 에러 발생
enum PasswordError: Error {
    case obvious
}

func checkPassword(_ password: String) throws -> Bool {
    if password == "password" {
        throw PasswordError.obvious
    }
    
    return true
}

try! checkPassword("sekrit")
print("OK")

# Failable Initializers

  • 성공할 수도, 실패할 수도 있는 이니셜라이저를 failable initializer 라고 함
  • init?() 을 통해 선언
  • 초기화를 실패하는 경우 반드시 nil 을 리턴하도록 해야 함
struct Person {
    var id: String
    
    init?(id: String) {
        if id.count == 9 {
            self.id = id
        } else {  // 초기화 실패 시
            return nil
        }
    }
}

var man1 = Person(id: "123456789")
man1?.id  // 123456789

var man2 = Person(id: "")  // nil
man2?.id  // nil

# 타입캐스팅(Typecasting)

  • as? 키워드를 통해 클래스의 인스턴스를 부모 혹은 자식 클래스의 타입으로 사용할 수 있는 지 확인 가능
  • 사용할 수 있다면 해당 타입 (으로 변환 해) 리턴하고 없다면 nil 리턴
class Animal {}
class Fish: Animal {}

class Dog: Animal {
    func makeNoise() {
        print("Woof!)
    }
}

let pets = [Fish(), Dog(), Fish(), Dog()]

// Fish 클래스와 Dog 클래스 모두 Animal 클래스를 상속하므로 Swift는 pets가 Animal 배열 타입이라고 추론
// 따라서 Animal 클래스의 프로퍼티/메서드에만 접근 가능하므로 Dog 클래스의 makeNoise()를 호출하려면 타입캐스팅 필요

for pet in pets {
    if let dog = pet as? Dog {  // 타입 캐스팅
        dog.makeNoise()
    }
}

☀️ 느낀점

  • 스위프트는 nil 을 처리하기 위한 방법이 매우 다양하다는 생각을 했다...어렵다...ㅋㅎㅋㅎㅋㅎ
  • 오늘부로 문법파트가 끝났는데 사실 벌써 가물가물한 부분도 많고 아직 직접 프로그램을 짜본 게 아니라 개념이 알쏭달송하게 느껴진다...내일 복습하면서 강의를 병행할 지 고민해봐야겠다!

좋은 웹페이지 즐겨찾기