아요 개발 일기

[Swift] Optional ( 옵셔널 ) 본문

Swift/Grammar

[Swift] Optional ( 옵셔널 )

소진이 2022. 12. 30. 11:58

오늘은 옵셔널에 대해 알아보도록 하겠습니다!:-)

다른 C, Java에서 접해보지 못한 문법이라 조금 어렵더라구요!!

자 그럼 같이 이해해보러 갑시당 👊

 

Optional(옵셔널)

옵셔널이란, "nil을 담을 수 있는 타입" 입니다.

일반 데이터 타입은 nil 값을 담을 수 없으며, nil 값을 넣으면 오류가 발생합니다!

 

그럼, 왜 굳이 nil 값을 담을 수 있는 타입을 만들었으며, 어떤 상황에서 사용할까요?

크게는 아래와 같이 두가지 경우에 사용됩니다.

 

1. nil 값이 들어갈 가능성이있을 때

2. 정상적으로 값을 처리하지 못하는 상황이 발생했을 때, 오류를 발생시키지 않고 nil을 반환

: 문제가 생겨도 앱이 다운되지 않고 nil 값만 반환 한다는 뜻입니다! (안전함 👍)

그냥 0이나, 빈 값("")을 반환해도 되지 않나요? 라고 물어볼 수도 있는데,

정말 그 곳에 0이라는 값이나 String ""빈 값이 들어가 있을 수도 있기때문에, nil로 반환하도록 정의하였습니다.

 

 

옵셔널의 종류는 아래와 같이 두가지 입니다.

  • nil이 아닌 경우 : 옵셔널로 둘러쌓인 값
  • nil인 경우 : nil 값

 


옵셔널 타입 선언과 정의

자료형 뒤에 물음표 마크만 붙이면 옵셔널 타입 선언이 됩니다.

 

String? -> 옵셔널 String

Int? -> 옵셔널 Int  등등..

 

예시 코드를 함께 볼까요?

// 옵셔널 Int 
var optInt: Int?
optInt = 3

print("\(optInt)")  // Optional(3)

// 옵셔널 String
var optString: String?
optString = "Swfit"

print("\(optString)")  // Optional("Swfit")

// 옵셔널 Array 타입
var optArr: [String]?
optArr = ["C" ,"Java", "Object-C"] 

print("\(optArr)")  // Optional(["C", "Java", "Object-C"])

// 옵셔널 Dictionary 
var optDic: [String: Int]?
optDic = ["국어": 98, "수학": 88, "영어":96]

print("\(optDic)")  // Optional(["영어": 96, "국어": 98, "수학": 88])

// 값이 없는 옵셔널
var acterName: String?
print(acterName) // nil

 

그렇다면, 일반 일반 데이터 타입과 옵셔널 타입이 같은 타입일까요?

아닙니다. 둘은 서로 다른 타입입니다.

아래 예시 코드를 보면 String? 타입으로 선언된 값을 String 값으로 넣으려고 하니까 컴파일 오류가 발생합니다.

func printName(_ name: String) {
    print(name)
}

var myName: String? = nil

printName(myName)
// 전달되는 값의 타입이 다르기 때문에 컴파일 오류 발생

 

타입 변환 + 옵셔널 처리

타입을 변환과 옵셔널 처리를 같이 할 수도 있습니다.

아래 예시 코드와 같이 변경할 타입(값)으로 선언하면됩니다!

//타입 변환하면서 옵셔널 처리하기
let num = Int("123")
print(num) //Optional(123)

let num2 = Int("소진")
print(num2) // nil

let num3 = Double("235")
print(num3)  // Optional(235.0)

옵셔널 해제

옵셔널로 감싸져있는 값은 연산에 사용할 수 없습니다ㅜㅜ 그러면 감싸져있는 값을 해제해야겠죠???

 

옵셔널 해제 방법은 아래 사진을 참고합시다!

 

! : 강제 해제/언래핑(Unwrapping)

강제 언래핑(Unwrapping) 쉽게 말해서 변수 안에 값이 있든 말든 그냥 깨부시고 값을 가지고 오겠다는 것 입니다.

깨부셨더니 운 좋게 값이 있을 수도 없을 수도 있겠죠?

 

그래서 ! 사용은 조심해야 합니다.

스위프트 언어 가이드를 보면 아래와 같이 말합니다.

느낌표(!)를 사용하여 값이 존재하지 않는 옵셔널 값에 접근하려 시도하면 
런타임 에러가 발생합니다. 
느낌표를 사용하여 강제 언랩핑을 하기 전에는 
항상 옵셔널 값이 nil이 아니라는 것을 확실히 해야 합니다.

nil이 아니라는 것이 확실하지도 않은 상태에서 !를 남용하여 쓰게 되면 오류가 날 가능성이 높습니다.

 

// 강제 추출

func printName(_ name: String) {
    print(name)
}

var myName: String? = "yagom"

printName(myName!) //yagom

myName = nil

print(myName!)
// 강제 추출시 값이 없으므로 런타임 오류 발생

var yourName: String! = nil

printName(yourName)
//nil 값이 전달되기 때문에 런타임 오류 발생

 

 

옵셔널 바인딩 (Optional Binding)

비 강제 해제에 해당되는 옵셔널 바인딩 입니다!

 

if - let : 옵셔널 바인딩

  • 옵셔널의 값을 꺼내오는 방법 중 하나
  • nil 체크 + 안전한 값 추출
// if - let

func printName(_ name: String) {
    print(name)
}

var myName: String! = nil

if let name: String = myName {
    printName(name)
} else {
    print("myName == nil")
}

// name 상수는 if-let 구문 내에서만 사용가능합니다.
// 상수 사용범위를 벗어났기 때문에 컴파일 오류 발생
printName(name)


var myName: String? = "sojin"
var yourName: String? = nill

if let name = myName, let friend = yourName {
    print("\(nam) and \(friend)")
}
// yourName이 nil이기 때문에 실행되지 않습니다.

yourName = "hana"

if let name = myNae, let friend = yourName {
    print("\(name) and \(friend)")
}
//sojin and hana

 

guard let

특성상 함수(메서드)에서만 쓰이며, guard 구문의 조건을 만족하지 못하면

else문으로 빠져서 함수의 실행을 종료 시킬 때 사용함(return)

 

func printParasedInt(from: String) {
    guard let parsedInt = Int(from) else {
        print("Int로 컨버팅 안돼!")
        return
    }
    print(parsedInt)
}


printParaedInt(from: "100") //100
printParaedInt(from: "안녕안녕 반가워") //Int로 컴버팅 안돼!

 

어떤 상황에서 if-let이나 guard문을 사용할까요? 

if-let

- 상수, 변수를 구문 안에서만 사용할 경우

- 함수가 아닌 곳에서 옵셔널 바인딩을 사용해야하는 경우

 

guard 문

- 상수, 변수를 구문 외부에서도 사용할 경우 (guard문의 값 캡쳐 기능으로 값을 외부에서도 사용가능)

-   함수 내에서 사용할 경우 (if-let보다 복잡성이 낮아서, 함수 내에서는 guard을 사용하는 것이 좋음)


옵셔널 체이닝 (Optional Chaining)

옵셔널 체이닝은 옵셔널 요소 내부의 프로퍼티로, 또 다시 옵셔널이 연속적으로 연결되는 경우 유용하게 사용할 수 있습니다.

이해를 돕기위해 아래 예시 코드를 함께 봅시다!

class Person {
    var name: String
    var dog: String?
    var home: Apartment?
    
    init(name: String) {
        self.name = name
    }
}

class Apartment {
    var buildingNumber: String
    var roomNumber: String
    var `guard`: Person?
    var owner: Person?
    
    init(dong: String, ho: String) {
        buildingNumber = dong
        roomNumber = ho
    }
}

let sojin: Person? = Person(name: "sojin")
let apart: Apartment? = Apartment(dong: "101", ho: "202")
let minsu: Person? = Person(name: "minsu")
//옵셔널 체이닝이 실행 후 결과 값이 nil일 수 있으므로 결과 타입도 옵셔널

//만약 우리집의 경비원의 강아지가 궁금하다면..?
sojin?.home = apart
sojin?.home?.guard = minsu
sojin?.home?.guard?.name //minsu
sojin?.home?.guard?.dog = "말티즈"
                   
//옵셔널 체이닝을 사용하지 않는다면...
func guardDog(owner:Person?) {
    if let owner = owner {
        if let home = owner.home {
            if let `guard` = home.guard {
                if let guardDog = `guard`.dog {
                    print("우리집 경비원의 강아지는 \(guardDog) 입니다!")
                } else {
                    print("우리집 경비원은 강아지가 없어요.")
                }
            }
        }
    }
}

guardDog(owner: sojin) //우리집 경비원의 강아지는 말티즈 입니다!

//옵셔널 체이닝을 사용한다면
func guardDogWithOptionalChaining(owner: Person?) {
    if let guardDog = owner?.home?.guard?.dog {
        print("우리집 경비원의 강아지는 \(guardDog) 입니다!")
    } else {
        print("우리집 경비원은 강아지가 없어요.")
    }
}

guardDogWithOptionalChaining(owner: sojin) //우리집 경비원의 강아지는 말티즈 입니다!

경비원의 강아지를 알아보기 위해서는

owner -> home -> guard -> dog 순으로 변수를 탐색해야합니다. 

옵셔널 체이닝을 사용하지 않은 함수는 길고 알이보기도 힘들죠?

 

이때, 옵셔널 체이닝을 사용합니다.

사용법은 간단합니다! 그냥 ?만 붙여주면됩니다.

sojin?.home,guard?.dog 처럼요!

위의 옵셔널 체이닝을 사용한 코드는 굉장히 짧고 간결하며 보기 쉽죠???


옵셔널은 주로 Class, Protocol 에서 많이 사용한다고 합니다!

잘 사용하면 정말 편리한 친구인 것 같아요!! 저도 다른 문법을 공부하면서 더 다양하게 사용해보도록할게요!!

오늘 옵셔널 공부는 끝!