Swift: Optional과 Nullability

Objective-C에서는 객체를 다룰 때 포인터 타입을 사용함으로써 어떤 객체를 가리키거나 nil 값을 가짐으로 런타임 에러와 같은 여러 가지 상황을 처리하곤 했다. Swift는 optional을 사용해 어떤 값을 가지거나, 아무 값도 아닌 상태(nil)를 다룬다.

1. Optionals

let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)

위 코드에서 이니셜라이저는 Int?를 리턴하는데, 이것을 optional Int라고 한다. 이 메소드는 문자열을 정수로 바꿀 수 없을 때 nil을 리턴한다. 성공적으로 변환하여 optional이 값을 가지고 있을 때는 언랩(정확한 표현은 forced unwrapping)을 해야 실제 값에 접근할 수 있다. 언랩을 하기 위해서는 optional 이름 뒤에 느낌표(!)를 붙인다. 따라서 안전하게 optional에 접근하는 코드는 아래와 같이 된다.

if convertedNumber != nil {
    println("convertedNumber has an integer value of \(convertedNumber!)")
}

optional을 if 또는 while 구문과 함께 사용할 때, 아래와 같이 간편하게 optional이 값을 가졌는지 확인해 임시 변수(혹은 상수)에 값을 대입할 수 있다. 이것을 optional 바인딩(binding)이라고 한다.

if let actualNumber = Int(possibleNumber) {
    println("possibleNumber has an integer value of \(actualNumber)")
} else {
    println("possibleNumber could not be converted to an integer")
}

초기화 과정 등에서 한번 설정되면 항상 값을 가지는 것으로 가정되어 작동하는 경우도 많이 있다. 이때 물음표(?) 대신 느낌표(!)를 사용하여 암묵적으로 언랩된(implicitly unwrapped) optional을 선언한다.

let assumedString: String! = "An implicitly unwrapped optional string."
println(assumedString)

암묵적으로 언랩된 optional도 nil과 비교하여 값을 가지는지 확인할 수 있지만, 변수의 라이프타임 동안 nil 검사를 해야 하는 경우에는 일반 optional을 사용하자.1

2. Nullability 키워드(annotations)

Xcode 6.3에서 Objective-C와 Swift의 optional을 더 깔끔하게 연동하기 위하여 nullablenonnull 키워드가 추가되었다.2 아래 코드와 같이 리턴 값과 메소드로 전달되는 값이 nil일 수 있는지, 항상 값을 가지는지를 선언할 수 있다.

@interface AAPLList : NSObject <NSCoding, NSCopying>
// ...
- (nullable AAPLListItem *)itemWithName:(nonnull NSString *)name;
- (NSInteger)indexOfItem:(nonnull AAPLListItem *)item;

@property (copy, nullable) NSString *name;
@property (copy, readonly, nonnull) NSArray * nonnull allItems;
// ...
@end

위 헤더 코드가 Swift 코드에서 사용될 때 아래와 같이 Swift에 제공되어 optional에 대응하는 코드를 작성할 수 있게 된다.

class AAPLList: NSObject, NSCoding, NSCopying {
  // ...
  func itemWithName(name: String) -> AAPLListItem?
  func indexOfItem(item: AAPLListItem) -> Int

  @NSCopying var name: String? { get set }
  @NSCopying var allItems: [AnyObject] { get }
  // ...
}

Swift에서뿐 아니라 Objective-C에서도 nonnull 인자에 nil을 전달하면 경고를 일으키기 때문에 더 안전한 코드가 된다.

[self.list itemWithName:nil]; // warning!
self.list.name = nil;         // okay

코드 블럭 전체의 기본값을 nonnull로 가정하는 블럭을 사용하면 더 깔끔해진다.

NS_ASSUME_NONNULL_BEGIN

@interface AAPLList : NSObject<NSCoding, NSCopying>
// ...
- (nullable AAPLListItem *)itemWithName:(NSString *)name;
- (NSInteger)indexOfItem:(AAPLListItem *)item;

@property (copy, nullable) NSString *name;
@property (copy, readonly) NSArray *allItems;
// ...
@end

NS_ASSUME_NONNULL_END

* C 코드에서는 _Nullable_Nonnull 키워드를 사용해야 한다. const 키워드를 사용하는 자리에 올 수 있다.

** Xcode 6.3에서 __nullable__nonnull로 소개되었지만, 외부 라이브러리와의 충돌을 피하기 위해 Xcode 7에서 _Nullable_Nonnull로 변경되었다.


  1. Language Guide: The Basics (The Swift Programming Language) ↩︎

  2. Nullability and Objective-C (Swift Blog) ↩︎