ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Swift) Optional
    Swift 2020. 11. 11. 04:22

    optional은 무엇인가?

    간단히 말해서 optional은 '값이 없음'을 나타냅니다.

     

    들어가기 전, 기존에 C의 null이나 object-C의 nil과 Swift의 nil은 다릅니다.

    특히 object-C의 nil은 '존재하지 않는 객체에 대한 포인터'지만 swift의 nil은 포인터가 아닌 값이 없음을 뜻하는 하나의 키워드입니다. 따라서 참조, 값 형식에 구애받지 않고 사용 가능합니다.

     

    우리가 만든 변수 혹은 상수를 사용하기 위해선 반드시 값을 할당 시켜주어야 합니다.  (물론 선언만 하는 것은 가능하지만 접근은 불가능합니다.)

    초기화를 통해 swift는 변수와 상수의 자료형을 형식 추론합니다.

     

    swift는 언어 특성상 변수 혹은 상수에 값을 할당해 형식 추론(Type Inference)을 통해 자료형을 자동으로 지정하거나 타입을 명시해줌으로써 자료형을 결정하는 Type Annotation이 존재합니다.

     

    let num  // error
    
    let num1 = 1 
    let num2: Int 
    
    print(num1) // 1
    print(num2) // error

     

    num1 상수는 값을 초기화시키고 swift의 type inference를 통해 Int 자료형으로 선언이 됩니다.

    num2는 값이 정해져 있지 않은 Int형식의 상수입니다. 값 없이 선언은 가능하지만, 접근하는 순간 에러가 발생합니다.

     

    그렇다면 값이 존재하지 않는 상수/변수를 사용하고 싶다면?

    그것이 바로 optional입니다.

     

    let optionalNum: Int? = nil
    
    print(optionalNum) // nil

     

    Optional 

     

    잠깐 그렇다면 왜 어디서 언제 optional을 사용하나?

     

    값이 없으니까 위의 num2처럼 값을 할당하지 않고 사용하다가 필요할 때 할당하면 되지 않나?라고 생각할 수 있습니다.

    우선 값이 없다는 num2는 값이 '없는 것'이 아닌 값이 '할당되지 않은' 초기화되지 않은 상태입니다. 따라서 어떠한 접근도 불가하며 값을 복사할 수도 어딘가에 집어넣을 수도 사용할 수도 없습니다.

    optional 변수/상수는 nil을 할당함으로써 값이 존재하지 않는 변수/상수가 됩니다. 이 상태에서 일반 변수/상수가 가지고 있는 특성을 사용자가 사용할 수 있습니다.

     

    그럼 언제 사용하느냐? 네트워크를 통해 받아 와야 할 문자열이 있다고 생각해봅시다. 이는 네트워크 통신이 성공하기 전까진 값이 존재하지 않는 변수입니다. 받아온 문자열을 담을 변수를 optional로 지정해 놓고 통신에 성공했을 때 값을 할당해 줄 때 optional을 사용합니다.

     

    optional을 사용할 때 하나만 기억합시다. nil은 값이 없는 상태입니다.

    optional 문법

    let 변수명: 타입? = nil

    optional은 기존 자료형 뒤에 '?'를 붙여 사용합니다. 그렇기 때문에 'Type Annotation을 통해 자료형을 명시' 해 주어야 합니다. 

    nil 자체로는 아무런 추론 할 수 있는 형식이 없을뿐더러, optional 타입이 아닌 자료형에는 nil을 할당할 수 없습니다.

     

    optional 또한 초기화를 해주어야 합니다. 초기화 없이 단순 선언 만으론 아직 값이 없는 상태가 아닙니다.

    optional 타입에는 nil뿐 아니라 타입에 해당하는 임의의 값을 집어넣을 수 있습니다.

    var opString: String? = "OptionalStr"
    opString = nil
    opString = "Hello"
    
    let newString = opString
    
    print(newString) // optional("Hello")
    print(type(of: newString)) // optional<String>

     

    unwrapping 

    let optionalNum: Int? = 3
    let num = 2
    
    print(optionalNum + num) // error 
    //Value of optional type 'Int?' must be unwrapped to a value of type 'Int'

    위의 optional 타입의 상수/변수의 특성을 그대로 사용할 수 있다에는 사실 조건이 붙습니다. 바로 optional이 해제된 unwrapped optional 변수/상수일 경우에 입니다.

    optional 변수/상수에 들어있는 값을 사용하기 위해선 unwrapping을 통해 값을 추출해야 합니다. 따라서 optional과 일반 자료형의 연산은 불가능합니다.

     

    이러한 unwrapping 방법엔 여러 가지 방법이 있습니다.

     

    • Forced Unwrapping
    var num: Int? = nil
    print(num!) // error 값이 없기 때문에 추출 할 수 없다.
    
    num = 3 
    print(num)  // optional(3)
    print(num!) // 3

     

       '!' 연산자를 상수/변수 바로 뒤에 붙여서 강제적으로 optional을 해제시키는 방법입니다.

       가장 위험한 방법으로 확실히 nil이 아닐 경우에만 사용하는 것을 추천합니다.

     

    • Optional Binding
    /* ------------ Syntax -----------  */
    if let name = OptionalExpression {
    }
    guard let name = OptionalExpression else {
    }
    /* -------------------------------- */
    

    optional을 가장 안전하게 처리하고 많이 사용하는 방법입니다.

    optional Binding은 조건문을 통해 nil이 아니라면 값을 추출합니다.

     

    let optionalNum: Int? = 3
    
    if let unrappedNum = optionalNum {
        print(unrappedNum)
    } else {
        print("nil")
    } // 3
    
    guard let unrrappedNum = optionalNum else { print("nil") }
    print(unrappedNum) // 3
    
    

    if문의 경우 OptionalExpression을 평가 후 값이 저장되어 있다면(nil이 아니라면) name 상수에 저장이 됩니다.

    scope 내에서 바인딩 한 상수를 사용할 수 있지만, 밖에선 사용 할 수 없습니다.

     

    guard문의 경우 OptionalExpression에 값이 존재하지 않는 다면 else scope으로 진입하고 아니라면 자연스럽게 다은 단계로 넘어갑니다.

    또한 바인딩 한 상수를 밖에서 사용할 수 있습니다.

     

    Optional Binding의 경우 Optional의 리턴 값이 존재해야 실행이 되므로 쉽고 보다 확실하게 optional 문제로 인한 에러를 피할 수 있습니다.

    let num: Int? = 5
    let str: String? = "Hello"
    
    if let num = num, let str = str, num > 3 && str.count > 3 {
    	print(num, str)
    } // 5 Hello

    또한 한 번에 다수의 Binding을 할 수 도 있고 condition을 추가할 수도 있습니다.

    이 경우 어느 한쪽이라도 binding에 실패할 경우 if문 은 실행되지 않습니다.

     

    • Nil-Coalescing Operator 
    OptionalExpression ?? Expression
    

    Nil-Coalescing Operator는 연산자입니다. 논리 연산자처럼 단락 평가를 합니다.

    따라서 왼쪽 피연산자가 값이 저장되어 있다면 오른쪽 피연산자는 평가되지 않습니다. 

     

    var num: Int? = nil
    
    print(num ?? "nil") // nil
    
    num = 3
    
    print(num ?? "nil") // 3
    print("num is", num ?? nil) // num is 3
    

     

    왼쪽 피연산자의 값이 저장되어 있는지(nil이 아닌지) 확인합니다. 값이 존재한다면 unwrapping 후 값을 리턴합니다.

    반대로 값이 저장되어 있지 않다면(nil이라면) 오른쪽 피연산자를 평가 후 그 결과를 리턴합니다. (unwrapping이 아닌 단순히 리턴만 해줍니다.) 때문에 반드시 왼쪽 피연산자가 nil 경우 반드시 오른쪽 피연산자에 오류가 없도록 해야 합니다.

     

     

    • Optional Chaining

    체이닝: 서로 연결되어 있는 것

    Optional Chaining이란 다수의 옵셔널 형식에서 멤버에 접근하는 방법입니다.

    struct Address {
        var country: String
        var city: String?
    }
    
    struct Identity {
        
        let name: String
        var age: Int
        var address: Address?
        
        init(_ name: String, _ age: Int, _ city: String) {
            self.name = name
            self.age = age
            address = Address(country: "Korea", city: city)
        }
    }
    
    var id = Identity("UY", 20, "Seoul")
    
    let a = id.address?.city?.count
    print(a) // Optional(5)
    print(type(of: a)) // Optional<Int>
    
    let b = id.address?.city
    print(b) // Optional("Seoul")
    print(type(of: b)) // Optional<String>
    
    
    

    옵셔널 체이닝을 통해 멤버에 접근할 때 접근하는 멤버중 하나라도 옵셔널이 존재 한다면 최종적으로 접근 하는 멤버가 옵셔널이 아니더라도 옵셔널 타입으로 반환됩니다.

     

    2가지만 기억하면 됩니다.

    optional chaining의 결과는 항상 옵셔널입니다.

    이어진 chaining 중 하나라도 nil을 반환한다면 중간에 끊기고 nil을 반환합니다. nil이 반환하는 타입은 최종 멤버의 옵셔널 타입입니다.

    'Swift' 카테고리의 다른 글

    Swift) API Design Guidelines  (0) 2021.06.10
    Swift) 함수 타입  (0) 2020.11.18
    Swift) swift 5에 추가된 String Interpolation  (0) 2020.11.04
    Swift) Type Inference과 컴파일 시간  (0) 2020.10.21
    Swift) removeLast()와 popLast()  (0) 2020.08.10

    댓글

Designed by Tistory.