기본 (The Basics) - 옵셔널 (Optional)

00yhsp·2024년 4월 1일

변수 또는 상수에 값이 없을 수도 있을 경우 옵셔널을 사용한다.
옵셔널 값은 특정 타입의 값을 가지고 있을 수도 있고 nil 값을 가지고 있을 수도 있다.

누락될 수 있는 값의 예로는, Swift의 Int 타입은 String 값을 Int로 변환하는 초기화가 존재한다.
그러나 일부 문자열만 정수로 전환될 수 있다.

let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)
// The type of convertedNumber is "optional Int"

위의 코등에서 possibleNumber가 "Hello, World!"등의 Int로 변환 불가한 String일 수 있으므로 초기화가 실패할 수 있다.
그럴 경우 Int가 아닌 Optional Int를 반환한다.

옵셔널 타입을 작성하려면 옵셔널을 포함하는 타입의 이름 다음에 물음표(?)를 작성한다.
예를 들어, 옵셔널 Int의 타입은 Int?이다.
옵셔널 Int는 항상 어떠한 Int값 또는 값이 없음을 포함한다.
Bool 또는 String과 같은 다른 타입을 포함할 수 없다.

nil

옵셔널 변수는 특수 값 nil로 지정하여 값이 없는 상태를 나타낼 수 있다.

var serverResponseCode: Int? = 404
// serverResponseCode contains an actual Int value of 404
serverResponseCode = nil
// serverResponseCode now contains no value

기본값이 없이 옵셔널 변수를 정의하면 이 변수는 자동적으로 nil로 설정된다.

var surveyAnswer: String?
// surveyAnswer is automatically set to nil

if 구문은 옵셔널과 nil을 비교하여 옵셔널에 값이 포함되어있는지 확인할 수 있다.
== 연산자 또는 != 연산자로 비교를 수행할 수 있다.
옵셔널에 값이 있다면 nil과 "같지 않음"으로 간주된다.

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

if convertedNumber != nil {
    print("convertedNumber contains some integer value.")
}
// Prints "convertedNumber contains some integer value."

옵셔널이 아닌 상수 또는 변수에 nil을 사용할 수 없다.
코드에서 상수 또는 변수가 특정 조건에서 값이 없이 동작하려면 적절한 타입의 옵셔널 값을 선언해야 한다.
옵셔널이 아닌 값으로 선언된 사웃 또는 변수는 nil 값이 포함되지 않도록 보장된다.
옵셔널이 아닌 값에 nil을 할당하면 컴파일 에러가 발생한다.

옵셔널과 옵셔널이 아닌 값의 분리는 누락된 정보를 명시적으로 표시할 수 있고 없는 값을 처리하는 코드를 더 쉽게 작성할 수 있다는 데 의미가 있다.
이런 실수는 컴파일 시 오류가 발생하므로 옵셔널을 옵셔널이 아닌 것처럼 처리할 수 없다.
값을 언래핑한 후에는 nil에 대한 값을 검사할 필요가 없으므로 코드의 다른 부분에서 이 값을 동일하게 확인할 필요가 없다.

옵셔널 값에 접근할 때 코드에서 nil과 nil이 아닌 것에 대해 모두 처리해야 한다.

  • 값이 nil일 때 해당 값에 대한 동작을 건너뛴다
  • nil을 반환하거나 ?. 연산자를 사용해 nil값을 전파한다.
  • ?? 연산자를 사용해 대체값을 제공한다.
  • ! 연산자를 사용해 프로그램을 멈춘다.

Note: Objective-C에서 nil은 객체가 존재하지 않는 포인터이고, Swift에서 nil은 포인터가 아니라 단지 특정 타입의 값이 없다는 의미이다.
모든 타입의 옵셔널은 객체 타입 뿐만 아니라 nil로 설정할 수 있다.

옵셔널 바인딩 (Optional Binding)

옵셔널 바인딩은 옵셔널이 값을 포함하고 있는지 확인하고 값이 있는 경우 해당 값을 임시 상수 또는 변수로 사용할 수 있게 해준다.
옵셔널 바인딩은 if, guard와 while 구문에서 옵셔널에 값이 있는지 체크하고 단일 동작의 일부로 상수 또는 변수로 추출할 수 있다.

if 구문에서 옵셔널 바인딩은 아래와 같이 사용한다.

if let <#constantName#> = <#someOptional#> {
	<#statements#>
}

강제 언래핑보다 옵셔널 바인딩을 사용하여 옵셔널 섹션에 있는 예제의 possibleNumber를 다시 작성할 수 있다.

if let actualNumber = Int(possibleNumber) {
    print("The string \"\(possibleNumber)\" has an integer value of \(actualNumber)")
} else {
    print("The string \"\(possibleNumber)\" could not be converted to an integer")
}
// Prints "The string "123" has an integer value of 123"

변환에 성공하면 actualNumber 상수는 if 구문 첫번째 중괄호 내에서 사용할 수 있다.
옵셔널에서 포함된 값으로 초기화되었고 적절한 옵셔널이 아닌 타입이다.
이런 경우에 possibleNumber의 타입은 Int?이므로 actualNumber의 타입은 Int이다.

값에 접근한 후에 기존 옵셔널 상수 또는 변수를 참조할 필요가 없다면 같은 이름으로 새로운 상수 또는 변수를 사용할 수 있다.

let myNumber = Int(possibleNumber)
// Here, myNumber is an optional integer
if let myNumber = myNumber {
    // Here, myNumber is a non-optional integer
    print("My number is \(myNumber)")
}
// Prints "My number is 123"

이러한 코드는 일반적이기 때문에 옵셔널 값을 언래핑할 때 더 짦게 사용할 수 있다.

if let myNumber {
    print("My number is \(myNumber)")
}
// Prints "My number is 123"

옵셔널 바인딩은 상수와 변수 둘 다 사용이 가능하다.
if 구문 첫번째 중괄호에서 myNumber의 값을 변경하고 싶다면 if var myNumber로 대신 쓸 수 있으며,
옵셔널에 포함된 값은 상수가 아닌 변수로 사용할 수 있다,
if 구문 내에서 myNumber를 바꾸면 이것은 지역 변수에만 적용되며, 기존 언래핑된 옵셔널 상수 또는 변수에 적용되지 않는다.

필요한 경우 if 구문에 쉼표로 구분하여 옵셔널 바인딩 및 부울 조건을 여러개 포함할 수 있다.
옵셔널 바인딩 값 중 하나가 nil이거나 부울 조건이 false로 판단되면 전체 if 구문의 조건은 false로 간주된다.

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"

if 구문에서 옵셔널 바인딩으로 생성된 상수와 변수는 if 구문의 본문 내에서만 사용 가능하다.
반대로 guard 구문에서 생성된 상수와 변수는 guard 구문 다음에 코드에서 사용 가능하다.

대체값 제공 (Providing a Fallback Value)

누락된 값을 처리하는 다른 방법은 nil-결합 연산자(??)를 사용하여 기본값을 제공하는 방법이다.
옵셔널에서 ?? 의 왼편이 nil이 아니면 값은 언래핑되고 사용된다.
그렇지 않으면 ?? 오른편 값이 사용된다.

let name: String? = nil
let greeting = "Hello, " + (name ?? "friend") + "!"
print(greeting)
// Prints "Hello, friend!"

강제 언래핑 (Force Unwrapping)

프로그래머의 에러 또는 원치 않는 상태와 같은 실패를 nil로 표현하려면 옵셔널의 이름 뒤에 느낌표(!)를 추가하여 접근할 수 있다.
이것을 옵셔널 값의 강제 언래핑이라고 한다.
nil이 아닌 값에 강제 언래핑을 하면 언래핑된 값을 결과로 얻고, nil 값을 강제 언래핑하면 런타임 에러가 발생한다.
!은 fatalError(_:file:line:)의 짧은 작성법이다.
예를 들어, 아래 코드는 동일한 접근을 보여 준다.

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

let number = convertedNumber!

guard let number = convertedNumber else {
    fatalError("The number was invalid")
}

위 코드의 두 버전은 값을 항상 가지는 convertedNumber에 의존하는 것을 보여준다.
위의 접근 방식 중 하나를 사용해서 코드의 일부로 작성하면 런타임 시 요구사항이 참인지 확인할 수 있다.

암시적으로 언래핑된 옵셔널 (Implicitly Unwrapped Optionals)

위에서 설명했듯이 옵셔널은 상수 또는 변수가 "값이 없음"을 허락하고 알려준다.
옵셔널은 값이 존재하는지 if 구문에서 체크할 수 있고 값이 존재한다면 옵셔널 값에 접근하기 위해 옵셔널 바인딩을 이용할 수 있다.

때로는 프로그램 구조에서 옵셔널이 값을 처음 설정한 후 항상 값을 갖는 것이 분명할 수 있다.
이러한 경우 항상 값이 있다고 가정할 수 있으므로 접근할 때다 옵셔널의 값을 확인하고 언래핑할 필요가 없다.

이러한 옵셔널은 암시적으로 언래핑된 옵셔널(implicitly unwrapped optionals)로 정의된다.
옵셔널을 만들기 위해 타입 뒤에 물음표를 작성하는 대신 느낌표로 암시적으로 언래핑된 옵셔널을 작성한다.
사용할 때 옵셔널 이름 뒤에 느낌표를 위치시키는 것보다 선언할 때 옵셔널 타입 뒤에 느낌표를 위치시키는 것이 더 좋다.

암시적으로 언래핑 된 옵셔널은 옵셔널이 처음 정의된 직후 옵셔널의 값이 존재하는 것으로 확인되고 그 이후 모든 시점에 존재한다고 가정할 수 있는 경우 유용하다.
Swift에서 암시적으로 언래핑된 옵셔널은 클래스 초기화 중에 주로 사용한다.

나중에 변수가 nil이 될 가능성이 있다면 암시적으로 언래핑된 옵셔널을 사용하면 안 된다.
변수를 사용하는 동안 nil값을 확인해야 한다면 일반적인 옵셔널 타입을 사용해야 한다.

암시적으로 언래핑 된 옵셔널은 내부적으로 옵셔널이지만 옵셔널에 접근할 때마다 옵셔널 값을 풀 필요 없이 옵셔널이 아닌 값처럼 사용할 수 있다.

let possibleString: String? = "An optional string."
let forcedString: String = possibleString! // Requires explicit unwrapping

let assumedString: String! = "An implicitly unwrapped optional string."
let implicitString: String = assumedString // Unwrapped automatically

필요한 경우 암시적으로 언래핑된 옵셔널은 강제로 언래핑을 허락하는 것으로 생각할 수 있다.
암시적으로 언래핑 된 옵셔널을 사용할 때 Swift는 처음에 기존의 옵셔널 값으로 사용하려고 하고 사용이 불가능할 경우 강제로 언래핑한다.
위의 코드에서 옵셔널 값 assumedString은 implicitString이 명시적으로 옵셔널이 아닌 String 타입이기 때문에 값을 할당하기 전 강제로 언래핑된다.
아래의 코드에서 optionalString은 명시적 타입이 없으므로 기본적으로 옵셔널이다.

let optionalString = assumedString
// The type of optionalString is "String?" and assumedString isn't force-unwrapped.

암시적으로 언래핑 된 옵셔널이 nil이고 래핑된 값에 접근하려고 하면 런타임 에러가 발생한다.
결과는 값을 포함하지 않는 일반적인 옵셔널을 강제 언래핑하기 위해 느낌표를 작성한 것과 같다.

암시적으로 언래핑된 옵셔널은 일반 옵셔널과 같은 방법으로 nil 체크를 할 수 있다.

if assumedString != nil {
    print(assumedString!)
}
// Prints "An implicitly unwrapped optional string."

옵셔널 바인딩과 함께 암시적으로 언래핑 된 옵셔널은 단일 구문으로 값을 확인하고 언래핑할 수 있다.

if let definiteString = assumedString {
    print(definiteString)
}
// Prints "An implicitly unwrapped optional string."
profile
iOS Dev

0개의 댓글