[Swift 공식문서 읽기]Access Control

안녕하세요. 엘림입니다🙇🏻‍♀️

Swift 공식 문서를 정독하기 시리즈입니다!

제 스타일대로 정리했으니 추가적으로 더 필요한 정보는
공식문서 링크를 눌러 확인해주세용!

좀 더 편하게 보기위해 한국어로 번역된 사이트를 함께 확인했습니다!ㅎㅎ

자, 그럼 시작해볼까요

이 글은 공부하면서 작성한 글이기 때문에 잘못된 정보가 있을 수 있습니다.🥺
금방 잊어버릴... 미래의 저에게 다시 알려주기 위한 글이다보니
혹시라도 틀린 부분이 있다면, 댓글로 친절하게 알려주시길 부탁드립니다.🙏


접근 제어

접근 제어는 특정 코드의 접근을 다른 소스파일이나 모듈에서 제한하는 것입니다. 이렇게 접근제어를 함으로써 특정 코드의 세부적인 구현을 감추고, 딱 필요한만큼 공개해 다른 곳에서 사용할 수 있도록 합니다.
접근 제어는 클래스, 구조체, 열거형 등 개별 타입에도 선언할 수 있고, 그 타입에 속한 프로퍼티, 메소드, 초기자, 서브스크립트에도 적용할 수 있습니다. 프로토콜은 전역상수, 변수, 함수와 같이 특정 문맥에 종속됩니다. Swift에서는 기본 접근 레벨을 제공해, 접근 레벨의 처리를 쉽게할 수 있도록 돕습니다. 그래서 사실 단일 타겟의 앱에서는 특별히 접근레벨을 명시하지 않아도 됩니다.

모듈과 소스파일

접근제어는 모듈과 소스파일에 기반을 두고 있습니다. 모듈은 코드를 배포하는 단일 단위로, 하나의 프레임워크나 앱이 이 단위로 배포되고, 다른 모듈에서 import하여 사용할 수 있습니다.
Xcode의 각 빌드 타겟은 Swift에서 분리된 단일 모듈로 취급됩니다. 소스파일은 모듈 안에 있는 소스파일을 의미합니다. 각 소스파일에 여러 특정 타입을 선언해 사용할 수 있습니다.

접근 레벨

  • open & public: 선언한 모듈이 아닌, 다른 모듈에서 사용이 가능합니다. 단, open은 서브클래싱과 오버라이딩이 가능하지만, public은 다른 모듈에서 서브클래싱과 오버라이딩이 불가능 합니다.
  • internal: 기본 접근 레벨로, 아무 접근 레벨을 선언하지 않으면 internal로 간주되며 해당모듈 전체에서 사용가능합니다.
  • File-private: 선언한 파일 안에서만 사용 가능합니다.
  • private: 선언된 괄호 안에서만 사용 가능합니다.

접근 레벨 가이드 원칙
(낮다 <- private // 높다 <- open)
접근 레벨은 더 낮은 레벨을 갖고 있는 다른 엔터티를, 특정 엔터티에 사용할 수 없습니다.

  • public 변수는 다른 internal, file-private, private 타입에서 정의될 수 없습니다. 왜냐면 그 타입은 public 변수가 사용되는 모든 곳에서 사용될 수 없을 것이기 때문입니다.
  • 함수는 그 함수의 파라미터 타입이나, 리턴값 보다 더 높은 접근 레벨을 가질 수 없습니다. 왜냐하면 함수에 접근 가능하지만 파라미터에 접근이 불가능하거나, 혹은 반환값 타입보다 접근 레벨이 낮아 함수를 사용하는 관련 코드에서 이용할 수 없을 수 있기 때문입니다.

프레임워크를 위한 접근 레벨
프레임워크를 개발한다면 public 혹은 open으로 지정해서, 다른 모듈에서 볼 수 있고 접근 가능하도록 만들어야 합니다.
만약 프레임워크의 구체적인 구현을 감추고 싶은 부분이 있다면, internal로 선언하면 됩니다. 그리고 노출 시키고 싶은 API만 public 혹은 open으로 지정합니다.

유닛테스트 타겟을 위한 접근 레벨

기본적으로 open이나 public으로 지정된 엔티티만 다른 모듈에서 접근 가능합니다. 하지만 유닛테스트를 하는 경우 모듈을 import할때, @testable를 앞에 붙여주면 해당 모듈은 테스트가 가능한 모듈로 컴파일해 사용합니다.

커스텀 타입

커스텀 클래스에 특정 접근 레벨을 지정할 수 있습니다.
예를 들어 클래스를 file-pravate로 선언하면, 그 안의 프로퍼티와 함수 파라미터, 반환 타입도 file-private 접근 권한을 갖게 됩니다.(타입의 접근 제한자 설정은, 그 타입의 멤버들의 기본 접근 레벨에 영향을 미칩니다.)

public타입은 기본 적으로 internal 멤버를 갖고 public을 갖지 않습니다. 이렇게 동작하는 이유는 public API를 만들시 노출되지 말아야 할 API가 실수로 노출되는 것을 막기 위함입니다. 그래서 노출시키고 싶은 멤버는 명시적으로 public 접근제어자를 붙여줘야 합니다.

public class SomePublicClass {                  // explicitly public class
    public var somePublicProperty = 0            // explicitly public class member
    var someInternalProperty = 0                 // implicitly internal class member
    fileprivate func someFilePrivateMethod() {}  // explicitly file-private class member
    private func somePrivateMethod() {}          // explicitly private class member
}

class SomeInternalClass {                       // implicitly internal class
    var someInternalProperty = 0                 // implicitly internal class member
    fileprivate func someFilePrivateMethod() {}  // explicitly file-private class member
    private func somePrivateMethod() {}          // explicitly private class member
}

fileprivate class SomeFilePrivateClass {        // explicitly file-private class
    func someFilePrivateMethod() {}              // implicitly file-private class member
    private func somePrivateMethod() {}          // explicitly private class member
}

private class SomePrivateClass {                // explicitly private class
    func somePrivateMethod() {}                  // implicitly private class member
}

튜플

튜플타입의 접근레벨은 튜플에서 사용되는 모든 타입의 접근레벨 중 가장 제한적인 접근레벨을 갖습니다.
(튜플은 자체적으로 접근레벨을 선언하지 않고 사용하는 클래스, 구조체, 열거형 그리고 함수 등에 따라 자동으로 최소 접근 레 벨을 부여 받습니다. 즉, 접근권한을 명시하지 않습니다.)

함수

함수 타입의 접근레벨은 함수의 파라미터 타입과 리턴타입의 접근레벨 중 최소의 접근레벨로 계산돼 사용됩니다. 그래서 그것에 맞는 접근 레벨을 함수 앞에 명시해 줘야 합니다.

func someFunction() -> (SomeInternalClass, SomePrivateClass) { ... } // ERROR!!!
private func someFunction() -> (SomeInternalClass, SomePrivateClass) { ... } // good~!

열거형

열거형에서 각 case는 enum의 접근레벨을 따르고 개별적으로 다른 접근레벨을 지정할 수 없습니다.

고유값과 연관값

고유값과 연관값을 사용하는 타입의 경우, 반드시 그 타입보다 높은 접근 레벨을 가져야합니다.
즉, internal접근 레벨을 갖고 있은 열거형에서, private 접근 레벨을 갖는 고유값을 사용할 수 없습니다.

중첩타입

private로 선언된 타입의 중첩 타입은 자동으로 private 접근 레벨을 갖습니다. (public 혹은 internal로 선언된 타입에서 중첩타입은 자동으로 internal접근레벨을 갖습니다. public으로 선언된 타입에서 public으로 선언된 중첩타입을 사용하고 싶으면 명시적으로 public접근자를 중첩타입에 적어줘야 합니다.)

서브클래싱

서브클래스는 슈퍼클래스보다 높은 접근레벨을 가질 수 없습니다. 예를들어 슈퍼클래스가 internal 를 갖는데 그것을 서브클래싱해서 pubic 서브클래스를 만들 수 없습니다. 하지만 메소드는 서브클래스에서 더 높은 접은 레벨을 갖는 메소드로 오버라이드 할 수 있습니다.

public class A {
    fileprivate func someMethod() {}
}

internal class B: A {
     override internal func someMethod() {
        super.someMethod()
    }
}

상수, 변수, 프로퍼티, 서브스크립트

게터와 세터

상수, 변수, 프로퍼티, 서브스크립트의 게터와 세터는 자동으로 그것이 갖는 접근 레벨을 동일하게 갖습니다.
필요에 따라서 더 낮게 설정할 수 있습니다
fileprivate(set), privat(set), internal(set)

struct TrackedString {
    private(set) var numberOfEdits = 0
    var value: String = "" {
        didSet {
            numberOfEdits += 1
        }
    }
}

var stringToEdit = TrackedString()
stringToEdit.value = "This string will be tracked."
stringToEdit.value += " This edit will increment numberOfEdits."
stringToEdit.value += " So will this one."
print("The number of edits is \(stringToEdit.numberOfEdits)")
// Prints "The number of edits is 3"

public struct TrackedString {
    public private(set) var numberOfEdits = 0
    public var value: String = "" {
        didSet {
            numberOfEdits += 1
        }
    }
    public init() {}
}

세터뿐 아니라 게터에도 접근레벨을 지정하고 싶다면 위 코드와 같이 public private(set) var numberOfEdits = 0로 지정할 수 있습니다.

이니셜라이저

초기자의 접근레벨은 타입의 레벨과 같거나 낮습니다. 한가지 예외상황은 지정초기자(required Initializer)인데, 이 지정초기자는 반드시 이 초기자가 속한 타입과 접근레벨이 같아야 합니다.

기본 초기자 (Default Initializers)

기본 초기자는 타입의 접근레벨이 public으로 지정돼있지 않은 이상 타입과 같은 접근레벨을 갖습니다. 만약 타입의 접근 레벨이 public으로 지정돼 있으면 기본 초기자는 internal 접근레벨을 갖습니다.

구조체 타입을 위한 기본 멤버쪽 초기자 (Default Memberwise Initializers for Structure Types)

만약 모든 저장 프로퍼티가 private로 지정된 경우 멤버쪽 초기자의 접근레벨은 private을 갖고 file private인 경우는 file private를 갖습니다. 그 밖에는 internal접근레벨을 갖습니다. 만약 public 구조체 타입이 다른 모듈에서 사용될 때는 반드시 멤버쪽 초기자의 선언에 public으로 지정해야 다른 모듈에서 그 멤버쪽 초기자를 사용할 수 있습니다.

프로토콜

?????? 프로토콜 전체적으로 다시 읽어봐야겠다!
프로토콜의 접근레벨과 그 안의 요구사항의 접근레벨은 항상 동일합니다.
(다른 타입은 타입의 선언이 public이어도 안의 구현은 internal일 수 있습니다.)

프로토콜 상속

이미 존재하는 프로토콜을 상속받아 새로운 프로토콜을 선언하는 경우 새 프로토콜은 상속을 한 프로토콜과 같은 접근레벨을 갖습니다.

프로토콜 순응

특정 타입은 그 타입보다 낮은 접근레벨을 갖는 프로토콜을 따를 수 있습니다. 예를들어 public타입을 다른 모듈에서 사용하지만 internal프로토콜은 오직 internal프로토콜이 선언된 곳에서만 사용가능 합니다. 만약 타입이 public이고 프로토콜이 internal이라면, 그 프로토콜을 따르는 타입도 internal입니다.
프로토콜 순응은 전역적으로 같은 프로그램에서 다른 방법으로 프로토콜을 따르는 것은 불가능 합니다.

익스텐션

클래스, 구조체, 열거형 등에 익스텐션에서 새로운 맴버를 추가하면 새로 추가된 것은 기존에 타입이 선언된 접근레벨과 같은 레벨을 갖습니다. 익스텐션에 명시적으로 접근레벨을 지정할수도 있습니다.
익스텐션을 프로토콜로 사용하는 경우 명시적인 접근레벨 변경이 불가능합니다. 대신 프로토콜의 접근레벨이 익스텐션의 프로토콜 구현에서 사용 됩니다.(???)

프라이빗 멤버

익스텐션이 클래스, 구조체 혹은 열거형과 같은 파일에 선언된 경우 다음이 가능합니다.

  • 원본 선언에서 private 멤버로 선언한것을 익스텐션에서 멤버로 접근할 수 있습니다.
  • 하나의 익스텐션에서 private멤버로 선언한 것을 같은 파일의 다른 익스텐션에서 접근할 수 있습니다.
  • 하나의 익스텐션에서 private멤버로 선언한 것을 원본 선언에서 멤버로 접근할 수 있습니다.
protocol SomeProtocol {
	func doSomething()
}

struct SomeStruct {
    private var privateVariable = 12
}

extension SomeStruct: SomeProtocol {
    func doSomething() {
        print(privateVariable)
    }
}

프로토콜을 따르는 익스텐션에서 그 타입의 private변수에 접근할 수 있습니다.

제네릭

제네릭(타입 혹은 함수)은 자체 또는 파라미터의 접근레벨의 최소 접근레벨을 갖습니다.

타입 별칭

타입 별칭은 본래 타입보다 같거나 낮은 접근레벨을 갖습니다. 예를들어, private타입의 별칭은 private, file-private, internal, public, open 타입의 접근 권한을 갖을 수 있습니다.
(이 규칙은 프로토콜의 순응을 충족시키는데 사용하는 연관 타입을 위한 별칭에도 동일하게 적용됩니다. ????)


오늘도 스위프트 공식문서를 정리해보았군욥~
다음편도 힘내보겠습니다!

감사합니다🙇🏻‍♀️

좋은 웹페이지 즐겨찾기