Swift 접근 제어 알아보기 ①
접근 제어란?
접근 제어(Access Control)은 소스 파일과 모듈 차원에서 코드의 접근 범위를 제한할 수 있는 방법입니다. 이를 통해 특정 코드의 세부 구현 사항을 외부로부터 숨길 수 있으며, 사용 가능한 인터페이스만 제공할 수 있습니다.
Swift에서는 접근 제어가 소스 파일과 모듈 단위로 이루어집니다. 모듈이란 배포할 코드의 묶음 단위를 말하는데, 프레임워크, 특정 앱을 타겟으로 하는 코드 묶음을 예로 들 수 있습니다. 일반적으로 (UIKit
, Combine
, RxSwift
등) import
키워드를 통해 불러올 수 있는 모든 코드 묶음을 모듈
이라 생각하시면 편합니다.
이러한 접근 제어는 클래스, 구조체와 열거형 뿐만 아니라 프로퍼티와 메서드에도 적용할 수 있습니다. 심지어, 프로토콜과 타입 별칭(Type Alias)에도 적용할 수 있습니다.
클래스, 구조체, 열거형과 프로퍼티, 메서드를 모두 통틀어
엔터티
라고 표현하겠습니다.
접근 제어 수준 단계
Swift는 총 5단계의 접근 제어 수준을 지원합니다.
키워드 | 설명 | 단계 |
---|---|---|
open | ■ 클래스가 정의된 모듈과 외부 모듈까지 어디서든 자유롭게 접근이 가능합니다. 단, 클래스와 그 멤버에만 적용할 수 있습니다. ■ 모듈 외부에서 다른 클래스가 상속이 가능합니다. 그리고 오버라이딩도 가능합니다. | 높음 |
public | ■ 엔터티가 정의된 모듈과 외부 모듈까지 어디서든 자유롭게 접근이 가능합니다. ■ 단, ( open 과 달리) 모듈 외부에서 다른 클래스가 상속할 수 없습니다. | - |
internal | ■ 엔터티가 정의된 모듈에서만 접근이 가능합니다. ■ 기본적으로 지정되는 접근 수준입니다. | 중간 |
fileprivate | ■ 엔터티가 정의된 소스 파일 안에서만 접근이 가능합니다. 동일한 모듈 안이더라도 소스 파일이 다르면 접근이 불가능합니다. | 높음 |
private | ■ 엔터티 내부에서만 접근이 가능합니다. | 낮음 |
Open ・ Public
open
과 public
은 가장 높은 접근 제어 수준을 가집니다. open
과 public
은 공통적으로 내부 모듈뿐만 아니라 외부 모듈에서도 자유롭게 접근할 수 있다는 특징을 지닙니다만, 모듈 외부에서 상속 가능 유무에 따라 쓰임새가 차이가 납니다.open
은 클래스와 그 멤버에만 적용할 수 있고, 모듈 외부에서 다른 클래스가 상속과 오버라이딩이 가능합니다. 하지만, public
은 클래스뿐만 아니라 다른 엔터티에도 적용할 수 있지만, 모듈 외부에서 다른 클래스가 상속과 오버라이딩이 불가능합니다.
그렇다면 open
은 언제 사용하는 걸까요? 이는 우리가 자주 접하는 프레임워크에서 찾아볼 수 있습니다. 바로 UIKit
프레임워크입니다. 우리는 아주 자연스럽게 UIKit
의 UIViewController
나 UITableViewController
를 상속받아 새로운 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
접근 제어 수준으로 지정되어 있는 모습을 볼 수 있습니다.
open
과 public
은 단일 XCode 프로젝트로 구성된 프로젝트에는 잘 사용되지 않습니다. 어느 위치에서 엔터티를 접근하더라도 동일한 모듈에 속하기 때문이죠. 다만 아래 그림과 같이 여러 XCode 프로젝트로 구성된 프로젝트에서는 적절히 open
과 public
키워드로 접근 제어 수준을 지정해줄 필요가 있습니다.
만약 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
함수의 접근 제어 수준은 생략할 수 있습니다.