포스트

Swift 접근 제어 알아보기 ②

Swift는 클래스뿐만 아니라 구조체, 열거형과 메서드, 프로퍼티에도 접근 제어 수준을 지정할 수 있습니다. 각 타입이 접근 제어 수준을 가질 때 가지는 규칙과 특징을 하나씩 알아보겠습니다.

Class 타입

클래서 선언 시작 부분에 접근 제어 수준을 지정해서 해당 클래스의 사용 범위를 제한할 수 있습니다. 클래스는 접근 제어 수준이 정하는 범위 내에서만 프로퍼티 혹은 매개변수나 반환 타입으로 사용될 수 있습니다. 만일 private이나 fileprivate 접근 제어 수준을 가지는 클래스는 동일한 파일에서만 사용될 수 있습니다. 아래는 이를 잘 보여주는 예제 코드입니다.

1
2
3
4
5
// main.swift

fileprivate class SomeFilePrivateClass { }

fileprivate var someProperty = SomeFilePrivateClass()
1
2
3
4
5
// another-main.swift

func someFunction() -> SomeFilePrivateClass { // Cannot find type 'SomeFilePrivateClass' in scope
    // ...
}

클래스가 어느 접근 제어 수준을 가지는지에 따라 그 멤버의 접근 제어 수준이 결정됩니다. 클래스가 private 젭근 제어 수준을 가진다면 그 멤버도 모두 private 접근 제어 수준을 가집니다.

그런데 클래스가 open이나 public 접근 제어 수준을 가진다고 하더라도 그 멤버가 해당 접근 제어 수준을 가지는 건 아닙니다. 왜냐하면 Swift는 기본적으로 모든 엔터티를 internal 접근 제어 수준을 가진다고 생각하기 때문입니다. 그래서 해당 클래스의 멤버에 아무런 접근 제어 수준을 지정하지 않으면 internal 접근 제어 수준을 가지게 되며, 더 높은 접근 제어 수준으로 지정하고 싶으면 별도로 지정해야 합니다.

아래는 서로 다른 접근 제어 수준을 가지는 클래스가 그 멤버에 어떤 접근 제어 수준을 지정할 수 있는지를 보여주는 예제 코드입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class SomePublicClass {
    public func somePublicMethod() { }
    func someInternalMethod() { }
    fileprivate func someFilePrivateMethod() { }
    private func somePrivateMethod() { }
}

class SomeInternalClass {
    func someInternalMethod() { }
    fileprivate func someFilePrivateMethod() { }
    private func somePrivateMethod() { }
}

fileprivate class someFilePrivateClass {
    fileprivate func someFilePrivateMethod() { }
    private func somePrivateMethod() { }
}

눈치채셨겠지만, 엔터티와 그 멤버의 접근 제어 수준은 다르게 적용됩니다. 만약 public 접근 제어 수준을 가지는 클래스가 private 접근 제어 수준을 가지는 이니셜라이저를 정의했다면, 해당 클래스는 인스턴스(Instance) 생성이 불가능합니다. 왜냐하면 클래스 외부에서는 인스턴스를 생성하도록 도와주는 이니셜라이저에 접근이 불가능하기 때문입니다. 가장 익숙한 패턴이 바로 싱글톤(Singleton) 패턴입니다. 아래는 이를 잘 보여주는 예제 코드입니다.

1
2
3
4
5
6
7
public class NetworkManager {
    static let shared = NetworkManager()
    private init() { }
}

let networkManager = NetworkManager() 
// 'NetworkManager' initializer is inaccessible due to 'private' protection level

상속과 오버라이딩

클래스가 open 접근 제어 수준을 가지면 해당 클래스가 선언된 모듈뿐만 아니라 외부 모듈에서도 상속과 오버라이딩이 가능해집니다. 앞서 UIKit 프레임워크에 선언되어 있는 ViewController 클래스로 예를 들었습니다.

이 밖에 클래스가 접근 제어 수준이 정하는 범위 내라면 자유롭게 상속과 오버라이딩이 가능합니다. 하위 클래스의 접근 제어 수준은 상위 클래스의 접근 제어 수준보다 더 높을 수 없습니다. 예를 들어, internal 접근 제어 수준을 가지는 상위 클래스가 public 접근 제어 수준을 가지는 하위 클래스를 상속할 수 없습니다.

반면에, 하위 클래스에서 오버라이딩한 멤버는 상위 클래스의 멤버보다 더 높은 접근 제어 수준으로 지정하는 게 가능합니다. 아래는 이를 잘 보여주는 예제 코드입니다.

1
2
3
4
5
6
7
8
9
internal class Person {
    fileprivate func sayHello() { }
}

fileprivate class Korean: Person {
    override internal func sayHello() {
        print("안녕하세요!")
    }
}

상위 클래스의 멤버의 접근 제어 수준이 정하는 범위 내라면 하위 클래스가 상위 클래스의 멤버를 자유롭게 호출할 수 있습니다. 아래는 이를 잘 보여주는 예제 코드입니다.

1
2
3
4
5
fileprivate class Korean: Person {
    override internal func sayHello() {
        super.sayHello()
    }
}

Structure 타입

구조체는 클래스와 크게 다르지 않습니다. 다만, 구조체는 클래스와 다르게 open 접근 제어 구문을 적용할 수 없습니다.

Enum 타입

케이스(Case)의 접근 제어 수준은 열거형의 접근 제어 수준에 따르게 되어 있습니다. private 접근 제어 수준을 가지는 열거형은 케이스도 동일한 접근 제어 수준을 가집니다.

열거형의 원시값(RawValue)과 연관된 값(Associated Value)은 열거형의 접근 제어 수준보다 같거나 높아야 합니다. 아래는 이를 잘 보여주는 예제 코드입니다.

1
2
3
4
5
6
enum Zoo: String {
    case dog
    case cat
    case rabbit
    case octopus
}

Zoo 열거형의 접근 제어 수준은 internal입니다. 그리고 원시값은 (Public 접근 제어 수준을 가지는) String이므로 위 예제 코드는 아무런 문제가 없습니다.

Tuple 타입

튜플의 접근 제어 수준은 모든 타입 중 가낭 낮은 접근 제어 수준으로 결정됩니다. 아래는 이를 잘 보여주는 예제 코드입니다.

1
fileprivate var someTuple: (SomeInternalClass, someFilePrivateClass)?

메서드 타입

메서드의 접근 제어 수준은 (튜플과 마찬가지로) 매개변수와 반환 타입 중 가장 낮은 접근 제어 수준으로 결정됩니다. 아래는 이를 잘 보여주는 예제 코드입니다.

1
2
3
fileprivate func someFunction(_ parameter: SomePublicClass) -> someFilePrivateClass {
    // ...
}

프로퍼티, 상수 및 변수

프로퍼티, 상수 및 변수의 접근 제어 수준은 할당 받는 엔터티의 접근 제어 수준보다 같거나 더 낮아야 합니다. 아래는 이를 잘 보여주는 예제 코드입니다.

1
2
3
internal var someInternalProperty = SomeInternalClass()
fileprivate var someFilePrivateProperty = SomeInternalClass()
private var someFilePrivateProperty = SomeInternalClass()

읽기 전용 프로퍼티

프로퍼티를 엔터티 외부에서 읽기(get)만 가능하고, 쓰기(set)는 불가능하도록 접근 제어 수준을 지정할 수 있습니다. 아래는 이를 잘 보여주는 예제 코드입니다.

1
2
3
4
5
6
7
8
9
10
11
struct Car {
    public private(set) var isCruiseControl = false
    
    mutating func toggleCruiseControl() {
        isCruiseControl.toggle()
    }
}

var genesis = Car()
// Cannot assign to property: 'isCruiseControl' setter is inaccessible
genesis.isCruiseControl = true

프로토콜

프로토콜이 엔터티에 채택되어 구현될 때, 구현되는 메서드나 프로퍼티의 접근 제어 수준은 해당 프로토콜의 접근 제어 수준보다 같거나 더 높아야 합니다. 프로토콜 요구사항과 관련없는 다른 메서드나 프로퍼티는 해당 규칙을 적용받지 않습니다. 아래는 이를 잘 보여주는 예제 코드입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fileprivate protocol FilePrivateProtocol {
    func sayHello()
    func sayGoodBye()
}

public class PublicClass: FilePrivateProtocol {
    public func sayHello() {
        print("Hello, World!")
    }
    
    fileprivate func sayGoodBye() {
        print("Good Bye, World!")
    }
}

타입 별칭

타입 별칭도 (프로퍼티, 상수 및 변수와 마찬가지로) 별칭의 대상이 되는 엔터티의 접근 제어 수준보다 같거나 더 낮아야 합니다. 아래는 이를 잘 보여주는 예제 코드입니다.

1
2
3
public protocol publicProtocol { }

fileprivate typealias `protocol` = publicProtocol

참고 자료

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.