옵셔널(Optional)

zeStars Team·2021년 5월 27일
0

Swift_Study

목록 보기
4/5

옵셔널(Optional)

개념

프로그램의 안전성을 높이기 위해 사용하는 개념

nil(Null, None, NaN)을 사용할 수 있는 타입사용할 수 없는 타입을 구분하고 사용할 수 있는 타입을 옵셔널타입(Optional Type)이라고 부름

nil의 배경

오류가 발생하는 상황에서 언어의 안정성을 위해 오류를 가급적 발생시키지 않고, 억지로 값을 반환하기 위해서 노력하며 등장

그 반환값을 처리하기 위하여 등장한 값이 nil이며 값이 없음을 의미함

nil? : 실제로 아무런 값이 없음을 의미하는 특수한 값 -> Int의 0, String의 "" 과는 다름

let zestars = ["first": 1, "second": 2, "thrid": 3]
zestars["fourth"] // nil	
let num = Int("1") // Optional(1)
let num = Int("zestars") // nil

일반자료형들은 nil을 가질 수 없으며, nil을 억지로 대입하려고 하면 오류가 발생함

var zestars: Int = nil // 에러발생

모든 값이 nil을 반환할 수 있는 것은 아니며, 옵셔널 타입으로 정의되어야만 nil을 반환할 수 있음

또한 옵셔널타입으로 반환된 값을 보면 실제값이 Optional로 싸여있는데, 이를 옵셔널 래핑(Optional Wrapping)이라고 부름

옵셔널래핑이 되어있는 값을 일반 자료형으로 사용하기 위해서는 옵셔널 언래핑(Optional Unwrapping)을 진행해야하며, nil값이 반환되었을때는 언래핑이 불가능함

그러면 모든 경우에 오류를 방지하기 위해 모든 값을 옵셔널로 정의하면 되지 않을까?

모든 경우에서 값을 사용할 때 마다 nil인지 아닌지에 대한 체크가 필요하여 로직의 복잡성과 처리과정의 낭비가 발생함

꼭 필요한 경우에만 제한적으로 옵셔널 타입을 활용하는 것이 적당하다.

옵셔널 타입의 선언과 정의

옵셔널 타입의 선언은 일반 자료형의 마지막에 ?(물음표)만 붙여주면 됨 (아주 간단)

이렇게 선언된 옵셔널 타입의 자료형은 nil값이 대입될 수 있으며 초기화를 따로 하지 않아도 자동으로 nil값으로 초기화됨

옵셔널 타입 자료형에 값을 할당해주는 것은 일반 자료형에 값을 할당하는 방법과 완전히 일치함

// 옵셔널 Int 타입
var optionalInt: Int?
optionalInt = 1
// 옵셔널 String 타입
var optionalString: String?
optionalString = "zestars"
// 옵셔널 Float 타입
var optionalFloat: Float?
optionalFloat = 3.14159
// 옵셔널 Array 타입
var optionalArray: [Int]?
optionalArray = [1,2,3,4,5,6]
// 옵셔널 Dictionary 타입
var optionalDictionary1: [String:String]?
optionalDictionary1 = ["a":"1","b":"2","c":"3"]
var optionalDictionary2: Dictionary<String, Int>?
optionalDictionary2 = ["a":1,"b":2,"c":3]
// 옵셔널 Class 타입
var optionalClass: AnyObject?

옵셔널 값의 처리

옵셔널 타입의 결과값은 연산이나 결합이 불가능한 타입임

옵셔널값 끼리나, 옵셔널값과 일반 자료형간의 연산이나 결합 모두 불가능함

1 + 2 // 3 연산이 가능  
Int("1") + Int("2") // Optional(1)과 Optional(2) 연산이 불가능
Int("1") + 2 // Optional(1)과 2 연산이 불가능

옵셔널 값을 활용하는 방법은 앞서 말한 옵셔널 언래핑(Optional Unwrapping)을 이용하여 일반자료형으로 활용이 가능함

옵셔널 언래핑의 방식은 크게 명시적 언래핑묵시적 언래핑으로 구분이 가능함

명시적 언래핑은 강제적 언래핑비강제적 언래핑으로 구분이됨

묵시적 언래핑은 컴파일러에 의한 자동 언래핑과, 연산자를 이용한 자동 언래핑으로 구분이됨

옵셔널 강제적 언래핑(Forced-Unwrapping)

방법 : 옵셔널 타입의 값 뒤에 ! (느낌표) 연산자만 붙여주면 됨.

강제적 언래핑 된 값은 일반 자료형이 되기 때문에 서로 연산이 가능함

// 옵셔널 Int 타입
var optionalInt: Int? = 1
print(optionalInt) // Optional(1)
print(optionalInt!) // 1

optionalInt + 2 // 연산 불가능
optionalInt! + 2 // 3
optionalInt! + optionalInt! // 2

한계점 : nil인 값에 ! 연산자를 붙이면 컴파일러 오류가 발생함 옵셔널을 사용하는 의미가 사라지게 됨

! 연산자를 안전하게 사용하기 위해서는 해당 옵셔널 값이 nil값인지를 점검하고, nil이 아닌 경우에만 ! 연산자를 활용

var optInt = Int(str)
// 같지 않다를 의미하는 != 연산자를 활용할 때는 ! 연산자와 구분을 위해 반드시 띄어쓰기를 해야함
if optInt != nil { 
  print(optInt!)
} else {
  print("nil값입니다.")
}

옵셔널 비강제적 언래핑 (옵셔널 바인딩 / Optional Binding)

방법 : 조건문내에서 조건식 대신 옵셔널 값을 일반 변수나 상수에 할당하는 구분을 사용

반드시 조건문에서 사용해야 하며, 상수/변수에 옵셔널 값을 대입한 결과는 T/F로 구분됨

조건문 if를 활용한 방식과, 조건문 guard를 활용한 방식이 있음

var str = "zestar"

// if를 활용한 방식
// 옵셔널 값의 처리 결과에 따라 서로 다른 피드백을 주고싶을 때 활용
if let intFromStr = Int(str) {
  print(intFromStr)
} else {
  print("언래핑 실패")
}

// guard는 특성상 함수나 메소드에서만 사용이 가능하기 때문에 함수 정의
func intStr(str: String) {
  // guard를 활용한 방식
  // guard는 조건에 맞지 않으면 함수의 실행이 종료됨
  // 옵셔널 값이 언래핑되지 않으면 함수의 진행이 불가능할 정도의 경우에 활용 
  guard let intFromStr = Int(str) else {
    print("언래핑 실패")
    return
  }
  print(intFromStr)
}

옵셔널 타입은 절대 nil값이 들어가지 않는다는 보장이 있으면 ! 연산자로 언래핑하는 것이 가장 효율적임

하지만 절대라는 보장이 없기 때문에 비강제적 언래핑이 필요함

var capital = ["1":"a", "2":"b", "3":"c"]

// 딕셔너리는 해당 키에 대한 값이 있는지 없는지에 대해 확인을 해여하기 때문에 키값에 대응되는 밸류값은 옵셔널로 표출됨
for i in 1...4 {
  print(capital["\(i)"])
  // Optional("a") Optional("b") Optional("c") nil
}

// ! 연산자를 활용하여 강제적 언래핑을 한 경우 (nil에 ! 연산자가 사용되어 에러발생)
for i in 1...4 {
  print(capital["\(i)"]!)
  // a b c error: Unexpectedly found nil while unwrapping an Optional value
}

for i in 1...4 {
    if let z = capital["\(i)"] {
        print(z)
    } else {
        print("nil값발생")
    }
  	// a b c nil값발생
}

컴파일러에 의한 옵셔널 자동 해제

옵셔널 타입으로 감싼 변수나 상수는 그 값을 사용하기 위해 옵셔널 객체를 해제해야 함

옵셔널 객체의 값을 비교연산자를 사용하여 비교하는 경우는 자동으로 옵셔널 객체를 해제하여 비교함

방법 : 옵셔널 객체의 값을 비교연산자를 이용해 비교

let zestars = Int("111")

print(zestars) // Optional(111)

zestars == 111				// true
zestars == Optional(111)    // true
zestars! == 111				// true
zestars! == Optional(111)   // true

옵셔널의 묵시적 해제 (Implicitly Unwrapped Optional)

방법 : 변수 선언 시 ? 연산자 대신에 ! 연산자를 붙임

옵셔널 타입이기는 하지만 값을 사용할 때는 자동으로 옵셔널을 해제되는 개념

선언할때 묵시적 해제를 미리 선언해주어야만 사용할 수 있음

var content: String? = "Zestars Swift"
print(content) // Optional("Zestars Swift")

// 옵셔널의 묵시적 해제
var content: String! = "Zestars Swift"
print(content) // Zestars Swift

// 묵시적 해제가 가능한 자료형은 당연하게도 nil값이 대응 가능함
var content: String! = nil

// 묵시적 해제가 가능하도록 선언됨 자료형은 일반 자료형 처럼 사용해도 됨
var number: Int! = 1
print(number + 1) // 2

일반 타입과 같이 사용할 수 있기 때문에 편리한 사용이 가능함

하지만 변수의 값이 nil이 될 가능성이 있으면 옵셔널의 묵시적 해제를 사용하지 않아야함.

즉, 형식상 옵셔널로 정의 해야하지만 절대 nil값이 대입되지 않을 변수일 경우 옵셔널의 묵시적 해제를 사용함

var number: Int! = Int("111")

사실 위의 경우에서도 Int("111")! 로 옵셔널 강제해제를 시켜 사용해도 됨.

실제로 옵셔널의 묵시적 해제를 유용하게 사용하는 경우는 ClassStruct 내에서 맴버변수를 정의할 때 선언과 초기화를 분리시켜야하는 경우이다.(내용보강)

마무리...

필요할 때만 값의 nil 여부를 점검하여 nil이 아닌 경우에만 처리할 수 있게 분기처리를 할 수 있기 때문에 옵셔널이라는 개념이 필요없다고 생각이 될 때가 있음 (없다고 코딩이 불가능 하지 않음)

그럼에도 불구하고 옵셔널의 강점은 안전성과 코드의 간결성을 보장해 준다고 한다.

옵셔널이 없는 Obj-C 코드

if (myDelegate != nil) {
    if ([myDelegate respondsToSelector:@selector(scrollViewDidScroll:)]) {
        [myDelegate scrollViewDidScroll:myScrollView];
    }
}

옵셔널이 있는 Swift 코드 (옵셔널 체이닝)

myDelegate?.scrollViewDidScroll?(myScrollView)
profile
zeStars 개발 블로그입니다

0개의 댓글