클로저부터 계속 중요한 것만 나오는군요
기초 배우는 부분은 오늘이 마지막인거 같네요
이 부분들은 이 챌린지 하면서도 틈틈이 복습해야겠어요
근래에 날씨가 따듯해져서 기분이 좋네요
오늘도 파이팅입니다!
우리는 값들을 저장하기 위해 Int
같은 타입들을 써왔죠
근데 만약 age
라는 프로퍼티로 유저들의 나이를 저장하고 싶은데
나이를 모른다면 어떻게 할까요?
임시로 0
을 입력한다고 할까요? 하지만 갓 태어난 아기들과 헷갈릴거에요
1000
이나 -1
같은 있을 수 없는 나이로 'unknown'을 표시할 수 있겠지만 그렇다면 이런 유형으로 만든 모든 숫자들을 기억해야 할겁니다.
스위프트에서는 optional
이라는 해결책을 줍니다
아무 타입에서나 옵셔널을 지정할 수 있어요
옵셔널 Int
는 0이나 50같은 숫자를 가질 수 있지만 값을 안 가질 수도 있습니다
nil
같은 값 말이죠.
스위프트에서 nil
은 다른 언어에서 null
NULL
과 같은 의미 입니다
타입에 옵셔널을 붙여주려면 ?
을 추가해주면 됩니다
아래처럼 말이죠
var age: Int? = nil
아무런 값도 가지지 않게 되는데요 나중에 값을 할당해 줄 수 있습니다
age = 28
옵셔널String
은 "안녕-"같은 값을 가질 수도 있고 nil
일 수도 있죠
아래 예를 보시죠
var name: String? = nil
여기서 name.count
를 쓰면 어떻게 될까요? string
은 count
라는 프로퍼티를 가지고 있죠. 하지만 위의 name
은 nil
이죠
nil
그 프로퍼티를 가지고 있지 않습니다
이 때문에 스위프트에서는 안전하지않아서 허용해주지 않습니다
그래서 우리는 unwrapping
과정을 통해서 옵셔널을 살펴봐야합니다.
옵셔널을 언래핑하는 가장 흔한 방법은 if let
구문을 사용하는거에요
조건을 가지고 언래핑을 해줘서 옵셔널안에 값이 있을 때만 사용할 수 있게 해줍니다
아래처럼 작성해서 사용합니다.
if let unwrapped = name {
print("\(unwrapped.count) letters")
} else {
print("Missing name.")
}
name
이 String
값을 가질 경우 unwrapped
에 넣어서 일반적인 String
처럼 사용될 거에요. 반면 값이 없다면 else 구문을 넘어가겠죠?
name
에 nil
이 있기 때문에 else 구문이 동작하네요
if let
의 대안으로 guard let
구문이 있어요
마찬가지로 옵셔널 언래핑을 해주는데 만약 내부에서 nil
을 발견하면 현재 사용중인 함수, 반복문, 조건문을 끝내기를 기대한다(expect) 잘 이해가...
if let
과 guard 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
을 사용하면 함수 시작 초기에 문제를 발견하면 그 즉시 처리하고 종료하기 때문에 나머지 부분은 안전하다고 볼 수 있죠.
옵셔널은 있을수도 없을수도 있는 데이터를 표현하는거죠.
하지만 어떤 경우는 값이 nil
아닌 것을 확실히 해야할 때가 있습니다.
이런 경우에 스위프트는 옵셔널을 강제로 언래핑 할 수 있게 해줍니다
옵셔널 타입을 non-optional타입으로 변경해줍니다
예전에 들었던 다른 강의에서는 택배상자를 막 잡아뜯어서 여는걸 비유했어요
예를 들어 "5"라는 숫자를 가진 문자열을 Int
로 변경할 수도 있습니다
let str = "5"
let num = Int(str)
이 과정에서 num
은 옵셔널Int
가 됩니다.
왜냐하면 num
에 "5"같은 숫자가 아닌 "치킨!"같은게 들어있을 수 있기 때문이죠
스위프트는 이게 작동할 지 확신은 못하지만 코드가 안전하다는걸 확인할 수 있으면 !
기호를 붙여서 강제로 언래핑 할 수 있습니다.
let num = Int(str)!
스위프트는 옵셔널을 바로 언래핑하고 num
을 Int?
가 아닌 보통의 Int
로 만듭니다
하지만 Int
로 변환할 수 없을때(내가 실수한 경우에)는 코드가 작동을 안하게 됩니다.
그래서 내가 안전하다는 것을 확인했을때만 force unwrapping을 써야합니다
다른 옵셔널들과 마찬가지로 이 옵셔널 또한 값을 가지고 있거나 nil
일 것이다
하지만 다른 옵셔널들과의 차이점이라면 사용하기 위해서 언래핑을 할 필요가 없다는 것이다. 마치 옵셔널이 아닌것처럼 사용한다.
Implicitly unwrapped optionals은 타입명 뒤에 !
을 붙여서 작성한다.
let age: Int! = nil
이 옵셔널들은 이미 언래핑된것처럼 작동하기에 if let
이나 guard let
을 사용할 필요가 없어요. 하지만 nil
인 값을 사용하면 당연히 오류가 나겠죠?
Implicitly unwrapped optionals은 변수가 nil
로 생성되어야 하는 경우가 있기 때문에 존재해요. 그리고 그 변수들은 내가 사용할 때 쯤이면 값을 가지게 됩니다.
매번 if let
같은 구문을 안 쓸수 있는것도 도움이 되죠
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
에 할당을 그렇지 않다면 "익명"이 사용될 겁니다.
스위프트는 옵셔널과 관련된 단축어를 많이 제공해줘요
만약에 가.나.다에 접근하고싶고 나 가 옵셔널이라고 가정해볼게요
이때 나 에 ?
만 붙여주면 옵셔널 체이닝을 쓸수있습니다. "가.나?.다"
저 코드가 작동하면 스위프트는 "나"가 값이 있는지 확인해요.
만약에 nil
이라면 나머지 코드는 무시하고 바로 nil
을 반환합니다.
값이 존재한다면 언래핑하고 나머지도 진행합니다
이름을 가진 배열로 확인해보죠
let names = ["John", "Peter", "Harry"]
우리는 배열의 first
프로퍼티를 사용할 거에요.
첫번째 이름을 반환하거나 배열이 비었다면 nil
을 반환해주는 역할입니다
그리고 uppercase()
도 사용할 거에요
let bigName = names.first?.uppercased()
저 ?
기호가 옵셔널 체이닝입니다.
만약 first
가 nil
을 반환하면 uppercase()
는 동작하지 않겠죠
그리고 bigName
에 nil
을 할당할겁니다
잠깐 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!
입니다
함수가 작동이 실패하지 않을거라는 전제하에 사용하는거죠
만일 함수가 에러를 도출한다면 크래시가 생길겁니다
let str = "5"
let num = Int(str)
앞서 강제 언래핑에서 사용했던 코드입니다.
String
을 Int
로 변환했죠. 하지만 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
을 반환합니다.
반대의 경우엔 정상적으로 작동하겠죠?
스위프트는 내가 작성한 모든 변수의 타입을 알아야하죠.
하지만 내가 스위프트보다 많은 걸 알 때도 있습니다.
class Animal { }
class Fish: Animal { }
class Dog: Animal {
func makeNoise() {
print("Woof!")
}
}
몇 가지 클래스가 있는데요
이걸 배열에 생성하여 넣을 수 있습니다.
let pets = [Fish(), Dog(), Fish(), Dog()]
스위프트는 Fish
와 Dog
이 Animal
을 상속한다는 걸 알죠
그래서 타입추론을 사용해 pets
라는 배열을 만들게 해줍니다.
우리가 반복문을 사용해 모든 Dog
들이 짖게 하려면 타입캐스팅을 해야합니다.
스위프트는 모든 pet
이 Dog
인지 확인할 것이고 맞다면 makeNoise()
을 호출할 수 있게 해줄겁니다.
타입 캐스팅을 하려면 as?
라는 문구를 쓰면됩니다.
옵셔널을 반환해주는데 타입캐스팅이 실패하면 nil
이 될겁니다
for pet in pets {
if let dog = pet as? Dog {
dog.makeNoise()
}
}
if let
이나 guard let
으로 언래핑 해주자!
기호를 사용해 옵셔널을 강제언래핑 할 수 있다. 하지만 nil
을 강제언래핑하면 크래시난다nil
이라면 기본값을 제공 할 수 있다.try?
를 통해 throwing function이 옵셔널을 반환하게 바꿀 수 있고 try!
를 하면 에러가 throw되면 크래시를 나게한다init?()
을 쓰면 된다. (failable initializer)