Swift Optional (1)

Optionals 기본 개념

Swift는 안전한 코딩을 할 수 있게 해주는 언어라고 알려져 있습니다. 그리고 이 안전성의 기본 바탕에 있는 중요한 요소 중 하나가 바로 Optional이라는 개념입니다. Optional은 Type casting이나 nil value 체크 등에 있어서 중요한 역할을 합니다. 이 포스팅에서는 Optional의 기본적인 의미에 대해서 알아보겠습니다.

Optional은 ‘?’을 통해 표현되는데, 그 의미는 다음과 같습니다.

이 변수에는 값이 들어갈 수도 있고, 아닐 수도 있어(nil)”

즉, nil을 표현하기 위한 수단으로 ?(물음표)를 사용한다는 것인데, 이 ?가 어떤식으로 적용이 될까요? 가장 기본적인 형태는 다음과 같습니다.

let myFirstOptionalVar: Int?

위처럼 변수의 타입의 뒤에 ?를 붙여주면 해당 변수는 Optional이 됩니다. Swift에서는 기본적으로 변수 선언시 nil 값이 들어가는 것을 허용하지 않습니다. 런타임 에러를 뿜는 것이 아니라, 아예 컴파일 에러를 내버립니다. 그러므로 아래에서 첫 번째 줄의 코드는 에러이고, 두 번째 줄은 Optional type(String?)으로 선언했으므로 에러가 아닙니다.

var optionalString: String = nil
var optionalString: String? = nil

다음 예시의 경우, String을 Int로 캐스팅하는 경우입니다.

let possibleNumber = “123”
let convertedNumber = Int(possibleNumber)
print(convertedNumber)
// 출력 결과 : Optional(123)

여기서 “123”은 String이기 때문에, Int(possibleNumber) 초기화에 실패합니다. 이 경우 바로 에러가 나와야한다고 생각하실 수 있지만, Swift는 이 경우 convertedNumber를 Optional Int형(Int?)으로 선언합니다.(Int가 아닙니다.)


nil

Swift에서 nil은 optional 변수 이외에서 사용할 수 없습니다. 그런데 iOS 개발을 할 경우 상당히 많은 부분에서 nil을 사용합니다. 그러므로 optional에 대해서 잘 알아두셔야 합니다. 또한 nil값은 따로 초기화하지 않아도 기본으로 설정됩니다.

var optionalString: String?
var optionalString2: String? = nil
// 두 값 모두 nil
0과 nil의 차이?

다만 유의할 점은 Swift의 nil은 다른 언어에서 pointer가 존재하지 않는 값을 가리키는 것과는 다릅니다. Swift의 nilvalue가 없는 것을 의미합니다. 주소값 비교와 value값 비교정도로 이해하면 될 것 같습니다.


Wrapping

Optional에 대해 보다보면, 많은 곳에서 Wrapping이라는 개념이 나옵니다. Optional 타입은 기본적으로 wrap되어 있는 상태입니다. 즉, Optional로 선언된 변수들은 값이 있는 것인지, nil인 것인지 wrap되어 있어서 모르는 상태입니다. 그렇기 때문에(컴파일러 입장에서는 변수가 nil일 수도 있기 때문에) wrap된 상태에서는 설령 변수에 value값이 있다고 하더라도 바로 value가 출력되지 않습니다.(Swift 3.0에서 반영된 사항입니다.) 아래 예제를 보시면,

var optionalString: String? = “Hello”
print(optionalString)
// 출력 결과: Optional(“Hello”)

이 경우, optionalString이 nil일 수도 있기 때문에, 결과값 “Hello”가 출력되지 않고, Optional(“Hello”) 가 콘솔창에 출력됩니다.


Forced Unwrapping

앞선 예제에서처럼 출력 결과가 Optional(“Hello”)처럼 나오는 것은 대부분의 경우 원하는 출력값이 아닙니다. 이 때 올바른 출력을 위해 사용하는 것이 !(exclamation mark), 즉 느낌표입니다. 즉, optional로 선언했지만, 무조건 변수가 있는 상황이 보장된 경우 느낌표(!)를 쓰면 우리가 원하는 Hello을 출력할 수 있습니다.

var optionalString: String? = “Hello”
print(optionalString!)
// 출력 결과: Hello

변수명 뒤의 느낌표는 Optional을 unwrap합니다. Optional은 unwrap된 상태에서만 값을 제대로 출력할 수 있습니다. 느낌표를 활용한 다른 예제를 살펴보겠습니다.

let value1: String? = nil
let value2: String! = nil // 여기서는 에러가 아닙니다.
print(value) // nil 출력
print(value2) // error

이 경우, value1과 value2는 모두 Optional 타입입니다. 다만 value1은 아직 wrap되어 있는 상태이므로 print에서 문제가 되지 않습니다. 다만 값이 있다면 Optional(값)형태로 출력이 될 것입니다. 다음으로 value2의 경우, 느낌표에 의해 Optional 값이 자동으로 unwrap됩니다. unwrap된 상태에서 값을 출력하면 런타임 에러가 발생합니다. 그러므로 일반적으로 위처럼 value2가 unwrap된 상태로 print하도록 놔두기보다는 if를 통해 값이 nil인 경우를 체크하고 출력을 합니다.

let value2: String! = nil
if value2 != nil {
print(value2)
}

이번에는 헷갈릴 수 있는 예제를 봐보겠습니다.

class Square {
var sideLength: Double
  init(sideLength: Double){
self.sideLength = sideLength
}
}
// 클래스를 Optional 타입(?)으로 선언
// sideLength1도 Optional 타입(?)으로 선언
// 값 출력시 !를 쓰면 정상 출력
let optionalSquare1: Square? = Square(sideLength: 2.5)
let sideLength1 = optionalSquare1?.sideLength
// 클래스를 Optional 타입(?)으로 선언
// sideLength2를 Optional 타입(!)으로 선언
// 값은 unwrap 상태이므로 !가 없어도 출력(있으면 !를 2번 쓰므로 에러)
let optionalSquare2: Square? = Square(sideLength: 2.5)
let sideLength2 = optionalSquare2!.sideLength
// sideLength3를 Optional 타입(!)으로 선언
// 이를 Implicitly Unwrapped Optional이라고 부릅니다.
// 클래스와 sideLength3 모두 unwrap 상태이므로 !가 없어도 출력(있으면 !를 2번 쓰므로 에러)
let optionalSquare3: Square! = Square(sideLength: 2.5)
let sideLength3 = optionalSquare3!.sideLength
print(optionalSquare1) // Optional(Square)
print(optionalSquare2) // Optional(Square)
print(optionalSquare3) // Square
print(sideLength1) // Optional(2.5)
print(sideLength2) // 2.5
print(sideLength3) // 2.5
print(sideLength1!) // 2.5
print(sideLength2!) // error
print(sideLength3!) // error

위 예제는 아래 링크에서 확인할 수 있습니다.


Optional Binding

Optional Binding은 Optional 타입으로 선언된 변수에 값이 있는지 없는지를 확인할 수 있도록 해주는 기능입니다. Optional Binding을 사용하면 느낌표 없이 Optional 타입의 변수 값을 출력할 수 있어서 좀 더 안전한 형태로 값을 얻을 수 있습니다. 기본적인 형태는 다음과 같습니다.

if let 변수명 = Optional 변수 {
// 임시 변수에 Optional 변수의 value값이 할당됩니다.
}

Optional Binding은 조금 독특한 if let 형태의 조건문입니다. 무슨 의미인지 알기 위해 예제를 살펴 보겠습니다.

// Optional type으로 선언한 myNumber
let myNumber: Int? = 1234
if let actualNumber = myNumber {
print(“\(myNumber)은 실제로 \(actualNumber)입니다.”)
} else {
print(“\(myNumber)는 변환될 수 없습니다.”)
}
// 출력 결과 : Optional(1234)은 실제로 1234입니다.
print(actualNumber) // error

위의 예에서는 myNumber가 Optional 타입으로 선언되어 있습니다. 원래는 이 myNumber값을 출력하기 위해서는 !를 사용해야합니다. 하지만, Optional Binding은 먼저 이 myNumber의 값이 있는 경우와 없는 경우로 나누고, 값이 있는 경우를 if let 조건문 안에 넣을 수 있습니다. 여기서는 actualNumbermyNumber의 값을 할당하고, 값이 있다면 actualNumber에 이를 넘겨주어 바로 실제 값으로 사용할 수 있도록 해줍니다. 추가적으로 actualNumber는 if문 안에서만 할당되는 로컬 변수입니다. if 밖에서는 actualNumber를 사용할 수 없습니다. 또한,

if let firstNumber = Int(“4”), let secondNumber = Int(“42”), firstNumber < secondNumber && secondNumber < 100 {
print(“\(firstNumber) < \(secondNumber) < 100”)
}
// 출력 결과: 4 < 42 < 100

위의 경우처럼 Optional value가 nil인지 여부와 함께 boolean 결과를 콤마로 연결해서 사용할 수도 있습니다.

저는 개인적으로 Optional Binding이 굉장히 유용한 기능이라고 생각합니다. 왜냐하면, Optional Binding이 코드를 짤 때 정말 많이 하는 “nil 체크 + nil이 아닌 경우에 새로 변수 만들기”를 1줄로 동시에 할 수 있도록 해주기 때문입니다. 실제로 상당히 많이 쓰이므로 잘 알아두시는 것이 좋습니다.

Optional Binding은 Optional type의 변수에 대한 nil 체크와 로컬변수에 이 값을 할당하는 두 가지 기능을 가지고 있습니다.

참고 서적 및 자료 : The swift Programming Language(3.0.1), stackoverflow(what is an unwrapped value in swift)