ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Default Initializers의 흔한 오해
    Swift 2023. 10. 17. 14:48

    안녕하세요. 그린입니다 🍏

    이번 포스팅은 짧게 Swift의 Access Level을 공부하면서 오해가 쌓일 수 있는 부분에 대해 한번 되짚어보려고해요 🙋🏻

     

     ⚠️ 내용 다소 짧음 주의 ⚠️

    일단 어느 부분에서 오해가 쌓일 수 있었는지 공식 문서를 통해 한번 확인해볼께요ㅎㅎ


    Default Initializers

    바로 Default Initializers 부분인데요.

    여길 한번 해석해보면 이렇습니다.

     

    "Swift는 모든 프로퍼티가 기본값을 가지고 있고, 그 자체로 적어도 하나의 초기화 함수를 제공하지 않는 구조체나 기본 클래스에 대해 자동으로 인자가 없는 기본 초기화 함수를 제공합니다.

    이것을 Default Initailizers인 기본 이니셜라이저라고 부릅니다.

    기본 이니셜라이저의 접근 레벨은 일반적으로 해당 타입과 동일합니다.
    그러나 만약 해당 타입의 접근 레벨이 public이면 기본 이니셜라이저는 internal로 간주됩니다.
    즉, 외부 모듈에서 사용될 때 public 타입이 인자 없는 생성자로 초기화가 가능하도록 하려면 public 이니셜라이저를 명시적으로 제공해야 합니다."

     

    즉, 요약해보자면 Swift가 특정 조건을 충족하는 경우에 대해 자동으로 기본 이니셜라이저를 제공하지만, 외부에서 접근 가능하도록 만드려면 public을 이니셜라이저에 명시적으로 붙여줘야 합니다.

     

    여기까지는 이해가 잘 된것 같이 보입니다.

     

    그런데 여기서 핵심적인 문구가 하나 있어요!

     

    바로, A default initializers same access level as the type it initialzers.

     

    기본 이니셜라이저의 접근 레벨은 해당 타입과 동일하다 입니다.

     

    코드로 예시를 살펴볼까요?

     

    struct A {
      private let b = B()
    }
    
    private struct B { }

     

    해당 코드에서 B는 private 접근 레벨을 가진 타입입니다.

    그럼 공식문서에 따라 명시적으로 이니셜라이저를 구현해주지 않으면 기본 이니셜라이저를 사용할 수 있죠.

     

    그런데 이 이니셜라이저의 접근 레벨은 어떻게 될까요?

     

    오해를 가진 부분은 타입과 동일한 레벨의 이니셜라이저를 가지게 된다로 이해하고 있는 경우가 종종 있다는 사실입니다.

    즉, 위 B의 기본 이니셜라이저의 접근 레벨을 private이 되는거 아니야?

    그런데 어떻게 A 타입에서 B 인스턴스를 생성할 수 있지...? 입니다!

     

    이게 바로 오늘의 오해입니다 😥

     

    공식문서를 자세히 들여다보면 same access level이라는것은 구조체가 private 접근 레벨 타입일경우 이 접근 레벨을 이니셜라이저에 카피한다는 뜻으로의 same이 아니라,구조체에 접근할 수 있는곳이라면 기본 이니셜라이저에 접근할 수 있다는 뜻의 same으로 해석하는게 맞습니다!

     

    그렇기에 여기서 private B 구조체가 작용하는 enclosing file 범위에서는 명시적인 이니셜라이저가 없다면 어디서든 해당 이니셜라이저를 접근할 수 있죠.

     

    사실상 internal 이니셜라이저지만 fileprivate이 숨겨져서 붙은 이니셜라이저일 수 밖에 없죠.

    왜냐면 private 타입이기에 다른 파일에서는 접근할 수 없으니까요 🙋🏻

     

    즉 정리하면 B가 작용하는 범위와 같은 레벨의 이니셜라이저 접근 레벨로 보는것이 맞습니다 😄


    SwiftUI에서 private View 타입을 만들때 이모저모

    사실 저는 SwiftUI에서 아래와 같이 상하위 뷰들의 접근 레벨을 만들어 사용하는 경우가 많습니다.

     

    import SwiftUI
    
    struct ParentView: View {
      var body: some View {
        ChildView()
      }
    }
    
    private struct ChildView: View {
      fileprivate init() { }
      
      fileprivate var body: some View {
        Text("Test")
      }
    }

     

    사실 여기서 ChildView는 private 타입이기에 기본 이니셜라이저가 없어도 되고 아니면 접근 레벨을 붙이지 않아도 무방합니다.

    왜냐하면 private 타입이기에 어차피 외부 파일에서는 호출할 수 없으니까요.

     

    그럼에도 불구하고 왜 fileprivate을 붙였을까요?

     

    여기에는 스타일의 차이가 있습니다.

    기본적으로 fileprivate을 붙이지 않아도 어차피 fileprivate으로 작용할테지만, 조금 더 확장성과 혹시 모를 실수를 방지할 수 있다는것이죠.

     

    예를들어, ChildView를 깜빡하고 private 타입으로 만들지 않고 이니셜라이저도 명시적으로 만들지 않거나 아니면 internal로 접근 레벨을 가져가게되면 외부 파일에서 의도치 않게 호출하고 접근할 수 있는 가능성이 있는것이죠.

     

    또한 언제 이 타입의 접근 레벨이 변할지도 모르고 그때 다시 스코프를 파악하여 주기도 꽤나 번거로울 수 있죠.

     

    그렇기에 이중으로 확실히 명시적으로 추론할 수 있도록 fileprivate을 붙여 이런 경우들을 방지하고 유지보수 하기 위한 목적에 있습니다 🙋🏻


    정리

    공식문서에서 명확히 해석을 하였지만 가볍게 본다면 그 속까지 완벽히 이해하기 어려울 수 있습니다.

    결국 이런 오해를 초래할 수 있는 경우가 발생합니다 😥

    그렇기에 꼭 공식문서로 학습을 할때도 코드를 통해 학습한 사실이 맞는지 잘못 오해하고 있는 부분은 없는지 확인해보는 과정이 필요합니다!

     


    레퍼런스

    https://docs.swift.org/swift-book/documentation/the-swift-programming-language/accesscontrol/

     

    Documentation

     

    docs.swift.org

Designed by Tistory.