[Swift] 옵셔널 (Optional)

김창규·2023년 3월 15일
0

Swift

목록 보기
2/3

* 해당 글은 개인 공부자료이므로 오류가 있을 수 있습니다.

옵셔널 (Optional)

옵셔널이란 ?

  • 값이 있을 수도 있고 없을 수도 있는 변수나 상수를 나타내는 데이터
  • 타입 뒤에 "?" 또는 "!"를 사용하여 선언

[ 개요 ]

옵셔널(Optional)은 값이 있을 수도 없을 수도 있는 경우에 사용되는 데이터 타입이며 ?,!로 표현합니다.

Swift로 작성된 코드를 보다 보면 타입뒤에 Int?, String?, Int!, String! 와 같이 사용되는 것을 보신적 있을겁니다. 이 기호가 바로 옵셔널 기호입니다.
반대로, ?가 없는 일반적인 데이터 타입일 때는 nil을 할당할 수 없습니다. ( *nil은 값이 없음을 의미합니다. = null )

이렇게 옵셔널로 값의 존재 여부를 명시적으로 표현하는 것은 코드 안정성신뢰성을 높이는데 도움이 됩니다.

예제를 통해 옵셔널을 알아보고, ?, !의 차이점도 같이 확인해보겠습니다.


[ 옵셔널 선언 ]

옵셔널의 변수 선언은 데이터 타입 뒤에 ?가 붙은 형식으로 되어있습니다.
먼저 ?가 붙은 옵셔널변수와 그렇지 않은 일반 변수의 코드를 보겠습니다.

// "myOptionalString"이라는 String타입을 "옵셔널변수"로 선언
var myOptionalString: String?

// "myString"이라는 String타입을 선언
var myString: String

// "myOptionalString"의 옵셔널 변수에 nil 할당
myOptionalString = nil

//"myString"의 일반 변수에 nil 할당, 그러나 불가능
myString = nil // Error

이렇게 nil값을 할당받기 위해서는 ?기호를 사용하여 옵셔널로 선언해 주어야만 정상적으로 할당이 가능합니다.
정상적으로 선언이 완료되었으면 사용할 준비가 끝났습니다.

이제 옵셔널을 사용하기 위해서는 언래핑(Unwrapping)을 해주어야 하는데요.
다음은 이 언래핑을 하기위해서 알아보겠습니다.


[ 옵셔널 값 추출 (Optional Unwrapping) ]

먼저 왜? 추출해서 사용해야되는지 부터 알아보겠습니다.
앞에서 옵셔널은 값이 있을 수도 없을 수도 있는 데이터 타입이라고 말씀드렸습니다.
옵셔널 타입 변수를 일반 데이터 타입변수에 할당이 가능할까요?

var myOptionalString: String? = "Hello, World!"
var mySecondOptionalString: String?
var myString: String

mySecondOptionalString = myOptionalString // Success
print(myOptionalString)       // result : Optional("Hello,World!")
print(mySecondOptionalString) // result : Optional("Hello,World!")

myString = myOptionalString // Error

myOptinalStringmySecondOptionalString에 할당이 가능합니다. 같은 옵셔널 타입이기 때문입니다.
하지만 myOptionalStringmyString에 대입한다면 컴파일 에러가 발생합니다.

있을 수도 없을 수도 없는 이 myOptionalString옵셔널을 항상 값이 있어야하는 일반 데이터 타입myString에 할당한다면 실제 코드 실행될 때 까지 이 값이 있는지 없는지 확인할 방법이 없기 때문입니다.

이 문제를 해결하기 위해서 Optional("Hellow, World!")에서 옵셔널을 해제해줘야 합니다.
이 때 사용하는 것이 옵셔널 언래핑(Optional Unwrapping)입니다.

1. 강제 언래핑(Unconditional Unwrapping)

옵셔널 언래핑은 몇가지 방법이 있습니다. 그 중 강제 언래핑에 대해 알아보겠습니다.

  • 옵셔널 값 뒤에 !를 붙혀 강제로 값을 추출
  • 강제 추출 시 값이 nil이면 런타임 에러 발생
var myOptionalString: String? = "Hello, World!"

print(myOptionalString) // result : Optional("Hello,World!")
print(myOptionalString!) // result : "Hello,World!"

myOptionalString = nil
print(myOptionalString!) // Error

뒤에 !만 붙혀주면 옵셔널을 풀 수가 있습니다. 간단하지만 nil일 경우 런타임 에러가 발생합니다.
지금은 코드가 짧기 때문에 에러가 생겨도 금방 찾을 수 있지만 코드양이 많아지고 복잡해지면 힘들어집니다.

값이 "100% 무조건 있다!"가 아니라면 강제 언래핑은 지양하는것이 좋겠습니다.

2. 옵셔널 바인딩 (Optional Binding)

  • 안전하게 값을 언래핑 할 수 있음
  • 옵셔널 값이 있으면 상수 또는 변수에 할당해서 사용
  • if let, guard let 을 사용
var myOptionalString: String? = "Hello, World!"

// if let 을 사용한 옵셔널 바인딩
if let myString = myOptionalString {
    // myOptional is not nil
    print(myString) // result : "Hello, World!"
} else {
    // myOptional is nil
    print("myOptional is nil!")
}

// guard let을 사용한 옵셔널 바인딩
guard let myString = myOptionalString else { 
    // myOptional is nil
    return 
    }
// myOptional is not nil
print(myString) // result : "Hello, World!"

if let, guard let 두 방식의 사용방법은 비슷합니다. 옵셔널 변수를 myString 상수에 할당해서 사용하는 방식입니다. 만약 옵셔널이 nil이면 해당 에러에 대한 처리만 잘 해준다면 런타임 에러는 발생 하지 않기 때문에 안전한 방법이라고 볼 수 있습니다.

if let은 옵셔널 변수에서 할당 받은 상수는 if문 내부에서만 사용가능하고 nil경우 에러처라가 필요 없습니다.
guard let은 할당 받은 상수는 계속 사용이 가능하고 nil 값에 대한 에러 처리가 필요합니다.

nil일 때 해당 메서드나 루프에서 탈출을 해야되는 코드를 작성할 때는 guard let
nil일 때도 코드를 계속 진행하고자 할 때는 if let을 사용하면 되겠습니다.

3. 옵셔널 체이닝 (Optional Chaining)

옵셔널체이닝은 단어 뜻 그대로 점(.)을 사용하여 henry?.car.model또는 herny?.car?.model과 같이 이어진 여러 개의 속성을 접근하여 사용합니다.

  • nil일 수 있는 속성, 메서드 등에 안전하게 접근하기 위한 방법
  • 중간에 인스턴스의 값이 nil이면 옵셔널 타입 상관없이 nil을 리턴
  • 마지막 데이터는 옵셔널이여도 ?는 생략
  • 결과 값의 타입은 옵셔널 체이닝의 마지막 데이터 타입
struct Car {
    var model: String?
    var color: String
}

struct Person {
    var name: String
    var age: Int
    var car: Car?
    init(name: String, age: Int, carModel: String, color: String) {
        self.name = name
        self.age = age
        car = Car(model: carModel, Color: color)
    }
}

//  "henry"변수에 Person 옵셔널 타입 할당
var henry: Person? = Person(name: "henry", age: 10, carModel: "K5", color: "white")

print(henry.car.model) // Error
print(henry?.car?.model) // result : Optional("K5")

henry이름의 Person 옵셔널 변수에 Person값을 할당하였습니다.
henryhenrycar는 옵셔널 이기 때문에 ?를 붙혀주지 않으면 에러가 발생힙니다.

여기서 henry,car,model중 하나라도 nil 값이면 옵셔널 타입이 아니여도 결과는 에러가 아닌 nil값이 할당되어 런타임에러를 방지하여 코드의 안정성을 높일 수 있습니다.

henry는 옵셔널 변수 이기때문에 옵셔널바인딩을 이용하여 언래핑이 가능합니다.

// 옵셔널 체이닝 사용 x 
f let henry = henry {
    if let car = henry.car {
        print(car.model)
        if let model = car.model {
            print(model)
        }
    }
}

// 옵셔널 체이닝 사용 o
if let model = henry?.car?.model {
    print(model) // result : "K5"
}

옵셔널 체이닝을 이용하면 위와 같이 코드를 간단하게 작성할 수 있습니다.

4. 암시적 언랩 옵셔널 (Implicity Unwrapped Optionals)

  • 선언할 때 타입 뒤에 !기호를 사용
  • 암시적으로 언래핑한 상태이기 때문에 nil값 할당 가능
  • 옵셔널이기 때문에 nil값일 때 접근하면 런타임 에러 발생
var myOptionalString: String!
var myString: String

myOptionalString = nil
print(myOptionalString) // result : nil

// 'myOptionalString'에 "Hello"문자열 할당
myOptionalString = "Hello"

// 'myOptionalString'은 옵셔널이기 때문에 일반타입의 변수에 할당하지 못함
myString = myOptionalString // Error 

// 단독 사용은 옵셔널로 출력
print(myOptionalString) // result : Optional("Hello")

// 접근 시 자동으로 언래핑
print(myOptionalString + "World!") // result : "HelloWorld"

암시적 언랩 옵셔널은 선언할 때 !기호로 표현합니다. 옵셔널이기 때문에 nil값을 할당이 가능합니다.
잠재적으로 언랩을 하겠다라고 사용하는 것인데, 옵셔널이지만 이 옵셔널타입 변수에 접근을 하면 자동으로 강제 언랩핑을 하는것과 동일합니다.
print(myOptionalString + "World!")에서 접근을 하게되면 강제로 언래핑이 되어 결과값이 일반 String타입으로 나오게됩니다.

선언할 때 강제 언래핑하는것 말고는 일반 옵셔널과 다를게 없는데 왜 사용할까?

옵셔널에 값이 무조건 있다면 강제 언래핑으로 이 옵셔널에는 값이 무조건 들어있다 라는 것을 명확하게 알 수 있어 코드의 가독성과 편리성을 높일 수 있다고합니다.
하지만 혹시나 nil이 들어가게 된다면 앱이 죽어버리니 사용은 강제 언래핑과 같이 지양하는것이 좋을것같습니다.

이상으로 옵셔널(Optional)과 그 옵셔널을 사용하기 위해 옵셔널을 해제하는 옵셔널 언래핑(Optional Unwrapping)을 알아보았습니다.

잘못된 정보가 있다면 댓글로 알려주시면 감사하겠습니다.

profile
iOS 공부

0개의 댓글