[Swift 문법] - Optional

sun02·2021년 7월 19일
0

100 days of Swift 

목록 보기
11/40

1. Handling missing data

우리는 5와 같은 값을 저장하기 위해서 Int 같은 타입을 사용해왔다.
하지만 만약 사용자의 age 프로퍼티를 저장하고 싶지만
나이를 모른다면 어떻게 할 것인가?

이에 대한 Swift의 해결책은 optional이다.
우리는 타입에 관계없이 옵셔널을 만들 수 있다.
옵셔널 정수는 0이나 40을 가질 수도 있고 값이 아예 없을 수도 있다

타입을 옵셔널로 만드려면 뒤에 물음표를 추가하면 된다.

var age: Int? = nil
  • 예를 들어, 옵셔널 정수를 다음과 같이 만들 수 있다.
  • 이것은 어느 숫자도 저장하고 있지 않다.
age = 38
  • 하지만 나중에 우리가 값을 알게 되었을 때 다음과 같이 사용할 수 있다.

2. Unwrapping optionals

옵셔널 문자열은 “Hello”와 같은 문자열을 포함할 수도, nil – 아무것도 없는 - 일 수도 있다.

이 옵셔널 문자열을 보자

var name: String? = nil

여기서 name.count를 사용하면 어떻게 될까?
진짜 문자열은 갖고있는 문자의 갯수를 저장하는 count 프로퍼티를 가지지만 이것은 nil이다 – 문자열이 아니라 빈 메모리이기 때문에 count 를 갖지 않는다.

이 때문에, name.count를 읽는 것은 안전하지 않고
Swift는 이것을 허용하지 않는다.
대신, 우리는 옵셔널 안에 뭐가 있는지 봐야한다 – unwrapping이다.

옵셔널을 언래핑하는 일반적인 방법은
조건에 따라 언래핑하는 if let 구문을 사용하는 것이다.
옵셔널 안에 값이 있다면 그 값을 사용할 수 있고 없다면 조건은 거짓이 된다.

if let unwrapped = name {
    print(“\(unwrapped.count) letters”)
} else {
    print(“Missing name.”)
}
  • 만약 name이 문자열을 갖고 있다면 그것은 일반 문자열처럼 unwrapped안에 들어가고
    조건 내에서 count 프로퍼티를 사용할 수 있다.
    그러나 만약 name이 비어있다면 else 코드가 실행된다.

3. Unwrapping with guard

if let에 대한 대안은 guard let 이다.
이것도 옵셔널을 언래핑한다.
guard let 은 옵셔널을 언래핑 해주지만
만약 옵셔널 안에서 nil을 발견한다면 사용했던 함수나 루프, 조건을 종료시킨다.

if let 과 guard let 의 중요한 차이점은
guard let 코드 이후에 언래핑된 옵셔널 값을 사용할 수 있다는 점이다.

greet() 함수와 함께 시도해보자.

func gree(_ name: String?) {
    guard let unwrapped = name else {
        print(“You didn’t provide a name!”)
        return
    }
    
    print(“Hello, \(unwrapped)!”)
}
  • 매개변수로 받은 옵셔널 문자열을 언래핑하려할 때 만약 값이 없다면 메세지를 출력하고 종료한다.
    guard let 으로 언래핑된 옵셔널 값은 guard 문이 끝난 이후에도 남아있기 때문에 이 함수의 끝에서 언래핑된 문자열을 출력할 수 있다.

guard let 을 사용하면 함수 시작점에서 문제를 다루고 빠르게 종료할 수 있다.

4. Force unwrapping

옵셔널은 있을 수도 없을 수도 있는 값을 나타내지만 그 값이 nil이 아니라는 것을 확신할 수 있는 때도 있다.
이러한 경우, Swift에서는 옵셔널을 강제로 언래핑 할 수 있다

let str = “5”
let num = Int(str)
  • 다음과 같이 숫자를 포함하는 문자열을 Int로 바꿀 수 있다.
  • 이것은 num을 옵셔널 Int로 바꾼다. 왜냐하면 "5”가 아니라 “Fish”와 같은 문자열을 바꾸려고 했을 수도 있기 때문이다.
iet num = Int(str)!
  • 스위프트는 이 변환을 확신할 순 없지만 너는 이 코드가 안전하다는 것을 알 수 있으므로 Int(str)뒤에 ! 를 적어 결과값을 강제로 언래핑 할 수 있다.

스위프트는 즉시 옵셔널을 언래핑하여 num을 Int?가 아니라 Int로 만든다. 하지만 만약 틀렸다면 – 만약 str이 정수로 바뀔 수 없는 값이였다면 – 코드는 크래시가 날 것이다.

따라서, 안전하다고 확신할 수 있을 때만 강제 언래핑을 해야한다.

5. Implicitly unwrapped optionals

일반 옵셔널처럼 암시적으로 언래핑된 옵셔널은 값을 포함할 수도 있고 nil일 수도 있다.
그러나, 일반 옵셔널과 다르게 사용하기 위해 언래핑할 필요는 없다. : 옵셔널이 아닌것처럼 사용할 수 있다.

let age: Int! = nil
  • 암시적으로 언래핑된 옵셔널은 타입 이름 뒤에 다음과 같이 느낌표를 추가하여 생성된다.

이미 언래핑된 것처럼 작동하기 때문에
암시적으로 언래핑된 옵셔널을 사용하기 위해서 if let이나 guard let 을 사용할 필요는 없다.
그러나, 만약 저들을 사용하려할 때 값이 없다면 – 만약 nil 이라면 – 코드는 충돌할 것이다.

암시적으로 언래핑된 옵셔널은 변수가 nil로 시작할 때도 있기 때문에 존재한다.
그러나 사용하려하기 전에 항상 값을 가진다.
이것은 if let을 항상 작성하지 않아도 되기 때문에 유용하다.

즉, 일반 옵셔널을 대신 사용할 수 잇다면 일반적으로 좋은 생각이다.

6. Nil coalescing

Nil 병합 연산자(coalescing operator)는 옵셔널을 언래핑하고 그 안의 값을 반환한다.
만약 값이 없다면 – 옵셔널이 nil이라면 – 디폴트 값이 대신 사용된다.
어느 쪽이든, 결과는 옵셔널이 아니다 : 옵셔널 안의 값이나 백업으로 사용되는 디폴트 값에 의한 값이다.

정수를 매개변수로 받고 옵셔널 문자열을 반환하는 함수이다.

func username(for id: Int) -> String? {
    if id == 1{
        return “Taylor Swift”
    } else { 
        return nil
    }
}

우리가 id 15로 함수를 호출한다면 사용자가 인식되지 않기 때문에 nil을 받을 것이다. 하지만 nil 병합으로 우리는 다음과 같이 “Anonymous” 디폴트 값을 제공할 수 있다.

let user = username(for: 15) ?? “Anonymous”

이것은 username() 함수로부터 받는 결과를 확인한다 : 만약 문자열이 존재한다면 언래핑되어 user에 들어가고 nil이라면 “Anonymous”가 대신 사용된다.

7. Optional chaining

스위프트에서 옵셔널을 사용할 때 단축키를 사용할 수 있다 : 만약 b가 옵셔널이며 a.b.c와 같은 것에 접근하고 싶을 때, 옵셔널 체이닝을 가능하게 하기 위해서 뒤에 물음표를 쓸 수 있다 : a.b?.c

코드가 실행될 때 스위프트는 b가 값이 있는지 확인할 것이고 만약 nil이라면 나머지는 무시될 것이다 – 스위프트는 즉시 nil을 반환할 것이다. 하지만 만약 값을 가진다면, b는 언래핑되고 실행은 계속될 것이다.

이것을 시도하기 위해, 여기 이름 배열이 있다.

let names = [“John”, “Paul”, “George”, “Ringo”]

우리는 이 배열의 첫번째 프로퍼티를 사용할 것이다. 이는 배열이 있다면 첫번째 이름을 반환하고 배열이 비어있다면 nil을 반환할 것이다. 우리는 uppercase()를 호출하여 그 결과가 대문자로 나오도록 할 수 있다.

let beatle = name.first?.uppercased()

이 물음표는 옵셔널 체이닝이다 – 만약 first가 nil을 반환하면 스위프트는 대문자로 바꾸려하지 않고 beatles를 바로 nil로 만든다.

8. Optional try

이전에 함수 throwing 에 대해 얘기할 때 우리는 다음 코드를 봤었다.

enum PasswordError: Error {
    case obvious
}

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

    return true
}

do {
    try checkPassword(“password”)
    print(“That password is good!”)
} catch {
    print(“You can’t use that password.”)
}

이것은 에러를 부드럽게 다루기 위해 do, try, catch 를 사용하여 함수를 throw 하였다.

try의 대안에는 두 가지가 있다, 네가 이제 옵셔널과 강제 언래핑을 이해하기 때문에 둘 다 더 잘 이해가 될 것이다.

첫번째는 try?이고 throwing function을 함수로 바꾸고 옵셔널을 반환한다. 만약 그 함수가 에러를 throw한다면 nil을 결과로 받을 것이고 그렇지 않으면 옵셔널로 감싸진 값을 받을 것이다.

try? 를 사용해서 checkPassword()를 실행해보자

If let result = try? checkPassword(“password) {
    print(“Result was \(result)”)
} else {
    print(“D’oh.”)
}

다른 대안은 try! 이다. 함수가 fail하지 않을 것임을 확신할 수 있을 때 사용한다. 만약 이 함수가 에러를 throw 한다면 코드는 충돌할 것이다.

try!를 사용해서 코드를 다음과 같이 다시 작성할 수 있다

try! checkPassword(“sekrit”)
print(“OK!”)

9. Failable initializers

강제 언래핑에 대해 얘기할 때, 다음의 코드를 사용했었다.

let str = “5”
let number = Int(str)

이것은 문자열을 정수로 바꾸지만 str에 어떤 문자열이 있던지 전달하려고 할 것이기 때문에 실제로 반환되는 것은 옵셔널 정수이다.

이것이 failable initializer이다 : 작동할 수도 안할 수도 있는데 이니셜라이저이다. 이것을 구조체나 클래스에서 init()대신 init?()을 사용하여 작성할 수 있고 문제가 발생하면 nil을 반환한다. 반환 값은 원할 때 언래핑할 수 있도록 타입의 옵셔널이 될 것이다.

예를 들어, 아홉 자리 Id 문자열을 사용하여 생성되는 Person 구조체를 코딩할 수 있다. 만약 9자리 문자열이 아닌 다른 것이 사용되면 우리는 nil을 반환할 것이고 그렇지 않으면 계속 진행될 것이다.

struct Person {
    var id: String

    init?(id: String) {
        if id.count == 9 {
            self.id = id
        } else {
            return nil
        }
    }
}

10. Typecasting

스위프트는 변수들의 타입을 항상 알아야하지만 때론 네가 스위프트보다 더 많이 알고 있다. 예를 들어, 여기 세 개의 클래스가 있다.

class Aninal { }
class Fish: Animal { }

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

우리는 fisth 와 dog를 두 개씩 만들어 배열에 넣을 것이다.

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

스위프트는 Fish와 Dog가 Animal 클래스에서 상속되는 것을 볼 수 있으므로 타입 추론을 상용해서 pets를 Animal 배열로 만든다.

pets 배열을 반복하고 모든 개들이 짖도록 요청하려면 우리는 typecast를 수행해야한다 : 스위프트는 각각의 pet이 Dog 객체인지 확인하고 만약 그렇다면 makeNoise()를 호출할 수 있다.

이것은 as?라 불리는 새로운 키워드를 사용한다, 이것은 옵셔널을 반환한다 : typecast가 실패하면 nil일 것이고 그렇지 않으면 바뀐 타입일 것이다.

for pet in pets {
    if let dog = pet as? Dog {
        dog.makeNoise()
    }
}

11. Optionals summary

  1. 옵셔널을 사용하여 값의 부재를 명확하고 모호하지 않은 방식으로 표현할 수 있다.
  2. 스위프트에서 if let이나 guard let을 사용하여 언래핑하지 않으면 옵셔널을 사용할 수 없다.
  3. 느낌표를 사용하여 강제 언래핑을 할 수 있지만 nil을 강제 언래핑 하려 한다면 코드는 충돌할 것이다.
  4. 암시적으로 언래핑된 옵셔널은 일반 옵셔널의 안전 체크가 없다.
  5. 옵셔널을 언래핑하고 만약 안에 아무것도 없다면 디폴트 값을 제공하기 위해서 nil 병합을 사용할 수 있다.
  6. 옵셔널 체이닝로 옵셔널을 조작하는 코드를 작성할 수 있지만 옵셔널이 비어있다면 그 코드는 무시될 것이다.
  7. try?를 사용하여 throwing 함수를 옵셔널 반환값으로 바꾸거나 try!를 사용하여 에러가 throw됐을 때 충돌할 수 있다.
  8. 잘못된 값을 받았을 때 이니셜라이져가 fail하길 바란다면 failable 이니셜라이져를 만들기 위해 init?()을 사용해라
  9. Typecasting을 사용하여 한 유형의 객체를 다른 유형으로 바꿀 수 있다.

출처

0개의 댓글