결론부터 말씀드리면, access control을 고민할 때는 다음과 같을 것입니다:
이전에는 단일 모듈 앱을 만드는 경우밖에 없었기때문에, "private을 쓰면 코드 성능이 좋아진다"라는 단순한 이유로 붙혔었는데, 한번 천천히 읽어보시면 적어도 access control에 대해서는 내가 작성하는 코드에 대해 좀더 고민해 볼 수 있을 것 같습니다 :)
아래에 작성된 포스트는 Swift 공식 문서의 Access Control을 한글로 번역 및 정리한 내용입니다.
선언, 파일 및 모듈 별로 코드의 가시성을 관리할 수 있다.
액세스 컨트롤(access control)은 다른 소스 파일 및 모듈에서 코드 일부에 대한 액세스를 제한하는 기능입니다. 이 기능을 사용하면 코드의 구현 세부 정보를 숨길 수 있으며, 해당 코드에 액세스하고 사용할 선호 인터페이스를 지정할 수 있습니다.
개별 타입(class, struct 및 enumeration)과 해당 유형에 속한 property, method, initializer 및 subscript에 대해 특정 액세스 수준을 할당할 수 있습니다. 프로토콜도 특정 컨텍스트로 제한할 수 있으며, 전역 상수, 변수 및 함수도 마찬가지입니다.
Swift는 다양한 수준의 액세스 컨트롤을 제공함과 동시에 일반적인 시나리오에 대한 기본 액세스 수준을 제공하여 명시적인 액세스 컨트롤 수준을 지정해야 하는 필요성을 줄입니다. 실제로, 단일 대상 앱을 작성하는 경우 명시적인 액세스 컨트롤 수준을 전혀 지정할 필요가 없을 수도 있습니다.
액세스 컨트롤은 코드 일부에 대한 액세스를 제한하여 구현 세부 정보를 숨기고 인터페이스를 지정하는 기능이다. 타입, 프로퍼티, 메서드, 이니셜라이저, subscript 등의 액세스 수준을 할당할 수 있으며, Swift는 다양한 수준의 액세스 컨트롤을 제공하면서도 기본 액세스 수준을 제공하여 명시적인 액세스 컨트롤 수준을 지정해야 하는 필요성을 줄인다.
Note
액세스 컨트롤이 적용될 수 있는 코드의 여러 측면(속성, 유형, 함수 등)은 아래 섹션에서 간결하게 "entity"라고 지칭합니다.
모듈은 코드 배포의 단일 단위인 프레임워크 또는 응용 프로그램으로, Swift의 import 키워드로 다른 모듈에서 가져올 수 있습니다.
Xcode의 각 빌드 대상(예: 앱 번들 또는 프레임워크)은 Swift에서 별개의 모듈로 처리됩니다. 앱 코드의 일부를 독립 실행형 프레임워크로 묶어 여러 응용 프로그램에서 코드를 캡슐화하고 재사용할 수 있도록 한 경우, 해당 프레임워크 내에서 정의한 모든 것은 응용 프로그램 내에서 가져와 사용하거나 다른 프레임워크 내에서 사용될 때 별도의 모듈의 일부가 됩니다.
소스 파일은 모듈 내의 단일 Swift 소스 코드 파일입니다. 예를 들어 앱이나 프레임워크 내에서 하나의 파일입니다. 일반적으로는 개별 타입을 별도의 소스 파일에 정의합니다. 하지만 하나의 소스 파일에서 여러 개의 타입, 함수 등을 정의할 수도 있습니다.
즉, 모듈은 하나 이상의 소스 파일로 구성될 수 있으며, 소스 파일은 여러 개의 타입, 함수 등을 포함할 수 있습니다. 이 소스 파일을 통해 모듈 내에 코드를 구성하고, 모듈 간에 코드를 공유할 수 있습니다.
Swift는 코드 내의 entity에 대해 다섯 가지 다른 access level을 제공합니다. 이 access level은 entity가 정의된 소스 파일과 소스 파일이 속한 모듈에 상대적입니다.
open 접근과 public 접근은 엔티티가 정의된 모듈에서는 물론, 해당 모듈을 import한 다른 모듈의 소스 파일에서도 사용될 수 있습니다. 보통 open 또는 public 접근은 프레임워크의 공개 인터페이스를 지정할 때 사용합니다. open 접근과 public 접근의 차이는 아래 Subclassing 항목에서 설명합니다.
internal 접근은 entity가 정의된 모듈 내의 모든 소스 파일에서 사용될 수 있지만, 그 밖의 소스 파일에서는 사용될 수 없습니다. internal 접근은 앱이나 프레임워크의 내부 구조를 정의할 때 사용합니다.
file-private 접근은 엔티티가 정의된 소스 파일 내에서만 사용될 수 있습니다. file-private 접근은 파일 전체에서 사용되는 구체적인 기능의 구현 세부 정보를 숨기기 위해 사용됩니다.
private 접근은 엔티티를 포함한 선언과 해당 선언과 동일한 파일의 확장에서만 사용될 수 있습니다. private 접근은 단일 선언 내에서만 사용되는 구체적인 기능의 구현 세부 정보를 숨기기 위해 사용됩니다.
open 접근은 가장 높은(가장 제한이 적은) 접근 수준이며, private 접근은 가장 낮은(가장 제한이 많은) 접근 수준입니다.
open 접근은 클래스와 클래스 멤버에만 적용되며, public 접근과 다른 점은 서브클래싱(아래 "서브클래싱" 항목 참조)을 허용한다는 것입니다. 클래스를 open으로 표시하면 다른 모듈에서 해당 클래스를 슈퍼클래스로 사용하는 코드의 영향을 고려하고 클래스 코드를 설계했다는 것을 명시합니다.
Swift에서 Access level의 전반적인 지침 원칙은 다음과 같습니다: 보다 제한적인 접근 수준을 가진 다른 entity에 대해 정의될 수 없습니다.
예시:
public 변수는 internal, file-private, 또는 private 타입으로 정의될 수 없습니다. 왜냐하면 public 변수를 사용하는 모든 곳에서 해당 타입을 사용할 수 없을 수 있기 때문입니다.
함수는 그 함수를 사용하는 코드에서 자신의 구성 요소 타입이 사용 불가능한 상황이 될 수 있기 때문에, 해당 함수의 매개변수 타입과 반환 타입보다 높은 액세스 레벨을 가질 수 없습니다.
언어의 다른 측면에 대한 이러한 지침의 구체적인 영향은 아래에서 자세히 다룹니다.
기본적으로, 몇 가지 특정한 예외를 제외하고는 코드 내의 모든 entity는 별도의 명시적인 access level을 지정하지 않으면 내부(internal) access level을 가집니다. 따라서 대부분의 경우에는 코드 내에서 명시적인 access level을 지정할 필요가 없습니다.
단일 대상 앱의 앱 코드는 일반적으로 앱 내에서 자체적으로 완결되며 앱 모듈 외부에서 사용할 필요가 없습니다. 내부의 기본 액세스 수준인 internal이 이미 이 요구 사항을 충족합니다. 따라서 사용자 정의 액세스 수준을 지정할 필요가 없습니다. 그러나 앱 모듈 내 다른 코드에서 해당 구현 세부 정보를 숨기기 위해 일부 코드를 file-private 또는 private으로 표시할 수 있습니다.
프레임워크를 개발할 때는, 해당 프레임워크의 외부에서 접근 가능한 인터페이스를 open
또는 public
으로 표시하여 다른 모듈(프레임워크를 가져오는 앱 등)에서 볼 수 있고 접근할 수 있도록 합니다. 이러한 외부 인터페이스를 API(Application Programming Interface)라고 합니다.
Note
당신의 프레임워크의 내부 구현 세부 사항은 여전히 기본 접근 레벨인 internal을 사용하거나, 해당 세부사항을 프레임워크의 다른 내부 코드에서 숨기고 싶을 경우 private 또는 file-private로 표시할 수 있습니다. open 또는 public으로 엔티티를 표시해야 하는 경우는 해당 엔티티를 프레임워크의 API의 일부로 만들고자 하는 경우에 해당합니다.
앱에 유닛 테스트 타겟을 포함시키는 경우, 앱의 코드는 테스트 모듈에서 사용 가능해야합니다. 기본적으로, 다른 모듈에서 액세스할 수 있는 것은 open 또는 public으로 표시된 엔티티뿐입니다. 그러나 @testable 속성을 사용하여 제품 모듈을 가져올 때 해당 모듈을 테스트 가능하게 컴파일하면, 단위 테스트 타겟에서는 모든 internal 엔티티에 액세스 할 수 있습니다.
엔터티의 선언 앞에 open, public, internal, fileprivate, 또는 private 중 하나의 수정자를 배치하여 엔터티의 액세스 레벨을 정의합니다.
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() {}
명시하지 않는 한, 기본 접근 수준은 Default Access Levels에서 설명한 대로 내부(internal)입니다. 이는 SomeInternalClass와 someInternalConstant를 명시적인 접근 수준 지정자 없이 작성할 수 있으며, 여전히 internal 접근 수준을 갖게 됩니다.
class SomeInternalClass {} // implicitly internal
let someInternalConstant = 0 // implicitly internal
만약 사용자 정의 타입에 명시적인 접근 수준을 지정하려면 타입을 정의하는 지점에서 그렇게 하십시오. 그러면 새로운 타입은 해당 접근 수준이 허용하는 모든 곳에서 사용할 수 있습니다. 예를 들어, file-private 클래스를 정의하면 해당 클래스는 file-private 클래스가 정의된 소스 파일에서만 속성의 타입으로 사용하거나 함수 매개변수 또는 반환 타입으로 사용할 수 있습니다.
타입의 접근 수준은 그 타입의 멤버(속성, 메서드, 이니셜라이저 및 서브스크립트)의 기본 접근 수준에도 영향을 미칩니다. 타입의 접근 수준을 private 또는 file private로 정의하면 해당 타입의 멤버의 기본 접근 수준도 private 또는 file private이 됩니다. 타입의 접근 수준을 internal 또는 public으로 정의하거나(또는 명시적으로 접근 수준을 지정하지 않고 내부의 기본 접근 수준인 internal을 사용하는 경우), 타입의 멤버의 기본 접근 수준은 internal이 됩니다.
중요
public 타입은 기본적으로 public 멤버가 아닌 internal 멤버를 가집니다. 만약 타입 멤버를 공개하려면 명시적으로 해당 멤버를 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가 됩니다.
note
클래스, 구조체, 열거형 및 함수와 같이 튜플 타입에 대한 독립적인 정의가 없습니다. 튜플 타입의 접근 수준은 튜플 타입을 구성하는 타입에서 자동으로 결정되며, 명시적으로 지정할 수 없습니다.
함수 타입의 접근 수준은 함수의 매개변수 타입과 반환 타입의 가장 제한적인(access level) 접근 수준을 계산하여 결정됩니다. 만약 함수의 계산된 접근 수준이 컨텍스트 기본값과 일치하지 않는다면 함수 정의에 접근 수준을 명시해야 합니다.
아래 예시에서는 someFunction()이라는 전역 함수를 명시적으로 함수 자체의 접근 수준을 지정하지 않고 정의하고 있습니다. 이 함수는 기본 접근 수준인 "internal"을 갖는 것으로 예상할 수 있습니다. 하지만 실제로는 그렇지 않습니다. 사실, 아래와 같이 작성하면 someFunction()은 컴파일되지 않습니다.
func someFunction() -> (SomeInternalClass, SomePrivateClass) {
// function implementation goes here
}
이 함수의 반환 타입은 Custom Types에서 정의된 두 사용자 정의 클래스 중 하나가 internal로 정의되고, 다른 하나는 private로 정의되어 있습니다. 따라서, 이 복합 튜플 타입의 전반적인 접근 레벨은 private입니다(튜플을 이루는 구성 타입 중 가장 낮은 접근 레벨).
함수의 반환 타입이 private이기 때문에, 함수 선언에서 전반적인 접근 레벨에 private 수식어를 표시해야 유효합니다.
private func someFunction() -> (SomeInternalClass, SomePrivateClass) {
// function implementation goes here
}
함수 someFunction()의 정의에 public 또는 internal 수정자를 표시하거나, 기본 설정인 internal을 사용하는 것은 유효하지 않습니다. 왜냐하면 함수의 반환 타입에 사용된 private 클래스에 대한 적절한 접근 권한을 가지지 않은 public 또는 internal 사용자가 있을 수 있기 때문입니다.
열거형의 개별 case는 자동으로 해당하는 열거형과 동일한 접근 수준을 받습니다. 개별 열거형 case에 대해 다른 접근 수준을 지정할 수 없습니다.
아래의 예시에서 CompassPoint 열거형은 명시적으로 public 접근 수준을 가지고 있습니다. 따라서 north, south, east, west와 같은 열거형 case들은 모두 public 접근 수준을 가집니다:
public enum CompassPoint {
case north
case south
case east
case west
}
열거형 정의에서 사용하는 원시 값 또는 연관 값의 유형은 해당 열거형의 접근 수준보다 적어도 같은 접근 수준을 가져야 합니다. 예를 들어, 내부 접근 수준을 갖는 열거형의 원시 값 유형으로 비공개 유형을 사용할 수 없습니다.
중첩 타입의 접근 수준은 포함 타입과 동일합니다. 다만, 포함 타입이 public인 경우 예외입니다. public 타입 내에 정의된 중첩 타입은 자동으로 internal의 접근 수준을 가집니다. public 타입 내의 중첩 타입을 공개적으로 사용하려면 중첩 타입을 명시적으로 public으로 선언해야 합니다.
동일 모듈에 정의된 클래스는 현재 액세스 컨텍스트에서 액세스 가능하면 그것을 서브클래스로 만들 수 있습니다. 또한, 다른 모듈에서 정의된 open 클래스도 서브클래싱이 가능합니다. 하지만 서브클래스는 그것의 수퍼클래스보다 더 높은 액세스 레벨을 가질 수 없으며, 즉 internal 수퍼클래스의 public 서브클래스를 작성할 수 없습니다.
또한, 동일한 모듈에 정의된 클래스의 경우, 특정 액세스 컨텍스트에서 볼 수 있는 모든 클래스 멤버(메서드, 프로퍼티, 이니셜라이저 또는 서브스크립트)를 오버라이드할 수 있습니다. 다른 모듈에 정의된 클래스의 경우, 모든 오픈 클래스 멤버를 오버라이드할 수 있습니다.
오버라이드는 상속된 클래스 멤버를 수퍼클래스 버전보다 더 접근 가능하게 만들 수 있습니다. 아래 예제에서 클래스 A는 someMethod()라는 fileprivate 메서드가 있는 public 클래스입니다. 클래스 B는 A의 서브클래스이며 "internal"이라는 낮은 액세스 레벨을 가지고 있습니다. 그럼에도 불구하고, 클래스 B는 someMethod()를 "internal" 액세스 레벨로 오버라이드 하여 someMethod()의 원래 구현보다 더 높은 액세스 레벨을 가지게 됩니다.
public class A {
fileprivate func someMethod() {}
}
internal class B: A {
override internal func someMethod() {}
}
하위 클래스 멤버에서 상위 클래스 멤버를 호출하는 것이 허용되는 경우가 있습니다. 단, 이 경우 상위 클래스 멤버의 액세스 권한이 하위 클래스 멤버보다 낮아야 하며, 호출은 허용된 액세스 레벨 컨텍스트(즉, fileprivate 멤버 호출의 경우 상위 클래스와 같은 소스 파일 내에서, 내부 멤버 호출의 경우 상위 클래스와 같은 모듈 내에서)에서 발생해야 합니다.
public class A {
fileprivate func someMethod() {}
}
internal class B: A {
override internal func someMethod() {
super.someMethod()
}
}
슈퍼클래스 A와 서브클래스 B가 같은 소스 파일에 정의되어 있기 때문에, B의 someMethod() 구현에서 super.someMethod()를 호출하는 것이 유효합니다.
상수, 변수 또는 프로퍼티는 해당 타입보다 더 공개적일 수 없습니다. 예를 들어, private 타입을 사용하여 public 프로퍼티를 작성하는 것은 유효하지 않습니다. 마찬가지로, subscript는 인덱스 타입 또는 반환 타입 중에서 가장 제한적인 접근 수준보다 더 공개적일 수 없습니다.
만약 상수, 변수, 프로퍼티 또는 subscript가 private 타입을 사용하는 경우, 해당 상수, 변수, 프로퍼티 또는 subscript도 private으로 표시해야 합니다.
private var privateInstance = SomePrivateClass()
상수, 변수, 프로퍼티, 및 서브스크립트에 대한 getter와 setter는 자동으로 해당 상수, 변수, 프로퍼티, 또는 서브스크립트와 동일한 접근 수준을 받습니다.
읽기 및 쓰기 범위를 제한하기 위해 setter에게 getter와 대응하는 것보다 낮은 접근 수준을 부여할 수 있습니다. var 또는 subscript 선언 앞에 fileprivate(set), private(set), 또는 internal(set)을 작성하여 낮은 접근 수준을 할당할 수 있습니다.
note
이 규칙은 저장 프로퍼티와 계산 프로퍼티 모두에 적용됩니다. 저장 프로퍼티에 대해 명시적인 getter와 setter를 작성하지 않아도, Swift는 여전히 암시적 getter와 setter를 합성하여 저장 프로퍼티의 지원 저장소에 대한 접근을 제공합니다. 계산 프로퍼티의 명시적인 setter와 정확히 동일한 방식으로 합성된 setter의 접근 수준을 변경하려면 fileprivate(set), private(set), internal(set)를 사용합니다.
아래 예제는 문자열 프로퍼티가 수정된 횟수를 추적하는 TrackedString이라는 구조체를 정의합니다:
struct TrackedString {
private(set) var numberOfEdits = 0
var value: String = "" {
didSet {
numberOfEdits += 1
}
}
}
TrackedString 구조체는 초기값이 ""(빈 문자열)인 저장형 문자열 프로퍼티인 value를 정의합니다. 또한, value가 수정된 횟수를 추적하는 데 사용되는 저장형 정수형 프로퍼티인 numberOfEdits도 정의합니다. 이 수정 추적은 value 프로퍼티의 didSet 프로퍼티 옵저버를 사용하여 value 프로퍼티가 새 값으로 설정될 때마다 numberOfEdits를 증가시킴으로써 구현됩니다.
TrackedString 구조체와 value 프로퍼티는 명시적인 접근 수준 한정자를 제공하지 않으므로 모두 기본 접근 수준인 internal을 받습니다. 그러나 numberOfEdits 프로퍼티의 접근 수준은 private(set) 한정자로 표시되어 getter는 여전히 기본 접근 수준인 internal을 갖지만, 프로퍼티는 TrackedString 구조체의 일부인 코드 내에서만 설정할 수 있습니다. 이를 통해 TrackedString은 내부적으로 numberOfEdits 프로퍼티를 수정하지만, 구조체 정의 외부에서 사용될 때 읽기 전용 프로퍼티로 표시할 수 있습니다.
TrackedString 인스턴스를 만들고 문자열 값을 몇 번 수정하면 numberOfEdits 프로퍼티 값이 수정 횟수와 일치하는 것을 확인할 수 있습니다.
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"
다른 소스 파일에서 numberOfEdits 프로퍼티의 현재 값을 쿼리할 수는 있지만, 이 속성을 다른 소스 파일에서 수정할 수는 없습니다. 이 제한은 TrackedString 편집 추적 기능의 구현 세부 정보를 보호하면서도 해당 기능의 측면에 편리한 접근을 제공합니다.
getter와 setter 모두에 대해 필요한 경우 명시적 액세스 수준을 할당할 수 있음을 유의하십시오. 아래 예제는 구조체 TrackedString의 버전을 보여줍니다. 이 구조체는 명시적으로 public 액세스 수준으로 정의되므로, 구조체의 멤버(포함하여 numberOfEdits 프로퍼티)는 기본적으로 내부 액세스 수준을 갖습니다. public과 private(set) 액세스 수준 수정자를 결합하여 구조체의 numberOfEdits 프로퍼티 getter를 public으로 만들고, 해당 프로퍼티 setter를 private으로 만들 수 있습니다.
public struct TrackedString {
public private(set) var numberOfEdits = 0
public var value: String = "" {
didSet {
numberOfEdits += 1
}
}
public init() {}
}
사용자 정의 initializer가 선언된 타입의 접근 제한 레벨보다 더 낮은 접근 제한 레벨을 가질 수 있습니다. required initializer의 경우, 해당 클래스의 접근 제한 수준과 동일한 접근 제한 수준이 있어야 합니다.
예를 들어, 클래스가 internal로 선언되면 public initializer를 가질 수 없지만, internal initializer를 가질 수 있습니다. 반면, required initializer는 internal로 선언된 클래스에서도 internal로 선언되어야 합니다.
함수 및 메서드 매개 변수와 마찬가지로, 초기화자 매개 변수의 유형은 해당 초기화자의 접근 수준보다 더 낮을 수 없습니다.
기본 이니셜라이저(Default Initializers)에 설명된 대로, 모든 속성에 기본값을 제공하고 자체 이니셜라이저를 하나 이상 제공하지 않는 구조체 또는 기본 클래스에 대해, Swift는 인자 없이 기본 이니셜라이저를 자동으로 제공합니다.
기본 이니셜라이저는 초기화 대상 타입과 동일한 접근 수준을 가집니다. 단, public으로 정의된 타입의 기본 이니셜라이저는 internal로 간주됩니다. 다른 모듈에서 인자 없이 public 타입을 초기화하기를 원한다면, 타입 정의 일부로 명시적으로 public 인자 없는 이니셜라이저를 제공해야 합니다.
구조체 유형의 default memberwise initializer는 구조체의 저장 프로퍼티 중 어떤 것이 private이면 해당 initializer는 private으로 간주됩니다. 마찬가지로 구조체의 저장 프로퍼티 중 하나라도 file private이면, 해당 initializer는 file private입니다. 그렇지 않으면 initializer의 접근 레벨은 internal입니다.
위의 기본 initializer와 마찬가지로, 다른 모듈에서 사용할 때 public 구조체 유형을 멤버 와이즈 초기화자로 초기화하려면 유형의 정의 일부로 명시적으로 public memberwise initializer를 제공해야 합니다.
프로토콜 타입에 명시적인 접근 레벨을 할당하려면 프로토콜을 정의하는 시점에서 그렇게 하십시오. 이렇게 하면 특정 액세스 컨텍스트 내에서만 채택할 수 있는 프로토콜을 만들 수 있습니다.
프로토콜 정의 내의 각 요구 사항의 액세스 레벨은 자동으로 프로토콜과 동일한 액세스 레벨로 설정됩니다. 프로토콜 요구 사항을 프로토콜이 지원하는 것과 다른 액세스 레벨로 설정할 수 없습니다. 이렇게 하면 프로토콜을 채택한 모든 타입에서 프로토콜의 모든 요구 사항이 볼 수 있도록 보장됩니다.
Note
public 프로토콜을 정의하면, 해당 요구 사항은 구현될 때 public 액세스 레벨이 필요합니다. 이 동작은 다른 타입과 다르며, public 타입 정의는 타입 멤버의 internal 액세스 레벨을 의미하지 않습니다.
기존 프로토콜을 상속하는 새로운 프로토콜을 정의할 경우, 새로운 프로토콜의 접근 수준은 최대 기존 프로토콜과 같거나 작아야 합니다. 예를 들어, 내부(internal) 프로토콜을 상속하는 공개(public) 프로토콜을 작성할 수는 없습니다.
타입은 자신보다 낮은 접근 수준을 가진 프로토콜을 따를 수 있습니다. 예를 들어, 타입은 다른 모듈에서 사용될 수 있지만 internal 프로토콜의 구현은 정의 모듈 내에서만 사용될 수 있는 public 타입을 정의할 수 있습니다.
타입이 특정 프로토콜을 따르는 컨텍스트는 타입의 접근 수준과 프로토콜의 접근 수준 중 최소한의 것입니다. 예를 들어, 타입이 public이지만 따르는 프로토콜이 internal이면, 타입의 프로토콜 준수 또한 internal입니다.
타입이 특정 프로토콜을 준수하기 위해 구현할 때, 타입의 프로토콜 준수와 같거나 높은 접근 수준으로 각 프로토콜 요구사항을 구현해야 합니다. 예를 들어, public 타입이 internal 프로토콜을 준수한다면, 해당 타입의 프로토콜 요구사항 구현은 적어도 internal이어야 합니다.
note
Swift에서 Objective-C와 마찬가지로, 프로토콜 준수는 전역적입니다. 즉, 한 프로그램 내에서 타입이 동일한 프로토콜을 두 가지 다른 방식으로 준수할 수 없습니다.
클래스, 구조체 또는 열거형을 어떤 접근 레벨에서든 확장할 수 있습니다. 확장에 추가된 어떤 타입 멤버든 기본 접근 레벨은 원래 확장되는 타입에서 선언된 타입 멤버와 동일합니다. public 또는 internal 타입을 확장하면 추가된 어떤 타입 멤버도 기본 접근 레벨이 internal입니다. file-private 타입을 확장하면 추가된 어떤 타입 멤버도 기본 접근 레벨이 file-private입니다. private 타입을 확장하면 추가된 어떤 타입 멤버도 기본 접근 레벨이 private입니다.
또는 확장을 명시적인 접근 레벨 수정자(예: private)로 표시하여 확장 내에서 정의된 모든 멤버의 새로운 기본 접근 레벨을 설정할 수 있습니다. 이 새로운 기본값은 여전히 확장에서 개별 타입 멤버에 대해 재정의될 수 있습니다.
프로토콜 적용을 위해 확장(extension)을 사용하는 경우에는 명시적인 접근 수준 수정자(access-level modifier)를 제공할 수 없습니다. 이 경우, 각 프로토콜 요구사항 구현의 기본 접근 수준은 해당 프로토콜 자체의 접근 수준을 사용합니다.
동일한 파일에 있는 클래스, 구조체 또는 열거형을 확장하는 확장(extension)은 확장(extension) 내 코드가 원래 타입(type)의 선언 부분의 일부로 작성된 것처럼 동작합니다. 결과적으로 다음을 할 수 있습니다.
원래 선언 부분에서 private 멤버를 선언하고, 해당 멤버를 동일 파일 내의 확장(extension)에서 접근할 수 있습니다.
하나의 확장(extension)에서 private 멤버를 선언하고, 해당 멤버를 동일 파일 내의 다른 확장(extension)에서 접근할 수 있습니다.
확장(extension)에서 private 멤버를 선언하고, 해당 멤버를 동일 파일 내 원래 선언 부분에서 접근할 수 있습니다.
이 동작은 타입(type)이 private entity를 갖고 있더라도 코드를 구성하는 데 동일한 방식으로 확장(extension)을 사용할 수 있다는 것을 의미합니다. 예를 들어, 다음과 같은 간단한 프로토콜이 주어졌을 때:
protocol SomeProtocol {
func doSomething()
}
다음과 같이 익스텐션을 사용하여 프로토콜 적합성을 추가할 수 있습니다:
struct SomeStruct {
private var privateVariable = 12
}
extension SomeStruct: SomeProtocol {
func doSomething() {
print(privateVariable)
}
}
제네릭 타입 또는 제네릭 함수의 접근 수준은 해당 제네릭 타입 또는 함수의 접근 수준과 해당 타입 매개변수의 타입 제약 조건의 접근 수준 중 낮은 수준으로 결정됩니다.
정의하는 타입 별칭은 액세스 제어의 목적으로 별개의 타입으로 처리됩니다. 타입 별칭은 자신이 별칭을 하는 타입의 액세스 수준보다 작거나 같은 액세스 수준을 가질 수 있습니다. 예를 들어, private 타입 별칭은 private, file-private, internal, public 또는 open 타입에 별칭을 지을 수 있지만, public 타입 별칭은 internal, file-private 또는 private 타입에는 별칭을 지을 수 없습니다.
note
이 규칙은 프로토콜 일치를 충족하는 데 사용되는 연관된 타입에 대한 타입 별칭에도 적용됩니다.