GGURUPiOS

Swift 공식 문서 정리 - (27) Access Control 본문

Swift/공식문서 정리 ( 문법 )

Swift 공식 문서 정리 - (27) Access Control

꾸럽 2023. 5. 14. 15:55

Access Control ( 접근제어, 액세스 제어)

접근 제어는 다른 소스 파일 및 모듈의 코드에서 코드 부분에 대한 액세스를 제한함

이 기능을 사용하면 코드의 구현 세부 정보를 숨기고 해당 코드에 액세스 하고 사용할 수 있는 기본 인터페이스를 지정 가능함

접근 제어는 클래스, 구조체, 열겨형 등 개별타입에도 적용할 수 있고

그 타입에 속한 프로퍼티, 메소드, 초기자, 서브스크립트에도 적용할 수 있음

프로토콜은 그래서 전역 상수, 변수, 함수 같이 특정 문맥에 종속됨

스위프트에서는 기본 접근 레벨을 제공해 접근 레벨의 처리를 쉽게할 수 있도록 도움

사실 단일 타겟의 앱에서는 특별히 접근레벨을 전혀 명시하지 않아도 됨

모듈과 소스 파일

스위프트의 접근 제어는 모듈과 소스파일에 기반을 둠

모듈은 코드를 배포하는 단일 단위로 하나의 프레임워크나 앱이 단위로 배포되고 다른 모듈에서 swift의 import 키워드를 사용해 import 될 수 있음

Xcode의 각 빌드 타겟은 Swift에서 분리된 단일 모듈로 취급됨

소스파일은 모듈안에 있는 소스파일을 의미함

각 소스파일에 여러 특정 타입을 선언해서 사용할 수 있음

접근레벨

5개의 접근레벨을 제공함

  • Open & Public Open과 Public 접근자 모두 선언한 모듈이 아닌 다른 모듈에서 사용가능 Open은 다른 모듈에서 오버라이드와 서브클래싱 가능하지만 Public은 다른 모듈에서 오버라이드와 서브클래실 불가능
  • Internal 기본 접근레벨로 아무 접근레벨을 선언하지 않으면 Internal 로 간주됨 Internal 레벨로 선언되면 해당 모듈 전체에서 사용가능함
  • File-private 특정 엔티티를 선언한 파일 안에서만 사용 가능함
  • Private 특정 엔티티가 선언된 괄호 안에서만 사용가능 함

Open은 클래스와 클래스 멤버만 적용될 수 있음

접근 레벨 가이드 원칙

스위프트에서 접근 레벨은 더 낮은 레벨을 갖고 있는 다른 엔티티를 특정 엔티티에 선언해 사용할 수 없다는 일반 가이드 원칙을 따름

  • public 변수는 다른 internal, file-private 혹은 private 타입에서 정의될 수 없음 그 타입은 public 변수가 사용되는 모든 곳에서 사용될 수 없을 것이기 때문
  • 함수는 구성 타입을 주변 코드에서 사용할 수 없는 상황에서 함수를 사용할 수 있으므로 파라미터 타입 및 반환 타입보다 더 높은 액세스 수준을 가질 수 있음

언어의 다양한 측면에 대한 이 기본 원칙의 구체적인 의미는 아래에서 다룸

기본 접근 레벨

코드의 모든 에티티는 명시적 접근 레벨을 직접 지정하지 않는 경우 Internal 이 기본임

결과 적으로 대부분의 경우 코드에서 명시적인 접근 레벨을 지정할 필요가 없음 다

단일 대상 앱의 접근 레벨

간단한 단일 대상 앱을 작성할 때 앱의 코드는 일반적으로 앱 내에 자체 포함되며 앱 모듈 외부에서 사용할 수 있도록 할 필요가 없음

내부의 기본 접근 레벨은 이미 이 요구 사항과 일치함

따라서 사용자 지정 접근 레벨을 지정할 필요가 없음

그러나 앱 모듈 내의 다른 코드에서 구현 세부 정보를 숨기기 위해 코드의 일부를 file-private 혹은 private로 표시할 수 있음

프레임워크의 접근 레벨

프레임워크를 개발할 때 프레임워크를 가져오는 앱과 같은 다른 모듈에서 보고 액세스 할 수 있도록 해당 프레임워크에 대한 접근 레벨을 public 또는 open 으로 표시해야함

이 공용 인터페이스에는 ㅡ레임 워크용 애플리케이션 프로그래밍 인터페이스 ( or API ) 임

단위 테스트 대상에 대한 접근 레벨

단위 테스트 대상이 있는 앱을 작성할 때 앱의 코드를 테스트하려면 해당 모듈에서 사용할 수 있어야 함

기본적으로 open 또는 public으로 표시된 엔티티만 다른 모듈에 접근 가능함

그러나 @testable 특성으로 제폼 모듈에 대한 import 선언을 표시하고 테스트를 사용하도록 설정한 상태에서 해당 제품 모듈을 컴파일 하는 경우 장치 테스트 대상은 모든 내부 엔티티에 액세스 가능

접근 제어 구문

엔티티 선언의 시작 부분에 위의 접근 제어자 중 하나를 정의하면 됨

public class SomePublicClass {}
internal class SomeInternalClass {}
fileprivate class SomeFilePrivateClass {}
private class SomePrivateClass {}

public var somePublicVariable = 0
internal let someInternalConstant = 0
fileprivate func someFilePrivateFunction() {}
private func somePrivateFunction() {}

커스텀 타입

사용자 지정 타입에 대한 명시적 접근 레벨을 지정하려면 타입을 정의하는 지점에서 지정함

예를 들어 file-private 클래스를 정의하는 경우 해당 클래스는 file-private 클래스가 정의된 소스파일에서 프로퍼티 타입이나 함수 파라미터 또는 반환 타입으로 사용 가능함

타입의 접근 레벨 은 해당 형식의 멤버의 기본 접근 레벨에 영향을 줌

타입의 접근 레벨을 private, file-private 으로 정의하면 해당 멤버의 기본 접근레벨도 private, file-private가 됨

타입의 접근 레벨을 internal 또는 public 으로 정의하는 경우 타입 멤버의 기본 접근레벨은 internal 이 됨

public 타입은 기본적으로 public멤버가 아닌 internal 멤버를 가짐, 타입 멤버를 공개하려면 타입 멤버를 public 으로 명시적 표시를 해야함

이 요구 사항은 타입에 대한 public API가 사용자가 게시하기로 선택한 것임을 보장하고 타입의 내부 작업을 공개 API로 표시하는 것을 방지함

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
}

아래는 구체적인 타입들의 접근 레벨이 나옵니다

튜플

튜플 타입의 접근 레벨은 해당 튜플에서 사용되는 모든 타입의 가장 제한적인 접근 레벨임.

예를 들어 internal 접근 레벨과 private 접근 레벨이 있는 두가지 타입으로 튜플을 구성하는 경우

튜플의 접근 레벨은 private임

튜플 타입에는 클래스, 구조체, 열거형 및 함수와 같은 독립 실행형 정의가 없음

→ 튜플 타입의 접근 레벨은 튜플을 구성하는 타입에서 자동으로 결정되며 명시적 지정 불가

함수 타입

함수 타입에 대한 접근 레벨은 함수의 파라미터 타입 및 반환 타입 중 가장 제한적인 접근 레벨로 계산됨

함수의 계산된 접근레벨이 컨텍스트 기본값과 일치하지 않는 경우 함수 정의의 일부로 접근 레벨을 명시적 지정 해야함

아래 예제는 기본 접근 레벨이 Internal로 예상 되지만 그렇지 않고, 컴파일 오류가 난다

( 공식문서에서 헷갈리게 써놨네요. 함수타입 처음에 설명을 해놓고 internal로 예상이 간다고 해놓았음 → 설명을 따라가면 튜플의 가장 제한적인 private 로 예상됨 )

func someFunction() -> (SomeInternalClass, SomePrivateClass) {
    // function implementation goes here
}

위의 괄호안에 쳐놓은 내용 처럼 반환값이 private의 접근 레벨이기 때문에 명시적으로 func 앞에 prviate 을 붙여줘야함

열거형 타입

열거의 개별 케이스는 자신이 속한 열거와 동일한 접근 레벨을 자동으로 받음

아래 예에서 열거형의 명시적 접근 레벨은 public임. 따라서 케이스 들도 자동으로 public 임

public enum CompassPoint {
    case north
    case south
    case east
    case west
}

raw 값 및 관련값

열거형 정의에서 원시 값 또는 관련 값에 사용되는 타입에는 적어도 열거형 접근 레벨 만큼 높은 접근 레벨이 있어야 함

예를 들어 internal 접근 레벨이 있는 열거형의 원시 값 타입으로 전용 타입을 사용할 수 없음

중첩 타입

중첩 타입의 접근 레벨은 포함 타입이 Public 이 아닌 한 포함 타입과 동일함

public 타입 내에서 정의된 중첩 타입의 자동 접근 레벨은 internal 임

public 타입 내의 중첩 타입을 공개적으로 사용할 수 있게 하려면 중첩 타입을 public 으로 명시해야함

서브 클래싱

현재 접근 컨텍스트에서 접근할 수 잇고 하위 클래스와 동일한 모듈에 정의된 모든 클래스를 하위 클래스로 만들 수 있음

또한 다른 모듈에 정의된 모든 open 클래스를 서브클래싱 할 수 있음

하위 클래스는 슈퍼클래스 보다 높은 접근 레벨을 가질 수 없음

예를들어 internal 슈퍼클래스의 public 서브클래스를 작성 할 수 없음

또한 동일한 모듈에 정의된 클래스의 경우 특정 액세스 컨텍스트에서 볼 수 있는 모든 클래스 멤버를 재정의 할 수 있음

다른 모듈에 정의된 클래스의 경우 open 클래스 멤버를 재정의 할 수 있음

재정의는 상속된 클래스 멤버가 해당 슈퍼클래스 버전보다 접근하기 쉽도록 만들 수 있음

아래 예제에서 A는 fileprivate 전용 메서드가 있는 공용 클래스임

클래스B는 A의 서브클래스이며 internal 접근레벨임

B는 someMethod를 원래 접근레벨보다 높은 internal로 재정의한다

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

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

서브클래스의 멤버가 서브클래스 멤버보다 낮은 접근 레벨을 가진 슈퍼클래스 구성원을 호출하는 것도 유효함

슈퍼클래스 멤버에 대한 허용된 접근 레벨 컨텍스트 내에서( 즉, file-private 멤버 호출에 대한 슈퍼클래스와 동일한 소스파일 내에서, 또는 내부 멤버 호출에 대한 슈퍼클래스와 동일한 모듈 내 호출)

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

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

슈퍼클래스 A와 서브클래스 B는 동일한 원본 파일에 정의되어 있으므로

super.someMethod()를 호출하는 것은 someMethod()의 B에서의 구현에 유효함

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

상수, 변수, 또는 프로퍼티는 그것의 타입보다 공개적일 수 없다.

private 타입으로 public 프로퍼티를 선언하는 것은 유효하지 않음

마찬가지로, 서브스크립트는 인덱스 타입 또는 반환 타입보다 공개적일 수 없음

상수, 변수, 프로퍼티 또는 서브스크립트가 private 타입을 사용하는 경우 상수, 변수, 프로퍼티 또는 서브스크립트도 private 로 표시해야함

private var privateInstance = SomePrivateClass()
// class 가 private 이기 때문에 변수도 마찬가지로 private 명시

Getters and Setters

상수, 변수, 프로퍼티 및 서브스크립트에 대한 게터와 세터는 자신이 속한 상수 변수, 프로퍼티 또는 스크립트와 동일한 접근 레벨을 자동으로 가짐

세터에게 해당 게터보다 더 낮은 접근 레벨을 지정하여 해당 변수, 프로퍼티, 또는 서브스크립트의 읽기-쓰기 범위를 제한할 수 있음

변수 또는 첨자 앞에 fileprivate(set), private(set), internal(set) 을 작성하여 더 낮은 액세스 단계를 할당할 수 있음

위 규칙은 계산된 프로퍼티 뿐만 아니라 저장 프로퍼티에도 적용됨

저장 프로퍼티에 대해 명시적인 게터와 세터를 작성하지 않아도 스위프트는 암시적인 게터와 세터를 통합하여 저장 프로퍼티의 백업 저장소에 대한 접근을 제공함

fileprivate(set), private(set), internal(set) 을 사용하여 계산된 프로퍼티의 명시적인 setter와 동일한 방식으로 setter의 접근 레벨을 변경함

예를 들어

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

numberOfEdits 변수를 TrackedString 내부에서만 변경되도록 지정한 코드

TrackedString 에 아무런 접근 레벨이 지정되지 않기 때문에 internal로 취급돼

numberOfEdits 변수에 private(set)이 지정되지 않았다면 TrackedString 밖에서도 접근가능

이 코드를 실행하면 다음과 가팅 게터만을 이용해 변경된 숫자를 확인할 수있음

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 private(set) var numberOfEdits = 0 이런식으로 지정 가능

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

이니셜라이저

이니셜라이저의 접근 레벨은 타입의 레벨과 같거나 낮음

한가지 예외 상황은 필수 지정 이니셜라이저인데 (required initializer)

이 지정 이니셜라이저는 반드시 이 이니셜라이저가 속한 타입과 접근 레벨이 같아야 함

기본 이니셜라이저 (Default Initializers)

기본 이니셜라이저는 타입의 접근 레벨이 public이 아닌 이상 타입과 같은 접근 레벨을 갖음

만약 타입의 접근 레벨이 public으로 지정돼있으면 기본 이니셜라이저는 internal 접근 레벨을 갖음

구조체 타입을 위한 멤버쪽 이니셜라이저

만약 모든 저장 프로퍼티가 private 로 지정된 경우 멤버쪽 이니셜라이저의 접근 레벨은

private을 갖고 fileprivate 인 경우는 fileprivate를 갖는다

그밖에는 internal을 갖는다

public 구조체가 다른 모듈에서 사용될 때 멤버쪽 초기자의 선언에 public으로 지정해야 함

프로토콜

프로토콜의 접근 레벨과 그 안의 요구사항의 접근 레벨은 항상 동일

프로토콜 상속

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

프로토콜 순응

특정 타입은 그 타입보다 낮은 접근 레벨을 갖는 프로토콜을 따를 수 있음

예를들어 public 타입을 다른 모듈에서 사용하지만

internal 프로토콜은 오직 Internal 프로토콜이 선언된 곳에서만 사용가능함

만약 타입이 public이고 프로토콜이 internal이라면 그 프로토콜을 따르는 타입도 internal 임

익스텐션

클래스, 구조체, 열거형 등에 익스텐션에서 새로운 멤버를 추가하면

새로 추가된 것은 기존에 타입이 선언된 접근레벨과 같은 레벨을 갖음

익스텐션에 명시적으로 접근 레벨을 지정할 수 도 있음

익스텐션을 프로토콜로 사용하는 경우 명시적인 접근 레벨 변경 불가

대신 프로토콜의 접근 레벨이 익스텐션의 프로토콜 구현에서 사용됨

프라이빗 멤버

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

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

위와 같이 프로토콜을 선언하고 아래와 같이 구조체를 하나 선언 후 private 변수를 하나 선언

struct SomeStruct {
    private var privateVariable = 12
}

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

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

제네릭

제네릭 타입 혹은 함수는 제네릭 타입 혹은 함수 자체 그리고 타입 파라미터의 접근레벨의 최소 접근 레벨을 갖는다

타입 별칭 ( Type Aliases )

타입 별칭은 별칭을 붙인 타입보다 같거나 낮은 접근 레벨을 갖음

예를들어 private 타입의 별칭은 private, file-private, internal, public, open 타입의 접근권한을 가짐

public 타입의 별칭은 internal, file-private, private 를 가질 수 없음