ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Swift - Failable InitialIzer
    Programming/Swift 2021. 12. 17. 19:36

     

    안녕하세요 BeePeach입니다 :)

     

    길고 길던 initializer 포스팅이 거의 끝나가고 있습니다.

    오늘 공부해볼 내용은 Failable initializer입니다.

     

    지금까지 우리가 사용한 init은 초기화에 실패한 적이 없습니다.

    하지만 만약 초기화될 값을 네트워크를 통해서 설정하려 하거나 특정 조건에 만족하는 값으로만 초기화하고 싶을 수 있습니다. 또는 init의 파라미터에 원치 않는 값이 전달됐을 경우도 있겠죠.

    이럴 때 사용하는 게 failiable initailizer입니다.

     

     


     

    Failiable Initializer

     

    Failable init은 class, structure, enum에서 사용할 수 있습니다.

    초기화에 실패할 수도 있고 성공할 수 도 있다.

    그럼 성공하면 초기값이 할당되고 실패하면?? nil이 들어간다는 말입니다.

    이 말은 어디서 많이 들어본 말이죠?? 

    Optional의 정의와 똑같습니다. 그래서 failable init로 ?과 !를 사용합니다.

     

     

    init과 parameter 사이에 ? 또는 !를 붙여주면 됩니다.

    하지만 여기서 !를 사용한 문법은 없다고 생각하는 게 좋습니다.

    이유는 Forced unwraapping과 비슷합니다. 초기화에 실패하면 인스턴스가 생성이 안될 뿐만 아니라 crash가 발생하기 때문에 아주 특별한 상황이 아니라면 사용할 이유가 딱히 없습니다.

     

    init? 은 실패하면 nil을 반환하게 됩니다.

    여기서 헷갈리면 안 되는 건 엄밀히 말하자면 init은 아무것도 리턴하지 않습니다.

    그래서 지금까지 init이 초기화에 성공하더라도 return을 쓰지 않았습니다. 하지만 failable init에서는 초기화를 실패했다는 것을 나타내기 위해서 return nil을 작성합니다.

     

    그럼 한번 작성해 볼까요??

     

     

    Position이라는 struct를 정의했습니다. 

    좌표를 나타내기 위해서 두 개의 x, y 프로퍼티를 선언했고 init?(x:y:)로 생성자를 선언했습니다.

    하지만 이 코드를 잘 생각해보면 엉망인 것을 알 수 있습니다.

     

    위 코드는 절대 실패할 일이 없습니다. 생성자만 failiable일 뿐이지 어떠한 double 값이 들어와도 상관없습니다.

    그럼 이렇게만 선언하면 뭐가 원래 init과 무슨 차이가 있을까요??

     

     

    Failiable Init으로 생성한 인스턴스는 optional으로 wrapping 되어 있습니다.

    생각해보면 실패한다면 nil이 담길 수 있으니 당연한 결과입니다.

     

    우리는 x와 y의 값이 모두 양수일 때만 초기화가 되길 원합니다. 그럼 init을 다시 바꿔보겠습니다. 

     

     

    이전 코드와의 차이는 init?(x:y:)에서 조건이 들어갔습니다.

    모두 양수일 경우가 아니라면 return nil을 해줬습니다.

     

    그리고 init?(x:y:)에 음수를 전달했더니 그 결과 nil이 리턴되는 것을 확인할 수 있습니다.

     

     


     

    Failable Init의 Overloading

     

    Failable init의 결과는 init과는 Optional과 non - optional으로 달랐습니다. 

    그럼 같은 class안에서 동일한 이름의 init?과 init을 선언할 수 있을까요??

    직접 작성해보고 확인해 보겠습니다.

     

     

    init?(x:y:)와 init(x:y:)는 다를 거 같지만 결과는 에러가 발생합니다.

    Failable init과 init은 같은 이름으로 선언할 수 없습니다.

     

    그 이유는 init을 호출하는 문법을 생각해보면 됩니다.

    우리가 failiable init이라고 해서 init?(x:y:)와 같이 호출하지 않고 똑같인 init(x:y:)로 호출하죠??

    Swift가 이것을 구별할 방법이 없으므로 overloading은 금지되어있습니다.

     

     


     

    Failable Init의 Delegation

     

    Failable Init은 이전에 우리가 공부했던 init과 같이 delegation up과 delegation across가 가능합니다.

    만약 중간에 init이 실패한다면 그 즉시 init 초기화 과정은 중지되고 nil을 리턴합니다.

     

     

    Position을 class로 바꾼 뒤 Position을 상속받는 SomeObject class를 선언했습니다.

    그리고 init?(width:height:x:y:)을 선언했습니다.

    여기서 x 또는 y가 음수일 경우에는 우리가 공부했던 2단계 초기화 과정을 거치기도 전에 초기화는 바로 종료가 됩니다.

     

     


     

    Failable Overriding

     

    Failable init도 override가 가능합니다.

    다만 주의해야 할 점이 있습니다.

    Superclass의 init이 failable init일 때 subclass에서 non-failable로 override가 가능합니다.

    하지만 superclass의 init이 non-failable일 때 subclass에서는 반드시 non-failable로 override 해야 합니다.

     

    왜 그럴까요??

    Failable init은 우리가 맨 처음 잘못 작성했던 것처럼 무조건 성공해도 에러가 발생하지 않습니다.

    그렇기 때문에 Superclass에서는 실패할 수도 있었지만 subclass에서는 무조건 성공하도록 바꿔도 괜찮습니다.

    하지만 non-failable init은 무조건 성공해야 합니다. 실패하는 경우란 존재하지 않습니다.

    그래서 override를 하는데 실패가 가능하다면 문제가 발생할 수 있습니다.

    이러한 이유로 Non-failable init을 Failable로 override 하는 것은 불가능합니다!!

     

    그럼 예시를 한번 보겠습니다.

    지금까지 공부했던 개념들을 잘 사용해야 하기 때문에 살짝 어려울 수 있습니다.

     

    먼저 Document라는 class를 생성했습니다.

    init()이라는 default init을 선언해주고 init?(name:)을 선언했습니다.

    init?(name:)은 파라미터로 빈 문자열이 전달되면 실패하는 failable init입니다.

     

    그럼 AutomaticallyNameDocument라는 Document를 상속하는 class를 선언했습니다.

    새롭게 선언된 프로퍼티는 없고 init만 override 해보도록 했습니다.

     

    먼저 non-failable init인 init()을 override 하는 것은 쉽습니다.

    delegate up을 해야 하기 때문에 super.init()을 호출하고 name을 "Untitled"로 초기화시켰습니다.

     

    이제 문제는 init?(name:)입니다.

    일단 init?(name:)이지만 subclass에서 non-failable로 선언할 수 있다고 했습니다.

    그래서 init(name:)으로 override 했는데 문제는 Line 26에서 발생합니다.

    Superclass의 failable init을 subclass에서 호출하기 위해서는 해당 init이 failable init으로 선언되어있어야하거나 ! (force - unwrap)을 이용해서 superclass의 failable init의 결과를 unwrapping 해야 합니다.

     

    지금 코드는 non-failable init으로 override 해보는 것이기 때문에 첫 번째 해결책은 도움이 되지 않습니다.

    그럼 나머지 해결 방식을 이용해야 합니다.

    하지만 ! 는 되도록 쓰고 싶지가 않죠..?

    그럼 어떻게 해결해야 할까요??

     

    Overriding을 할 때 반드시 같은 이름의 superclass의 init을 delegate up하지 않아도 됩니다.

    여기서 super.init(name:)을 호출하는 게 아닌 non-failable init인 super.init()를 delegate up 하는 방식으로 구현하면 됩니다.

     

     

    이렇게 구현한다면 모든 조건을 만족하면서 구현할 수 있습니다.

    혹시라도 !를 이용하고 싶다면 아래와 같이 구현할 수 있습니다.

     

     

    Superclass의 failable Init을 subclass의 non-failable init에 호출하기위해선 !를 사용할 수 밖에 없습니다.

    이렇게 하면 init?(name:)을 호출했지만 아까와 같은 에러가 발생하지 않습니다.

    이 코드를 보면 name에 빈 문자열이 들어간다면 nil을 강제 추출하기 때문에 런타임 에러가 발생하게 됩니다.

    하지만 무조건 "Untitled"라는 문자열을 전달하기 때문에 super.init(name:)은 실패하지 않고 이 코드는 절대 runtime 에러를 일으키지 않습니다....만 !의 사용은 안 하는 게 좋습니다.

    혹시라는 게 있잖아요?? 우리가 어디서 실수를 할지 모릅니다.

    그러니 이 방법보단 위의 방법을 사용하는 게 더 좋습니다.

     

     


     

    참고자료

     

    https://docs.swift.org/swift-book/LanguageGuide/Initialization.html

     

    Initialization — The Swift Programming Language (Swift 5.6)

    Initialization Initialization is the process of preparing an instance of a class, structure, or enumeration for use. This process involves setting an initial value for each stored property on that instance and performing any other setup or initialization t

    docs.swift.org

     

     

    https://kxcoding.com

     

    여러분의 새로운 도전을 응원합니다 | KxCoding

    Mastering SwiftUI 더 적은 코드로, 더 멋진 UI 만들기

    kxcoding.com

     

    728x90

    'Programming > Swift' 카테고리의 다른 글

    Swift - Deinitializer  (0) 2021.12.19
    Swift - Required Initializer  (0) 2021.12.18
    Swift - Initializer - Inheritance, Overriding  (0) 2021.12.11
    Swift - Initializer - Two Phase Initialization  (1) 2021.12.06
    Swift - Initializer Delegation  (0) 2021.11.28
Designed by Tistory.