스위프트의 접근제어는 모듈과 소스파일을 기반으로 설계되었다.
모듈(Module)은 배포할 코드의 묶음 단위이다. 통상 하나의 프레임워크(Framework)나 라이브러리(Library) 또는 애플리케이션(Application)이 모듈 단위가 될 수 있다. 스위프트에서는 import 키워드를 사용해 불러온다.
소스파일은 하나의 스위프트 소스 코드 파일을 의미힌다. 자바나 Objective-C와 같은 기존 의 프로그래밍 언어에서는 통상 파일 하나에 타입을 하나만 정의한다. 스위프트에서도 보통 파일 하나에 타입 하나만 정의하지만, 때로는 소스파일 하나에 여러 타입(여러 개의 클래스나 구조체, 열거형 등)이나 함수 등 많은 것을 정의하거나 구현할 수도 있다.
접근제어는 접근수준(Access Level) 키워드를 통해 구현할 수 있다.
각 타입(클래스, 구조체, 열 거형 등)에 특정 접근수준을 지정할 수 있고, 타입 내부의 프로퍼티, 메서드, 이니셜라이저, 서브스크립트 각각에도 접근수준을 지정할 수 있다.
접근수준을 명시할 수 있는 키워드는 open, public, internal, fileprivate, private 다섯 가지가 있다.
스위프트의 접근수준은 기본적으로 모듈과 소스파일에 따라 구분합니다.
접근수준 | 키워드 | 범위 | 비고 |
---|---|---|---|
개방 접근수준 | open | 모듈 외부까지 | 클래스에서만 사용 |
공개 접근수준 | public | 모듈 외부까지 | - |
내부 접근수준 | internal | 모듈 내부 | - |
파일외부비공개 접근수준 | fileprivate | 파일 내부 | - |
비공개 접근수준 | private | 기능정의 내부 | - |
public 키워드로 접근수준이 지정된 요소는 어디서든 쓰일 수 있다. 자신이 구현된 소스 파일은 물론, 그 소스파일이 속해 있는 모듈, 그 모듈을 가져다 쓰는 모듈 등 모든 곳에서 사용할 수 있다. 공개(Public) 접근수준은 주로 프레임워크에서 외부와 연결될 인터페이스를 구현하는데 많이 쓰인다. 우리가 사용하는 스위프트의 기본 요소는 모두 공개 접근수준으로 구현 되어 있다고 생각하면 된다.
open 키워드로 지정할 수 있는 개방(Open) 접근수준은 공개(Public) 접근수준 이상으로 높은 접근수준이며, 클래스와 클래스의 멤버에서만 사용할 수 있다.
기본적으로 공개 접근수준과 비슷하지만 다음과 같은 차이점이 있다.
- 개방 접근수준을 제외한 다른 모든 접근수준의 클래스는 그 클래스가 정의된 모듈 안에서만 상속할 수 있다.
- 개방 접근수준을 제외한 다른 모든 접근수준의 클래스 멤버는 해당 멤버가 정의된 모듈 안에서만 재정의할 수 있다.
- 개방 접근수준의 클래스는 그 클래스가 정의된 모듈 밖의 다른 모듈에서도 상속할 수 있다.
- 개방 접근수준의 클래스 멤버는 해당 멤버가 정의된 모듈 밖의 다른 모듈에서도 재정의(override)할 수 있다.
- 클래스를 개방 접근수준으로 명시하는 것은 그 클래스를 다른 모듈에서도 부모클래스로 사용 하겠다는 목적으로 클래스를 설계하고 코드를 작성했음을 의미한다.
internal 키워드로 지정하는 내부(Internal) 접근수준은 기본적으로 모든 요소에 암묵적으로 지정하는 기본 접근수준이다. 내부 접근수준으로 지정된 요소는 소스파일이 속해 있는 모듈 어디에서든 쓰일 수 있다. 다만 그 모듈을 가져다 쓰는 외부 모듈에서는 접근할 수 없다. 보통 외부에서 사용할 클래스나 구조체가 아니며, 모듈 내부에서 광역적으로 사용할 경우 내부 접근수준을 지정한다.
파일외부비공개(File-private) 접근수준으로 지정된 요소는 그 요소가 구현된 소스파일 내부에서만 사용할 수 있다. 해당 소스파일 외부에서 값이 변경되거나 함수를 호출하면 부작용이 생길 수 있는 경우에 사용하면 좋다.
비공개(Private) 접근수준은 가장 한정적인 범위이다. 비공개 접근수준으로 지정된 요소는 그 기능을 정의하고 구현한 범위 내에서만 사용할 수 있다. 비공개 접근수준으로 지정한 기능은 심지어 같은 소스파일 안에 구현한 다른 타입이나 기능에서도 사용할 수 없다.
모든 타입에 적용되는 접근수준의 규칙은 ‘상위 요소보다 하위 요소가 더 높은 접근수준을 가 질 수 없다’이다.
비공개 접근수준으로 정의한 구조체 내부의 프로퍼티로 내부수준이나 공개수준을 갖는 프로퍼티를 정의할 수 없다. 또, 함수의 매개변수로 특정 접근수준이 부여된 타입이 전달되거나 반환된다면, 그 타입의 접근수준보다 함 수의 접근수준이 높게 설정될 수 없다.
// 잘못된 접근수준 부여
private class AClass {
// 공개 접근수준을 부여해도 AClass의 접근수준이 비공개 접근수준이므로
// 이 메서드의 접근수준도 비공개 접근수준으로 취급된다.
public func someMethod() {
// ...
}
}
// AClass의 접근수준이 비공개 접근수준이므로
// 공개 접근수준 함수의 매개변수나 반환 값 타입으로 사용할 수 없다.
public func someFunction(a: AClass) -> AClass {
// 오류 발생!
return a
}
같은 파일 내부에서 private 접근수준과 fileprivate 접근수준은 사용할 때 분명한 차이가 있다. fileprivate 접근수준으로 지정한 요소는 같은 파일 어떤 코드에서도 접근할 수 있다.
반면에 private 접근수준으로 지정한 요소는 같은 파일 내부에 다른 타입의 코드가 있더라도 접근이 불가능하다. 그러나 자신을 확장하는 익스텐션(extension) 코드가 같은 파일에 존재하는 경우에는 접근할 수 있다.
public struct SomeType {
private var privateVariable = 0
fileprivate var fileprivateVariable = 0
}
// 같은 타입의 익스텐션에서는 private 요소에 접근 가능
extension SomeType {
public func publicMethod() {
print("\(self.privateVariable), \(self.fileprivateVariable)")
}
private func privateMethod() {
print("\(self.privateVariable), \(self.fileprivateVariable)")
}
fileprivate func fileprivateMethod() {
print("\(self.privateVariable), \(self.fileprivateVariable)")
}
}
struct AnotherType {
var someInstance: SomeType = SomeType()
mutating func someMethod() {
// public 접근수준에는 어디서든 접근 가능
self.someInstance.publicMethod() // 0, 0
// 같은 파일에 속해 있는 코드이므로 fileprivate 접근수준 요소에 접근 가능
self.someInstance.fileprivateVariable = 100
self.someInstance.fileprivateMethod() // 0, 100
// 다른 타입 내부의 코드이므로 private 요소에 접근 불가! 오류!
// self.someInstance.privateVariable = 100
// self.someInstance.privateMethod()
}
}
var anotherInstance = AnotherType()
anotherInstance.someMethod()
구조체 또는 클래스를 사용하여 저장 프로퍼티를 구현할 때는 허용된 접근수준에서 프로퍼티 값을 가져갈 수 있다. 값을 변경할 수 없도록 구현하고 싶다면 설정자(Setter)만 더 낮은 접근수준을 갖도록 제한할 수 있다. 요소의 접근수준 키워드 뒤에 {접근수준}(set)처럼 표현하면 설정자의 접근수준만 더 낮도록 지정해줄 수 있다.
설정자 접근수준 제한은 프로퍼티, 서브스크립트, 변수 등에 적용될 수 있으며, 해당 요소의 접근수준보다 같거나 낮은 수준으로 제한해주어야 한다.
public struct SomeType {
// 비공개 접근수준 저장 프로퍼티 count
private var count: Int = 0
// 공개 접근수준 저장 프로퍼티 publicStoredProperty
public var publicStoredProperty: Int = 0
// 공개 접근수준 저장 프로퍼티 publicGetOnlyStoredProperty
// 설정자는 비공개 접근수준
public private(set) var publicGetOnlyStoredProperty: Int = 0
// 내부 접근수준 연산 프로퍼티 internalComputedProperty
internal var internalComputedProperty: Int {
get {
return count
}
set {
count += 1
}
}
// 내부 접근수준 저장 프로퍼티 internalGetOnlyComputedProperty
// 설정자는 비공개 접근수준
internal private(set) var internalGetOnlyComputedProperty: Int {
get {
return count
}
set {
count += 1
}
}
}
var someInstance: SomeType = SomeType()
// 외부에서 접근자, 설정자 모두 사용 가능
print(someInstance.publicStoredProperty) // 0
someInstance.publicStoredProperty = 100
// 외부에서 접근자만 사용 가능
print(someInstance.publicGetOnlyStoredProperty) // 0
//someInstance.publicGetOnlyStoredProperty = 100 // 오류 발생
// 외부에서 접근자, 설정자 모두 사용 가능
print(someInstance.internalComputedProperty) // 0
someInstance.internalComputedProperty = 100
// 외부에서 접근자만 사용 가능
print(someInstance.internalGetOnlyComputedProperty) // 1
//someInstance.internalGetOnlyComputedProperty = 100 // 오류 발생