[Swift] 옵셔널(Optional)의 구현과 활용

권승용·2022년 7월 12일
0

Swift 프로그래밍

목록 보기
4/7
post-thumbnail

Optional 이란?

  • nil (아무것도 없음) 일수도 있는 타입.

    먼저 아래 코드를 살펴보자.

    var myName: String
    print(myName) 

    위와 같은 코드를 실행시키면 expression failed to parse 에러가 난다. 변수 myName이 초기화되기 이전에 사용되었기 때문이다.

    변수를 초기화하지 않으면 에러가 나는 이유

    변수가 메모리에 저장될 때, 변수의 타입으로 인해 메모리를 차지하는 크기와 그 저장 구조가 결정된다.

    예를 들어 Double 타입의 변수에 50을 저장한다면, 50은 8바이트의 메모리 공간에 부호 비트와 데이터 비트로 표현되어 저장될 것이다.

    만약 똑같이 Double 타입 변수를 선언했지만, 값을 초기화하지 않았다면 무슨 일이 일어날까?

    8비트의 메모리 공간만 확보했을 뿐, 그 내용은 비어있다고 컴파일러가 인식하기 때문에 초기화가 필요하다는 에러가 나타난다.

    따라서 값을 통한 초기화가 이루어져야만 에러 없이 변수를 활용할 수 있다.

    그러나 변수 초기화를 미루고 싶다면?

    Optional 타입에 변수를 저장하여 이 타입의 변수가 nil (아무것도 없음) 일수도 있음을 명시할 수 있고, 그렇게 한다면 초기화가 이루어지지 않았어도 변수에 접근 시 자동적으로 nil이 할당되어 에러가 나지 않는다.

    var myName: String? // <- 옵셔널 타입으로 선언
    print(myName) // "nil" 이 출력된다.

    nil 이란?

    '아무것도 없음'을 표현하는 키워드.
    다른 언어들과 다르게, Swift에서의 nil은 실제로도 아무런 값이 없는 것이 아니다.
    아무런 값이 없다면 메모리 접근 오류가 나기 때문에, 그것을 nil을 통해 포장지로 한 번 감싸(wrapping)서 값이 없음을 표현하는 것이다.

Optional의 구현

  • 옵셔널은 어떻게 동작하는가?

    먼저 옵셔널은 열거형(enum)으로 구현되어 있다.

    // 옵셔널의 구현
    enum Optional<Wrapped> { // 제네릭 문법을 사용한다
        // 'Wrapped'로 저장된 값이 있을 때
    	case some(Wrapped) // 열거형의 연관값 사용
        // 값이 없을 때
        case none
    }

    따라서 열거형을 처리하는 방법을 사용해 아래와 같이 옵셔널을 처리할 수도 있다.

    var num: Int?
    
    // switch문을 이용한 열거형의 분기 처리
    func printNum(_ num: Int) {
      switch num {
      case .some(let a): 
         print(a)
      case .none:
         print("nil")
    }
    
    printNum(num) // "nil"
    num = 3
    printNum(num) // "3"

    ⭐️ nil 과 .none 은 완전히 동일하다.

    옵셔널은 값이 있을 때와 없을 때의 두 가지 case로 이루어진 열거형 타입이다. 따라서 옵셔널의 .none 이 곧 '값이 없음'을 뜻하는 nil 의 명시적인 열거형 표현법이다.

    위와 같은 동작을 간편화한 것이 옵셔널 바인딩이다.

    var optionalNum: Int? = 3
    
    if let num = optionalNum {
    	print(num) // "3"
    } else {
    	print("nil") // "nil"
    }
    
    // 또는
    
    guard let num = optionalNum else {
    	print("nil") // "nil"
    }
    
    print(num) // "3"
    
    // 결국 열거형의 case에 따른 처리를 하는 것. 
    

Optional의 활용

  • 일반 타입과 옵셔널 타입 변수의 선언 시 메모리 공간 차이점 (초기화가 없을 때)

    (옵셔널이 아닌) 일반 타입으로 변수를 선언해 보자.

    var num: Int 

    현재는 변수를 담을 공간이 메모리에 확보된 상태이지만, 그 안에는 아무런 값이 없기 때문에 변수에 대해 접근하게 되면 에러가 난다.

    print(num) // expression failed to parse 에러 

    이번엔 옵셔널 타입으로 변수를 선언해 보자.

    var num: Int?

    이렇게 옵셔널 타입으로 변수를 선언하게 되면, 변수를 담을 공간이 확보됨은 물론 그 공간 안에 옵셔널 열거형이 임시적으로 자리를 차지하고 있게 된다.

    print(num) // "nil" 출력 
    // 옵셔널 열거형이 임시적으로 자리를 차지하고 있다.
    // (메모리 공간이 비어있지 않다.)
    // 또한 num이 초기화되지 않았기 때문에 nil값을 출력한다.

    이렇게 임시값을 사용함으로써 옵셔널 타입은 '값이 없음' 상태를 표현 가능하게 되고, 초기화되지 않은 변수에도 임시값을 할당함으로써 에러를 피할 수 있게 된다.

  • 옵셔널의 확장 (extension)

    옵셔널은 열거형으로 구현되었다는 것을 확인하였고, 그렇다면 옵셔널의 확장(extension)을 작성할 수도 있을 것이다.

    아래 코드를 살펴보자.

    // 옵셔널의 확장
    // 계산 속성을 추가하여 옵셔널 타입인 자기 자신을 닐 코얼레싱으로 추출했다.
    // 따라서 nil일 경우 nil 대신 ""을 반환하게 된다.
    extension Optional where Wrapped == String {
      	var orEmpty: String { // getter, 즉 변수에 접근할 때 실행 
      		return self ?? "" // nil-coalescing으로 옵셔널 추출. 
        }
     }
     
     var name: String? = "권승용"
     print(name.orEmpty) // "권승용" 출력
     name = nil
     print(name.orEmpty) // "" 출력

    extensioin 을 사용하여 옵셔널 열거형에 계산 속성을 추가한 것을 볼 수 있다.
    해당 계산 속성은 자기 자신(옵셔널, 즉 열거형의 인스턴스)가 값을 갖고 있다면 그것을 반환하고, 값이 없이 nil이라면 ""을 반환한다.
    이를 이용해 String 타입의 값이 nil일 때 nil이 아닌 빈 문자열을 반환하는 작업을 간단하게 수행할 수 있다.

  • 옵셔널을 이용한 유닛 테스트

    만약 유닛 테스트 중 옵셔널을 강제 언래핑해서 사용하는 경우 옵셔널에 nil이 들어 있다면 fatal error를 일으키며 테스트 도중인 전체 프로젝트가 종료되게 된다.
    그럴 경우 강제 언래핑 대신 XCTUnwrap(_:_:file:line:) 을 사용하면 옵셔널이 값을 갖고 있지 않을 경우 프로그램이 뻗어버리는 대신 에러 메시지를 출력해 준다.

    var value: Int?
    var unwrappedValue = XCTUnwrap(value) // 에러 메시지 출력 (테스트 실패로 표시)

출처 : https://www.avanderlee.com/swift/optionals-in-swift-explained-5-things-you-should-know/

profile
ios 개발자 지망생 입니다!

0개의 댓글