클로저부터 계속 중요한 것만 나오는군요
기초 배우는 부분은 오늘이 마지막인거 같네요
이 부분들은 이 챌린지 하면서도 틈틈이 복습해야겠어요
근래에 날씨가 따듯해져서 기분이 좋네요
오늘도 파이팅입니다!


Handling missing data

우리는 값들을 저장하기 위해 Int같은 타입들을 써왔죠
근데 만약 age라는 프로퍼티로 유저들의 나이를 저장하고 싶은데
나이를 모른다면 어떻게 할까요?

임시로 0을 입력한다고 할까요? 하지만 갓 태어난 아기들과 헷갈릴거에요
1000이나 -1같은 있을 수 없는 나이로 'unknown'을 표시할 수 있겠지만 그렇다면 이런 유형으로 만든 모든 숫자들을 기억해야 할겁니다.

스위프트에서는 optional이라는 해결책을 줍니다
아무 타입에서나 옵셔널을 지정할 수 있어요
옵셔널 Int는 0이나 50같은 숫자를 가질 수 있지만 값을 안 가질 수도 있습니다
nil같은 값 말이죠.
스위프트에서 nil은 다른 언어에서 null NULL과 같은 의미 입니다

타입에 옵셔널을 붙여주려면 ?을 추가해주면 됩니다
아래처럼 말이죠

var age: Int? = nil

아무런 값도 가지지 않게 되는데요 나중에 값을 할당해 줄 수 있습니다

age = 28

Unwrapping optionals

옵셔널String은 "안녕-"같은 값을 가질 수도 있고 nil일 수도 있죠

아래 예를 보시죠

var name: String? = nil

여기서 name.count를 쓰면 어떻게 될까요? stringcount라는 프로퍼티를 가지고 있죠. 하지만 위의 namenil이죠
nil 그 프로퍼티를 가지고 있지 않습니다

이 때문에 스위프트에서는 안전하지않아서 허용해주지 않습니다
그래서 우리는 unwrapping과정을 통해서 옵셔널을 살펴봐야합니다.

옵셔널을 언래핑하는 가장 흔한 방법은 if let구문을 사용하는거에요
조건을 가지고 언래핑을 해줘서 옵셔널안에 값이 있을 때만 사용할 수 있게 해줍니다

아래처럼 작성해서 사용합니다.

if let unwrapped = name {
    print("\(unwrapped.count) letters")
} else {
    print("Missing name.")
}

nameString값을 가질 경우 unwrapped에 넣어서 일반적인 String처럼 사용될 거에요. 반면 값이 없다면 else 구문을 넘어가겠죠?

namenil이 있기 때문에 else 구문이 동작하네요


Unwrapping with guard

if let의 대안으로 guard let 구문이 있어요
마찬가지로 옵셔널 언래핑을 해주는데 만약 내부에서 nil을 발견하면 현재 사용중인 함수, 반복문, 조건문을 끝내기를 기대한다(expect) 잘 이해가...

if letguard let의 가장 큰 차이점은 언래핑한 옵셔널이 guard 구문 이후에도 사용이 가능하다는 거에요

아래 예시를 통해서 볼까요?

func greet(_ name: String?) {
    guard let unwrapped = name else {
        print("You didn't provide a name!")
        return
    }

    print("Hello, \(unwrapped)!")
}

옵셔널 String을 받아서 언래핑 하는 함수입니다.
만약 값이 없다면 print()문을 출력하고 함수를 종료하구요
그리고 위에서 언급햇듯이 guard구문에서 언래핑된 옵셔널은 그 뒤에도 사용될 수 있어서 guard문 밖에서 출력됩니다.

guard let을 사용하면 함수 시작 초기에 문제를 발견하면 그 즉시 처리하고 종료하기 때문에 나머지 부분은 안전하다고 볼 수 있죠.


Force unwrapping

옵셔널은 있을수도 없을수도 있는 데이터를 표현하는거죠.
하지만 어떤 경우는 값이 nil아닌 것을 확실히 해야할 때가 있습니다.
이런 경우에 스위프트는 옵셔널을 강제로 언래핑 할 수 있게 해줍니다
옵셔널 타입을 non-optional타입으로 변경해줍니다
예전에 들었던 다른 강의에서는 택배상자를 막 잡아뜯어서 여는걸 비유했어요

예를 들어 "5"라는 숫자를 가진 문자열을 Int로 변경할 수도 있습니다

let str = "5"
let num = Int(str)

이 과정에서 num은 옵셔널Int가 됩니다.
왜냐하면 num에 "5"같은 숫자가 아닌 "치킨!"같은게 들어있을 수 있기 때문이죠

스위프트는 이게 작동할 지 확신은 못하지만 코드가 안전하다는걸 확인할 수 있으면 !기호를 붙여서 강제로 언래핑 할 수 있습니다.

let num = Int(str)!

스위프트는 옵셔널을 바로 언래핑하고 numInt?가 아닌 보통의 Int로 만듭니다
하지만 Int로 변환할 수 없을때(내가 실수한 경우에)는 코드가 작동을 안하게 됩니다.

그래서 내가 안전하다는 것을 확인했을때만 force unwrapping을 써야합니다


Implicitly unwrapped optionals

암묵적으로(암시적으로) 언래핑한 옵셔널

다른 옵셔널들과 마찬가지로 이 옵셔널 또한 값을 가지고 있거나 nil일 것이다
하지만 다른 옵셔널들과의 차이점이라면 사용하기 위해서 언래핑을 할 필요가 없다는 것이다. 마치 옵셔널이 아닌것처럼 사용한다.

Implicitly unwrapped optionals은 타입명 뒤에 !을 붙여서 작성한다.

let age: Int! = nil

이 옵셔널들은 이미 언래핑된것처럼 작동하기에 if let이나 guard let을 사용할 필요가 없어요. 하지만 nil인 값을 사용하면 당연히 오류가 나겠죠?

Implicitly unwrapped optionals은 변수가 nil로 생성되어야 하는 경우가 있기 때문에 존재해요. 그리고 그 변수들은 내가 사용할 때 쯤이면 값을 가지게 됩니다.
매번 if let같은 구문을 안 쓸수 있는것도 도움이 되죠


Nil coalescing

nil 병합 연산자라고도 합니다. 옵셔널을 언래핑하고 값이 있다면 반환합니다.
만약 값이없다면(nil이라면) 기본값을 반환합니다.
두 방법다 결과는 옵셔널이 아니게됩니다

Int 값을 받아서 String?을 반환하는 함수를 보시죠

func username(for id: Int) -> String? {
    if id == 1 {
        return "버스커버스커"
    } else {
        return nil
    }
}

이 함수를 15라는 ID로 호출하면 nil을 반환할 겁니다. 등록되지 않은 유저라면요
하지만 nil병합 연산자를 사용하면 "익명"이라는 기본값을 제공할 수 있습니다

let user = username(for: 15) ?? "익명"

이 코드는 username()에서 반환되는 값을 체크하고 값이 있다면 user에 할당을 그렇지 않다면 "익명"이 사용될 겁니다.


Optional chaining

스위프트는 옵셔널과 관련된 단축어를 많이 제공해줘요
만약에 가.나.다에 접근하고싶고 가 옵셔널이라고 가정해볼게요
이때 ?만 붙여주면 옵셔널 체이닝을 쓸수있습니다. "가.나?.다"

저 코드가 작동하면 스위프트는 "나"가 값이 있는지 확인해요.
만약에 nil이라면 나머지 코드는 무시하고 바로 nil을 반환합니다.
값이 존재한다면 언래핑하고 나머지도 진행합니다

이름을 가진 배열로 확인해보죠

let names = ["John", "Peter", "Harry"]

우리는 배열의 first 프로퍼티를 사용할 거에요.
첫번째 이름을 반환하거나 배열이 비었다면 nil을 반환해주는 역할입니다
그리고 uppercase()도 사용할 거에요

let bigName = names.first?.uppercased()

?기호가 옵셔널 체이닝입니다.
만약 firstnil을 반환하면 uppercase()는 동작하지 않겠죠
그리고 bigNamenil을 할당할겁니다


Optional try

잠깐 throw파트로 가볼까요?

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.")
}

우리는 이 코드를 에러를 발견하고 대처하는데 사용했습니다

try에는 두가지 대안이 있는데요 옵셔널과 강제 언래핑을 배운 지금 이쪽이 더 이해가 될거에요

첫 번째는 try?입니다. throwing functions 을 옵셔널을 반환하는 함수로 바꿉니다.
함수가 에러를 throw하면 결과로 nil을 받게됩니다. 그외는 wrap된 옵셔널을 받죠

try?을 사용해서 checkPassword() 써보죠

if let result = try? checkPassword("password") {
    print("Result was \(result)")
} else {
    print("D'oh.")
}

다른 대안은 try!입니다
함수가 작동이 실패하지 않을거라는 전제하에 사용하는거죠
만일 함수가 에러를 도출한다면 크래시가 생길겁니다


Failable initializers

let str = "5"
let num = Int(str)

앞서 강제 언래핑에서 사용했던 코드입니다.

StringInt로 변환했죠. 하지만 String이 전달 될 수도 있기 때문에 옵셔널Int를 받습니다.

이것이 failable initializer인데요. 초기화 생성자인데 작성할수도 안할수도 있다는 의미입니다.
구조체나 클래스에 init()?으로 작성할 수 있구요 뭔가 잘못되면 nil을 반환해줍니다.
그리고 우리가 사용한 타입의 옵셔널로 반환해줍니다. 언래핑을 할지는 우리의 선택이구요

예시 코드를 볼까요?

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

위의 코드는 id가 9글자의 문자열이 아니면 nil을 반환합니다.
반대의 경우엔 정상적으로 작동하겠죠?


Typecasting

스위프트는 내가 작성한 모든 변수의 타입을 알아야하죠.
하지만 내가 스위프트보다 많은 걸 알 때도 있습니다.

class Animal { }
class Fish: Animal { }

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

몇 가지 클래스가 있는데요
이걸 배열에 생성하여 넣을 수 있습니다.

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

스위프트는 FishDogAnimal을 상속한다는 걸 알죠
그래서 타입추론을 사용해 pets라는 배열을 만들게 해줍니다.

우리가 반복문을 사용해 모든 Dog들이 짖게 하려면 타입캐스팅을 해야합니다.
스위프트는 모든 petDog인지 확인할 것이고 맞다면 makeNoise()을 호출할 수 있게 해줄겁니다.

타입 캐스팅을 하려면 as?라는 문구를 쓰면됩니다.
옵셔널을 반환해주는데 타입캐스팅이 실패하면 nil이 될겁니다

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


Summary

  1. 옵셔널은 값의 부재를 확실하고 명확한 방법으로 표현하게 해준다
  2. 스위프트는 언래핑을 하지않고서 옵셔널을 사용하게 두지 않는다. if let이나 guard let으로 언래핑 해주자
  3. !기호를 사용해 옵셔널을 강제언래핑 할 수 있다. 하지만 nil을 강제언래핑하면 크래시난다
  4. Implicitly unwrapped optionals은 일반 옵셔널들처럼 안전 체크가 없다
  5. nil 병합 연사자를 통해서 옵셔널을 언래핑하고 만약 nil이라면 기본값을 제공 할 수 있다.
  6. 옵셔널 체이닝은 옵셔널을 제어하는 코드를 작성할 수 있게 해준다. 하지만 옵셔널이 빈 상태면 코드는 무시된다.
  7. try?를 통해 throwing function이 옵셔널을 반환하게 바꿀 수 있고 try!를 하면 에러가 throw되면 크래시를 나게한다
  8. 올바르지 못한 입력값을 받았을 때 이니셜라이저가 실패하게 하려면 init?()을 쓰면 된다. (failable initializer)
  9. 타입 캐스팅을 통해 한 타입의 개체를 다른 것으로 바꿀수 있다.
profile
초보 개발자 살아남기

0개의 댓글

Powered by GraphCDN, the GraphQL CDN