Notice
Recent Posts
Recent Comments
Link
«   2025/04   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
Archives
Today
Total
관리 메뉴

J

[Swift] Access Control (접근 제어) 본문

Swift

[Swift] Access Control (접근 제어)

yujaehui 2024. 4. 3. 11:38

선언, 파일, 그리고 모듈에 의해 코드의 노출을 관리.

현재 사용하고 있는 파일이나 모듈이 아닌 다른 파일과 모듈에서 코드 접근에 대해 제한하는 것.

이 기능은 코드의 세부적인 구현을 숨기고, 해당 코드에 접근할 수 있는 기본 인터페이스를 지정할 수 있음.

접근 수준은 개별 타입(클래스, 구조체, 그리고 열거형)과 해당 타입에 속하는 프로퍼티, 메서드, 초기화 구문과 서브 스크립트에 할당 가능.

Swift는 기본 접근 수준을 제공하기 때문에 명시적으로 접근 제어 수준을 지정할 필요성을 줄여 줌.

→ 단일 앱의 경우 접근 제어 수준을 지정할 필요가 없음.


1. 모듈과 소스 파일(Modules and Source Files)

Swift의 접근 제어 모델은 모듈과 파일의 개념을 기초.

모듈(Modules)

  • 프레임워크 또는 애플리케이션과 같은 배포할 코드의 단일 단위.
  • 우리가 자주 사용하는 `import`키워드를 사용하여 다른 모듈의 것을 가져올 수 있음.

소스파일(Source Files)

  • 모듈 내의 단일 Swift 소스 코드 파일(실제로 앱 또는 프레임워크 내의 단일 파일).
  • 일반적으로 하나의 소스 파일에 하나의 타입을 정의하지만, 하나의 소스 파일에 여러 타입이나 함수 등을 정의하는 경우도 있음.

2. 접근 수준(Access Levels)

종류

  • `open`, `public`
    • 현재 모듈의 모든 파일 내에서 사용할 수 있으며, 다른 모듈의 파일에서도 사용 가능.
    • 일반적으로 프레임워크를 공개로 지정할 때 사용.
  • `internal`
    • 현재 모듈의 모든 파일 내에서 사용할 수 있지만, 다른 모듈의 파일에서는 사용할 수 없음.
    • 일반적으로 앱 또는 프레임워크의 내부 구조체를 정의할 때 사용.
  • `file-private`
    • 현재 파일 내에서만 사용.
    • 기능의 세부적인 구현 정보를 숨기기 위해 사용.
  • `private`
    • 현재 선언 내에서만 사용.
    • 기능의 세부적인 구현 정보를 숨기기 위해 사용

`open`과 `public`의 차이

  • `open`은 현재 모듈이 아닌 다른 모듈의 클래스를 상속과 재정의할 수 있다는 점에서 `public`과 다름.
  • 이러한 이유로 `open`은 클래스에만 적용.

접근 수준의 기본 원칙(Guiding Principle of Access Levels)

  • 상위의 접근 수준보다 하위의 접근 수준이 더 높을 수 없음.
  • 예를 들어 `private` 클래스 내에 `public` 메서드를 선언할 수 없음.

기본 접근 수준(Default Access Levels)

  • 접근 수준을 따로 적지 않을 경우 기본적으로 `internal`

3. 접근 제어 구문 (Access Control Syntax)

선언의 시작 부분에 `open`, `public`, `internal`, `fileprivate`, `private` 수식어 중 하나를 작성하여서 접근 수준을 정의.

open class SomeOpenClass {}
public class SomePublicClass {}
internal class SomeInternalClass {}
fileprivate class SomeFilePrivateClass {}
private class SomePrivateClass {}

open var someOpenVariable = 0
public var somePublicVariable = 0
internal let someInternalConstant = 0
fileprivate func someFilePrivateFunction() {}
private func somePrivateFunction() {}
class SomeInternalClass {}              // implicitly internal
let someInternalConstant = 0            // implicitly internal
  • 달리 지정하지 않는 한 기본 접근 수준 (Default Access Levels)은 `internal`

4. 사용자 정의 타입(Custom Types)

명시적으로 접근 수준을 지정하고 싶다면 타입을 정의할 때 지정.

그러면 그 타입은 접근 수준이 허용하는 범위내에서 사용될 수 있음.

예를 들어 `file-private` 클래스를 정의하면 당연히 해당 클래스는 해당 파일에서만 사용.

 

타입의 접근 수준은 해당 타입 멤버(프로퍼티, 메서드, 초기화 구문, 서브 스크립트) 접근 수준에도 영향.

타입의 접근 수준을 `private`, `file private`로 정의한다면 멤버의 접근 수준 또한 `private`, `file private`.

접근 수준을 `internal`, `public` 또는 지정하지 않으면 타입의 멤버의 접근 수준은 `internal`.

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
}
  • `public` 타입은 기본적으로 `public` 멤버가 아닌 `internal` 멤버를 가짐.

5. 서브 클래싱 (Subclassing)

현재 접근 수준에 문제가 없고, 동일한 모듈이라면 모든 클래스는 하위 클래스로 지정 가능.

물론, 하위 클래스는 상위 클래스의 접근 수준보다 높은 접근 수준을 가질 수 없음.

또한 접근 수준에 문제가 없고, 동일한 모듈이라면 모든 클래스 멤버(메서드, 프로퍼티, 초기화 구문 또는 서브 스크립트)를 재정의 가능.

 

다른 모듈에 있어도 `open` 클래스는 하위 클래스로 지정 가능.

다른 모듈에 있어도 모든 `open` 클래스 멤버는 재정의 가능.

public class A {
    fileprivate func someMethod() {}
}

internal class B: A {
    override internal func someMethod() {} // fileprivate -> internal
}
  • 재정의는 클래스 멤버를 상위 클래스보다 더 쉽게 접근할 수 있도록 만들 수 있음.
  • 부모 클래스의 `fileprivate` 메서드를 자식 클래스에서 재정의를 통해 `internal`로 변경

6. 상수, 변수, 프로퍼티, 그리고 서브 스크립트 (Constants, Variables, Properties, and Subscripts)

상수, 변수, 또는 프로퍼티는 타입보다 더 공개할 수 없음.
예를 들어 `private` 클래스에 `public` 프로퍼티를 작성하는 것은 유효하지 않음.
유사하게 서브 스크립트는 인덱스 타입 또는 반환 타입 보다 더 공개될 수 없음.

private class SomePrivateClass {                 // explicitly private class
    func somePrivateMethod() {}                  // implicitly private class member
}

private var privateInstance = SomePrivateClass()
  • 상수, 변수, 프로퍼티, 또는 서브 스크립트가 `private` 타입에 사용되는 경우 `private` 로 표시.

7. 게터, 세터(Getter, Setter)

상수, 변수, 프로퍼티, 서브 스크립트에 대한 getter와 setter는 자동으로 같은 접근 수준.

 

해당 변수, 프로퍼티, 서브 스크립트에 읽기-쓰기 범위를 제한하기 위해 getter 보다 더 낮은 접근 수준으로 setter를 제공할 수 있음.

`var` 또는 `subscript` 전에 `fileprivate(set)`, `private(set)`, `internal(set)`을 작성하여 더 낮은 접근 수준을 할당.

struct Example {
    private(set) var number = 0 // 더 낮은 접근 수준 할당
    
    var value: String = "" {
        didSet {
            number += 1
        }
    }
}

var example = Example()

//example.number = 1
//Cannot assign to property: 'number' setter is inaccessible 오류 발생
//해당 오류는 읽기 전용을 나타내는 표시

print(example.number) // 0 출력
example.value = "Hello"
print(example.number) // 1 출력
  • `Example` 구조체와 `value` 프로퍼티는 `internal`의 기본 접근 수준.
  • 그러나 `number`에 대한 접근 수준에서 getter는 `internal` 접근 수준을 가지지만, setter는 `Example` 구조체 내에서만 설정 가능함을 나타내기 위해 `private(set)` 수식어로 표기.
  • 이렇게 하면 `Example` 내부적으로는 `number` 를 수정할 수 있지만 외부에서 사용될 때는 읽기 전용.
public struct Example {
    private(set) var number = 0
    
    public var value: String = "" {
        didSet {
            number += 1
        }
    }
}
  • 필요한 경우 getter와 setter 모두에 명시적으로 접근 수준을 할당할 수 있음

7. 초기화 구문 (Initializers)

사용자 정의하는 초기화 구문은 초기화 하는 타입보다 더 낮거나 같은 접근 수준으로 할당 가능.

유일한 예외는 필수 초기화 구문(Required initializers).

필수 초기화 구문은 자신이 속한 클래스와 동일한 접근 수준만 가능.

메서드와 마찬가지로 초기화 구문의 파라미터의 타입은 초기화 구문의 자체 접근 수준보다 더 낮을 수 없음.


8. 접근 제어(Access Control)를 사용하는 이유

  • 모듈성(Modularity)
    • 소프트웨어를 모듈화하여 코드를 더욱 구조화하고 관리하기 쉽게 함.
    • 모듈은 각각의 기능을 담당하며, 접근 제어를 사용하여 모듈 간에 필요한 인터페이스를 공개하거나 외부에 숨길 수 있음.
  • 캡슐화(Encapsulation)
    • 접근 제어를 사용하여 데이터나 기능을 캡슐화하여 외부에서 직접적으로 접근하지 못하게 함.
    • 객체 지향 프로그래밍의 원칙을 따라 데이터 은닉을 지원.
  • 유지 보수성(Maintainability)
    • 코드의 유지 보수성을 높이고 코드베이스를 보호하기 위해 접근 제어를 사용하여 외부 코드가 내부 구현에 대한 의존성을 최소화.
    • 이를 통해 내부 구현을 변경하더라도 외부 코드에 미치는 영향을 최소화.
  • 안전성(Safety)
    • 잘못된 접근으로부터 코드를 보호하고, 예기치 않은 동작이 발생하는 것을 방지.
    • 또한, 의도치 않은 변경이나 오류를 방지하고 안정성을 높이는 데 도움.
  • Whole-Module Optimization

참고 문헌 : https://docs.swift.org/swift-book/documentation/the-swift-programming-language/accesscontrol/

이미지 출처: https://samwize.com/2017/04/20/access-levels-in-swift/