본문 바로가기

iOS 앱 개발자 프로젝트/Swift 문법 정복하기

[Swift] optional & nil

2024년 3월 8일 금요일

 

  • swift 기본 개념 중, optional과 nil에 대해 명확하게 이해하는 것이 오늘의 목표!
  • 빈 값은 컴퓨터에게는 있어야 할 것이 없다! 는 느낌이기 때문에 에러를 발생시키고 어플리케이션을 종료시키기도 합니다. 때문에 이렇게 모든 변수에 박스를 씌워놓고(optional) 저장을 하면 값이 있을 때와 없을 때 모두 안전하게 컴퓨터가 값을 읽을 수 있습니다.

1. 옵셔널과 nil

2. 옵셔널 바인딩

3. 옵셔널 강제 언래핑

4. 옵셔널 변수 값이 nil일 때를 위한 기본값 설정(nil-coalescing)

5. 옵셔널 체이닝


 

 

1. 옵셔널과 nil

 

1) 옵셔널

  • 값이 없을 수 있는 상황에서 옵셔널(Optional)을 사용한다.
  • 물음표 ? 로 나타낸다. (타입 어노테이션)
  • 아래의 두 가지 가능성이 있다.
    • 값이 있고 옵셔널로 래핑(wrapping)해놓은 값을 언래핑(unwrapping)하여 해당 값에 액세스할 수 있다.
    • 값이 전혀 없다.
  • 옵셔널 타입끼리의 연산은 불가능하다.
// 축약 타입 표현
var serverResponseCode: Int? = 404 
// 정식 타입 표현
var myPetName: Optional<String> = "멍멍이"

func pay(with card: String?) {
   // 구현 코드
}

// 옵셔널 타입끼리의 연산은 불가능
var num1: Int? = 4
var num2: Int? = 2

num1 + num2 // 에러 발생!

let optionalString1: String? = "Hello, "
let optionalString2: String? = "world!"

// 옵셔널 String 값들을 연결하려는 시도
let result = optionalString1 + optionalString2 // 에러 발생!

 

 

2) nil

  • 변수에 nil을 할당함으로써 값이 없는 상태의 옵셔널 프로퍼티를 만들 수 있다.
var serverResponseCode: Int? = 404
serverResponseCode = nil

var surveyAnswer: String?
// surveyAnswer 는 자동으로 nil 로 설정됩니다.

 

 


 

2. 옵셔널 바인딩

 

  • 옵셔널 값이 빈값인지 존재하는지 검사한 후, 존재하는 경우 그 값을 다른 변수에 대입시켜 바인딩하는 것

      ▷  이렇게 빈 값을 체크하고 옵셔널 값을 언래핑 해주는 것은 강제로 언래핑하는 것보다 훨씬 안전하다.

 

  • if let / if var, guard let / guard var을 써서 옵셔널 값을 추출해 새로운 변수에 바인딩한다.
  • if let  vs.  guard let 

      ▷  if let  :  if문의 코드 구현부 내에서만 상수 사용이 가능(지역 변수)

        guard let  :  guard문을 통과한 상수를 guard문 밖에서 사용이 가능(전역변수)

if let <#상수 이름#> = <#옵셔널 값#> {
   // 구현 코드
}


let roommateNumbers: Int? = nil
if let roommates = roommateNumbers {
    print (roommates)
}
// 출력값 없음

let ticketCounts: Int? = 3
if let ticket = ticketCounts {
    print (ticket)
}
// 출력값: 3


// 옵셔널 바인딩 할 변수가 여러 개인 경우
let boyName : String?
let girlName : String?

boyName = "하늘"
girlName = "나연"

// , 콤마로 나열합니다
if let boy = boyName,
   let girl = girlName {
    print(boy, girl)
}
// 출력값: 하늘 나연

let x : Int? = 10
let y : Int? = nil

func opbinding() {
    guard let x = x else { return }
    print(x)

    guard let y = y else { return } // y는 nil 이므로 여기서 return 
    print(y) // 위에서 return 하였기 때문에 이 코드 라인은 실행되지 않음
}

opbinding()
// 출력값: 10

 

 


 

 

3. 옵셔널 강제 언래핑

 

  • 강제 언래핑(Force unwrapping)은 !를 써서 강제로 옵셔널 추출한다.

        (변수 앞에 !를 붙이는 것은 not의 의미 - 헷갈리지 말것!)

 

  • 강제 언래핑을 잘못 사용할 경우 프로그램이 비정상 종료될 수도 있다. 
  • 따라서 반드시 nil이 아닌 것이 확실한 상황에서 사용할 것 (최대한 안쓰는게 좋다)
let number = Int("42")!
// String값을 Int로 변환하는 함수는 return값으로 옵셔널 값을 반환합니다.
print(number)
// 출력값: 42

// 강제 언래핑이 실패한 경우
let address: String? = nil
print(address!)
// 에러🚨 메시지: Unexpectedly found nil while unwrapping an Optional value

 

 


 

4. 옵셔널 변수의 값이 nil일 때를 위한 기본값 설정 (nil-coalescing)

 

  • 값이 nil일 경우를 위해 기본값을 설정할 수 있다.
    • ?? 을 사용하여 기본값을 사용할 수 있는데, ?? 을 사용하여 기본값을 부여한 변수는 옵셔널 타입이 아니다.
    • let(var) a = b ?? c 형태로 이루어지는데 b는 옵셔널 타입이고, b가 nil일 경우는 a에 c가 대입되고, nil이 아닐 경우에는 옵셔널을 제거한 값이 a에 대입
var optNumber: Int? = 3
let number = optNumber ?? 5
print(number) // 출력값 : 3
//number는 Int? 타입이 아니라 Int 타입

optNumber = nil
let number2 = optNumber ?? 5
print(number) // 출력값 : 5
//number는 Int? 타입이 아니라 Int 타입

print(heartPath)
// imagePaths["heart"]가 nil일 때 
// 출력값: "/images/default.png"

 

 


 

 

5. 옵셔널 체이닝

 

  • 옵셔널을 연쇄적으로 사용하는 것을 옵셔널 체이닝이라고 한다.
  • . 을 통해 내부 프로퍼티나 메서드에 연속적으로 접근할 때 옵셔널 값이 있으면 옵셔널 체이닝으로 접근할 수 있다.
struct Person {
	var name: String
	var address: Address
}

struct Address {
	var city: String
	var street: String
	var detail: String
}

let sam: Person? = Person(name: "Sam", address: Address(city: "서울", street: "신논현로", detail: "100"))
print(sam.address.city) // 에러 🚨. 에러 메시지: Chain the optional using '?' to access member 'address' only for non-'nil' base values
sam?.address.city  // ✅
// 출력값: 서울

 

 

  • 만약 sam이 옵셔널이 아닌 Person 타입이었다면 sam.address.city가 가능하지만 sam이 옵셔널 타입이기 때문에 address에 접근하려면 sam?.address.city 형태로 이루어져야 한다.
  • 위 코드 중 “sam?.address.city” 을 다시 살펴보면, sam == nil 일 경우 에러가 나지 않고 뒤 코드가 실행되지 않는다. 다른 언어의 null은 .으로 참조를 하면 바로 NullPointerException 에러가 발생한다. 따라서 Swift에서의 nil은 메모리의 주소값이 없음을 나타내는 null이 아니라 값이 없음을 나타내는 값(실제로는 뒤에서 배울 Enum)이다.