10. 기본(The Basic) - 옵셔널과 nil(Optionals & nil)

이경은·2024년 1월 5일
0

요약

  1. 옵셔널(Optional)
    • Swift에서 상수 또는 변수에 값이 없는 경우를 다루기 위해 필요한 타입 중 하나.
    • 형태 : 옵셔널Int -> Int?, 옵셔널String -> String?

  2. nil
    • 옵셔널 변수에서 값이 없음을 표현하는 값.

  3. 옵셔널 바인딩(Optional Binding)
    • if, guard, while구문으로 옵셔널 타입이 값을 가지고 있는지 확인하고, 값이 있다면 임시 상수 또는 변수로 사용할 수 있게 해주는 것.
    if let <#변수이름#> = <#옵셔널변수#>{
    	<#옵셔널에 값이 있을 때 실행#>
    }

  4. 대체값 제공(Providing a Fallback Value)
    • 옵셔널 타입이 nil일 때, ??를 사용해 nil을 대체할 값을 제공하는 방법.
    let name: String? = nil
    let greeting = "Hello, " + (name ?? "friend") + "!"

  5. 강제 언래핑(Force Unwrapping)
    • 옵셔널을 !를 사용해 강제로 해제하는 방법.
    let optionalNumber = Int("123")	// optional(123)
    let number = optionalNumber!		// 123

  6. 암시적으로 언래핑된 옵셔널(Implicitly Unwrapped Optionals)
    • 변수가 초기화 후 변수가 nil이 될 가능성이 없는 경우 사용하는 구문.
    • 강제 언래핑처럼 !를 사용하지만, 변수를 사용할 때가 아니라 선언할 때 사용한다.
    let assumedString: String! = "An implicitly unwrapped optional string"	// 변수를 선언할 때, !를 사용
    let implicitString: String = assumedString	// 변수를 불러올 때, 자동으로 언래핑된다.



옵셔널(Optional)

Swift에서는 값이 없는 경우에 옵셔널(Optional)을 사용합니다. 옵셔널은 2가지 가능성이 있습니다. 1; 지정된 타입의 값이 있고 옵셔널을 풀어서 값에 접근하거나, 2; 값이 없을 수도 있습니다.

값이 없을 수 있는 예시로 Swift의 Int타입은 String값을 Int값으로 변환하는 초기화가 존재합니다. 그러나 일부 문자열만 정수로 변환할 수 있습니다. 문자열 "123"은 숫자값 123으로 변환될 수 있지만, 문자열 "Hello, world"는 변환할 숫자값이 없습니다.

아래 예제는 StringInt로 초기화하는 것을 보여줍니다.

let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)
// 상수 convertedNumber의 타입은 "optional Int"입니다.

위의 코드에서 초기화가 실패할 수 있으므로 Int가 아닌 옵셔널Int를 반환합니다. 옵셔널 타입을 작성하려면 옵셔널을 포함하는 타입의 이름 다음에 물음표(?)를 작성합니다. 예를 들어, 옵셔널Int의 타입은 Int?입니다. 옵셔널 Int는 항상 어떠한 Int값 또는 값이 없음을 뜻합니다. Int?에는 Bool 또는 String값처럼 다른 타입들은 포함할 수 없습니다.




nil

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

var serverResponseCode: Int? = 404
// serverResponseCode는 Int 값을 가지고 있으며, 그 값은 404이다.

serverResponseCode = nil
// serverResponseCode는 값을 가지고 있지 않다.



변수를 선언할 때, 초기화 값을 할당하지 않으면 nil값으로 초기화 됩니다.

var survayAnswer: String?
// survayAnser는 자동으로 nil로 지정되었습니다.

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

옵셔널에 값이 있다면 nil과 "같지않음"으로 간주됩니다.

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

if convertedNumber != nil {
	print("convertedNumber contains some integer value")
}
// 출력값 : "convertedNumber contains some integer value"
// convertedNumber는 123이라는 Int 값을 가지고 있기 때문에 "nil과 같지 않음"이 참이 되므로 print구문이 실행됩니다.

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

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

옵셔널 값에 접근할 때 코드에서 nilnil이 아닌 것에 대해 모두 처리해야 합니다. 다음 섹션에서 설명하는 것처럼 값이 없을 때 수행할 수 있는 작업이 있습니다.

  • 값이 nil일 때 해당값에 대한 동작을 건너뜁니다.
  • nil을 반환하거나 옵셔널 체이닝(Optional Chaning)에서 설명한?.연산자를 사용해서 nil값을 전파합니다.
  • ??연산자를 사용해서 대체값을 제공합니다.
  • !연산자를 사용해서 프로그램을 멈춥니다.

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




옵셔널 바인딩(Optional Binding)

옵셔널 바인딩(Optional Binding)은 옵셔널이 값을 포함하고 있는지 확인하고 값이 있는 경우 해당 값을 임시 상수 또는 변수로 사용할 수 있게 해줍니다. 옵셔널 바인딩은 if, guardwhile 구문에서 옵셔널에 값이 있는지 체크하고 단일 동작의 일부로 상수 또는 변수로 추출할 수 있습니다. if, guardwhile구문은 제어 흐름(Control Flow)에서 자세히 다룹니다.

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

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

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

let possibleNumber = "123"

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")
}
// 출력값 : "The string "123" has an integer value of 123"
// possibleNumber는 nil이 아니므로 if 구문이 참일때의 코드를 실행하므로 출력값은 위와 같습니다.

위 코드는 아래와 같이 읽을 수 있습니다.

"Int(possibleNumber)에 의해 반환된 옵셔널Int에 값이 포함되어 있으면 actualNumber라는 새로운 상수에 옵셔널에 포함된 값을 설정합니다."

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

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

let possibleNumber = "123"
let myNumber = Int(possibleNumber)		// myNumber는 type conversion되면서 옵셔널 Int로 초기화되었습니다.

// 오른쪽 항의 myNumber가 값을 가지고 있다면(!= nil), 왼쪽항의 myNumber는 옵셔널이 해제된 값을 가집니다.
if let myNumber = myNumber {
	print("My number is \(mynumber)")
}
// 출력값 : "My number is 123"

이 코드는 이전 예제에서의 코드와 마찬가지로 myNumber가 값이 있는지 없는지를 먼저 확인합니다. myNumber가 값이 있다면 해당 값이 새로운 myNumber 라는 상수에 설정됩니다. if구문 안에서 myNumber는 새로운 옵셔널이 아닌 상수를 참조합니다. if구문 밖에서(앞 또는 뒤에서) myNumber를 작성하면 원래의 Int?myNumber를 참조합니다.

이러한 코드는 일반적이기 때문에 옵셔널 값을 언래핑하는데 더 짧게 사용할 수 있습니다. 언래핑할 상수 또는 변수의 이름만 작성합니다. 언래핑된 상수 또는 변수는 암시적으로 같은 이름의 옵셔널 값을 사용합니다.

// 중복되는 상수의 이름을 사용하여도 좋습니다. 단 여기에서 myNumber는 옵셔널이 해제된 myNumber를 말합니다.
if let myNumber {
	print("My number is \(myNumber)")
}
// 출력값 : "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")
}
// 출력값 : "4 < 42 < 100"

if let firstNumber = Int("4") {
	if let secondNumber = Int("42") {
    	if firstNumber < secondNumber && secondNumber < 100 {
        	print("\(firstNumber) < \(secondNumber) < 100")
        }
    }
}
// 출력값 : "4 < 42 < 100"

if구문에서 옵셔널 바인딩으로 생성된 상수와 변수는 if구문의 본문 내에서만 사용가능합니다. 반대로 guard구문에서 생성된 상수와 변수는 이른종료(Early Exit)에서 설명되어 있는 것처럼 guard구문 다음에 코드에서 사용가능합니다.




대체값 제공(Providing a Fallback Value)

누락된 값을 처리하는 다른 방법은 nil-결합 연산자(??)를 사용하여 기본값을 제공하는 방법입니다. 옵셔널에서 ??의 왼편이 nil이 아니면, 값은 언래핑되고 사용할 수 있습니다. ??의 왼편이 nil이라면, ??의 오른편의 값이 대신 사용됩니다. 아래 코드는 이름이 지정되면 지정된 이름에 인사하고 이름이 nil이라면 일반적인 인사를 사용합니다.

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

대체값 제공을 하는?? 사용에 대한 더 자세한 내용은 Nil-결합 연산자(Nil-Coalescing Operator)를 참고바랍니다.




강제 언래핑(Force Unwrapping)

프로그래머의 에러 또는 원치않는 상태와 같은 실패를 nil로 표현하려면 옵셔널의 이름 뒤에 느낌표(!)를 추가하여 접근할 수 있습니다.이것을 옵셔널의 값의 강제 언래핑(Force Unwrapping)이라고 합니다. 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)로 정의됩니다. 옵셔널을 만들기 위해 타입뒤에 물음표(String?)를 작성하는 대신에 느낌표(String!)로 암시적으로 언래핑 된 옵셔널을 작성합니다. 사용할 때보다 선언할 때 옵셔널 타입 뒤에 느낌표를 위치시키는 것이 더 좋습니다.

암시적으로 언래핑 된 옵셔널은 옵셔널이 처음 정위된 직후에 옵셔널의 값이 존재하는 것으로 확인되고 그 이후 모든 시점에 존재한다고 가정할 수 있는 경우에 유용합니다. Swift에서 암시적으로 언래핑 된 옵셔널은 미소유 참조와 암시적으로 언래핑 된 옵셔널 프로퍼티(Unowned References and Implicitly Unwrapped Optional Properties)에서 설명한 대로 클래스 초기화 중에 주로 사용합니다.

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

암시적으로 언래핑 된 옵셔널은 내부적으로 옵셔널이지만, 옵셔널에 접근할 때마다 옵셔널 값을 풀 필요없이 옵셔널이 아닌 값처럼 사용할 수도 있습니다. 다음 예제는 명시적 String으로서 랩핑된 값에 접근할 때 옵셔널 문자열과 암시적으로 언래핑 된 옵셔널 문자열의 동작차이를 보여줍니다.

let possibleString: String? = "An optional string."
let forcedString: String = possibleString!	// 암시적으로 옵셔널을 해제해주어야 한다.

let assumedString: String! = "An implicitly unwrapped optional string"
let implicitString: String = assumedString	// 자동으로 언래핑된다.

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

let optionalString = assumedString
// optionalString의 타입은 String?입니다. 그리고 assumedString은 강제로 언래핑되지 않습니다.

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

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

if assumedString != nil {
	print(assumedString!)
}
// 출력값 : "An implicitly unwrapped optional string"

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

if let definiteString = assumedString {
	print(definiteString)
}
// 출력값 : "An implicitly unwrapped optional string"

0개의 댓글