포스트

Swift 접근 제어 알아보기 ①

접근 제어란?

접근 제어(Access Control)은 소스 파일과 모듈 차원에서 코드의 접근 범위를 제한할 수 있는 방법입니다. 이를 통해 특정 코드의 세부 구현 사항을 외부로부터 숨길 수 있으며, 사용 가능한 인터페이스만 제공할 수 있습니다.

Swift에서는 접근 제어가 소스 파일과 모듈 단위로 이루어집니다. 모듈이란 배포할 코드의 묶음 단위를 말하는데, 프레임워크, 특정 앱을 타겟으로 하는 코드 묶음을 예로 들 수 있습니다. 일반적으로 (UIKit, Combine, RxSwift 등) import 키워드를 통해 불러올 수 있는 모든 코드 묶음을 모듈이라 생각하시면 편합니다.

이러한 접근 제어는 클래스, 구조체와 열거형 뿐만 아니라 프로퍼티와 메서드에도 적용할 수 있습니다. 심지어, 프로토콜과 타입 별칭(Type Alias)에도 적용할 수 있습니다.

클래스, 구조체, 열거형과 프로퍼티, 메서드를 모두 통틀어 엔터티라고 표현하겠습니다.

접근 제어 수준 단계

Swift는 총 5단계의 접근 제어 수준을 지원합니다.

키워드설명단계
open■ 클래스가 정의된 모듈과 외부 모듈까지 어디서든 자유롭게 접근이 가능합니다. 단, 클래스와 그 멤버에만 적용할 수 있습니다.
■ 모듈 외부에서 다른 클래스가 상속이 가능합니다. 그리고 오버라이딩도 가능합니다.
높음
public■ 엔터티가 정의된 모듈과 외부 모듈까지 어디서든 자유롭게 접근이 가능합니다.
단, (open과 달리) 모듈 외부에서 다른 클래스가 상속할 수 없습니다.
-
internal■ 엔터티가 정의된 모듈에서만 접근이 가능합니다.
■ 기본적으로 지정되는 접근 수준입니다.
중간
fileprivate■ 엔터티가 정의된 소스 파일 안에서만 접근이 가능합니다.
동일한 모듈 안이더라도 소스 파일이 다르면 접근이 불가능합니다.
높음
private■ 엔터티 내부에서만 접근이 가능합니다.낮음

Open ・ Public

openpublic은 가장 높은 접근 제어 수준을 가집니다. openpublic은 공통적으로 내부 모듈뿐만 아니라 외부 모듈에서도 자유롭게 접근할 수 있다는 특징을 지닙니다만, 모듈 외부에서 상속 가능 유무에 따라 쓰임새가 차이가 납니다.open은 클래스와 그 멤버에만 적용할 수 있고, 모듈 외부에서 다른 클래스가 상속과 오버라이딩이 가능합니다. 하지만, public은 클래스뿐만 아니라 다른 엔터티에도 적용할 수 있지만, 모듈 외부에서 다른 클래스가 상속과 오버라이딩이 불가능합니다.

그렇다면 open은 언제 사용하는 걸까요? 이는 우리가 자주 접하는 프레임워크에서 찾아볼 수 있습니다. 바로 UIKit 프레임워크입니다. 우리는 아주 자연스럽게 UIKitUIViewControllerUITableViewController를 상속받아 새로운 ViewController 클래스를 정의하고 있습니다. 심지어 viewDidLoad() 메서드도 오버라이딩을 하고 있죠. 이게 가능한 이유는 UIKit 프레임워크를 살펴보면 알 수 있습니다.

1
2
3
4
5
6
@available(iOS 2.0, *)
@MainActor open class UIViewController : UIResponder, NSCoding, UIAppearanceContainer, UITraitEnvironment, UIContentContainer, UIFocusEnvironment {

    open func viewDidLoad()
    
 }

UIViewController 클래스와 viewDidLoad() 메서드가 open 접근 제어 수준으로 지정되어 있는 모습을 볼 수 있습니다.

openpublic은 단일 XCode 프로젝트로 구성된 프로젝트에는 잘 사용되지 않습니다. 어느 위치에서 엔터티를 접근하더라도 동일한 모듈에 속하기 때문이죠. 다만 아래 그림과 같이 여러 XCode 프로젝트로 구성된 프로젝트에서는 적절히 openpublic 키워드로 접근 제어 수준을 지정해줄 필요가 있습니다.

2

만약 Core 모듈에 정의된 Repository 클래스가 internal 접근 제어 수준을 가진다면 App 모듈에서 Repository 클래스에 접근하는 게 불가능합니다. 그래서 구현 의도에 따라 Repository 클래스에 open이나 public 접근 제어 수준을 지정해 줄 필요가 있습니다.

Internal

internal은 중간 접근 제어 수준을 가집니다. internal은 엔터티가 정의된 모듈 내부에서만 사용이 가능합니다. 엔터티에 아무런 접근 제어 수준이 지정되어 있지 않다면, 기본적으로 internal 접근 제어 수준을 가집니다.

FilePrivate

fileprivate은 낮은 접근 제어 수준을 가집니다. fileprivate 접근 제어 수준으로 정의된 엔터티는 소스 파일 내부에서만 접근이 가능합니다. 아래는 이를 잘 보여주는 예제 코드입니다.

1
2
3
4
5
6
7
// main.swift 파일

fileprivate struct Repository {
    func save() { }
}

fileprivate let repo = Repository()
1
2
3
// another-main.swift 파일

let anotherRepo = Repository() // ❗️Cannot find 'Repository' in scope

Private

private은 가장 낮은 접근 제어 수준을 가집니다. private 접근 제어 수준으로 정의된 엔터티는 엔터티 내부에서만 접근이 가능합니다. 전역 변수나 함수를 private 접근 제어 수준으로 정의했다면, 엔터티가 정의된 (fileprivate과 동일하게) 소스 파일에서만 접근이 가능합니다. 그리고 상위 클래스의 메서드나 프로퍼티의 접근 제어 수준이 private이라면 하위 클래스에서 해당 메서드나 프로퍼티에 접근하는 게 불가능합니다. 아래는 이를 잘 보여주는 예제 코드입니다.

1
2
3
4
5
6
7
8
9
10
class Framework {
    private var name: String
    init(name: String) { self.name = name }
}

class RxSwift: Framework {
    func hello() -> String {
        return "Hello, \(name)!" // ❗️'name' is inaccessible due to 'private' protection level
    }
}

접근 제어 구문

엔터티의 접근 제어 수준은 선언의 시작 부분에 접근 제어 구문을 붙여 지정할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
private class somePrivateClass { }
fileprivate class someFilePrivateClass { }
internal class someInternalClass { }
public class somePublicClass { }
open class someOpenClass { }

private func somePrivateFunction() { }
fileprivate var someFilePrivateProperty: Int = 0
internal func someInternalFunction() { }
public var somePublicProperty: Int = 0

엔터티 선언의 시작 부분에 아무런 접근 제어 구문을 명시하지 않으면, 기본 값인 internal 접근 제어 수준을 가집니다. 따라서 위 예제 코드에서 someInternalClass 클래스와 someInternalFunction 함수의 접근 제어 수준은 생략할 수 있습니다.

참고 자료

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