Swift는 모든 변수가 값을 반드시 가지도록 강제합니다. 변수를 선언만하고 초기화를 하지 않으면 컴파일 단계에서 오류를 발생시키죠. 하지만 때로는 ‘값이 없는 상태’를 표현해야 할 때가 있는데, 이를 위해 Swift는 Optional이라는 기능을 제공합니다.

  • 정의
    swift의 Optional은 다음과 같이 선언되어 있습니다. 보다시피 단순한 enum 형태입니다.

      public enum Optional<Wrapped>: ExpressibleByNilLiteral { // nil 리터럴로 초기화 할 수 있게 해주는 프로토콜. Optional 이외에는 사용되지 않습니다.  
      case none // 값의 부재, nil
      case some(Wrapped) // 값이 존재함
      }
    

    Swift에서는 이를 간편하게 사용하게 하기위한 추가 문법을 제공합니다. 아래 두 구문은 같은 의미를 가집니다.

      let optionalInt: Int? 
      let optionalInt: Optional<Int> = nil
    

    Objective-C나 C에서는 optional 개념이 존재하지 않습니다. Objective-C에서는 nil이 존재하긴 하지만, 이는 ‘존재하지 않는 객체에 대한 포인터’를 의미합니다. 따라서 객체 타입에만 적용할 수 있고, 구조체, 열거형, 기본 타입에는 적용할 수 없었습니다. nil을 적용할 수 없는 타입에는 NSNotFound 등의 특수한 상수 값을 사용했는데, 이는 메소드를 사용할 때 이런 특수한 상수 값의 존재와 그 용도를 알고 있어야 한다는 전제 조건이 필요합니다. 하지만 Swift의 Optional은 모든 타입에 적용할 수 있기 때문에 이런 특수한 상수 값이 필요없습니다.

    Optional은 nil 혹은 특정 값으로 초기화 할 수 있습니다. 값으로 초기화할 때는 Optional을 적용하지 않을 때와 동일하게 사용하면 됩니다. 초기화를 하지 않았을 때의 기본 값은 nil이 됩니다. 리터럴로 초기화 할 때는 타입 추론이 불가능 하기 때문에 반드시 타입을 명시해주도록 합니다.

      let optionalString: String? = "Hello, World!"
      let nilString: String? = nil
    
  • Optional 해제하기
    Optional이 적용된 타입은 한 단계 감싸져 있기 때문에 값을 사용하기 위해서는 이를 벗겨내야 합니다. Swift에서는 Optional을 벗겨내기 위한 몇가지 방법을 제공합니다.

    1. 강제 언래핑 : Optional 변수 뒤에 !를 붙여주면 Optional이 가진 값을 직접적으로 참조할 수 있습니다. 하지만 이 때 Optional이 nil이리면, 런타임 에러가 발생합니다. 때문에 반드시 nil여부를 체크한 뒤에 언래핑해야 합니다.

         if optionalValue != nil {
             print(optionalValue!)
         }
      
    2. Optional 바인딩 : Optional 변수에 값이 있는지 체크한 후, 존재하면 이를 다른 변수를 통해 Optional이 해제된 값을 사용하도록 하는 방식입니다. if, while, guard 등을 통해 적용할 수 있습니다. 이 들은 모두 조건문으로, ‘만약 Optional이 가진 값을 다른 변수에 대입할수 있다면’ 정도의 뜻을 가집니다. 참고로 이 구문은 우측값이 Optional이 아니면 사용할 수 없습니다.

         if let actualValue = optionalValue {
             //  actualValue는 이 중괄호 내에서만 유효합니다.
         }
      
         while let actualValue = optionalValue {
             // actualValue는 이 중괄호 내에서만 유효합니다.
         }
      
         guard let actualValue = optionalValue else { // actualValue는 현재 스코프 내에서 유효합니다.
             return
         }
      

    하나의 조건문에는 여러개의 Optional 바인딩과 Boolean 조건문을 자유롭게 나열할 수 있습니다. 아래 두 조건문은 동일한 의미를 가집니다.

      if let firstNumber = Int("4"), let secondNumber = Int("42"), firstNumber < secondNumber && secondNumber < 100 {
      print("\(firstNumber) < \(secondNumber) < 100")
      }  
      // Prints "4 < 42 < 100"
    
      if let firstNumber = Int("4") {
          if let secondNumber = Int("42") {
              if firstNumber < secondNumber && secondNumber < 100 {
                  print("\(firstNumber) < \(secondNumber) < 100")
              }
          }
      }
      // Prints "4 < 42 < 100"
    
  • 암시적 해제 Optional
    안전하지 않은 Optional 사용은 런타임 에러를 발생시키기 때문에 반드시 체크되어야만 합니다. 하지만 매번 Optional을 체크하는 것은 번거롭고, 코드 분량을 뻥튀기하는 주범이기도 합니다. 만약에 Optional 변수가 항상 값을 가지고 있다는 것이 명확하다면, 굳이 매번 해제하는 수고를 들이지 않아도 되지 않을까요?
    Swift는 이러한 요구를 수용하기 위해 ‘암시적 해제 Optional’을 제공합니다. 암시적 해제 Optional은 값이 무조건 존재한다고 가정하기에 마치 Optional을 적용하지 않은 것 처럼 사용할 수 있습니다. 하지만 그렇게 사용했을 때, 값이 존재하지 않는다면 런타임 에러를 발생시킵니다.

    let possibleString: String? = "An optional string."
    let forcedString: String = possibleString! // 언래핑이 필요하다.
    
    let assumedString: String! = "An implicitly unwrapped optional string."
    let implicitString: String = assumedString // 명시적인 언래핑이 필요하지 않다.
    

Optional의 개념은 Swift에서 중요하게 사용되기 때문에, 그 개념과 사용법에 대해서 반드시 숙지하여야 합니다.