Swift - 접근제어

임성빈·2022년 4월 25일
0

Swift

목록 보기
25/26
post-thumbnail
post-custom-banner

접근제어


접근제어는 특정 코드의 접근을 다른 소스파일이나 모듈에서 제한하는 것이다. 이렇게 접근제어를 함으로써 특정 코드의 세부적인 구현을 감추고 딱 필요한 만큼 공개해 다른 곳에서 사용할 수 있도록 한다. 접근제어는 클래스, 구조체, 열거형 등 개별 타입에도 적용할 수 있고 그 타입에 속한 프로퍼티, 메소드, 초기자, 서브스크립트에도 적용할 수 있다. 프로토콜은 그래서 전역상수, 변수, 함수 같이 특정 문맥에 종속된다. Swift에서는 기본 접근레벨을 제공해 접근레벨의 처리를 쉽게 할 수 있도록 돕는다. 그래서 사실 단일타겟의 앱에서는 특별히 접근레벨을 전혀 명시하지 않아도 된다.


모듈과 소스파일

Swift의 접근제어는 모듈과 소스파일에 기반을 두고 있다. 모듈은 코드를 배포하는 단일 단위로 하나의 프레임워크나 앱이 이 단위로 배포되고 다른 모듈에서 Swift의 import 키워드를 사용해 import 될 수 있다. Xcode의 각 빌드 타겟은 Swift에서 분리된 단일 모듈로 취급된다. 소스파일은 모듈안에 있는 소스파일을 의미한다. 각 소스파일에 여러 특정 타입을 선언해 사용할 수 있다.


접근레벨

Swift에서는 5개의 접근레벨을 제공한다.

  • Open & Public : OpenPublic 접근자 모두 선언한 모듈이 아닌 다른 모듈에 사용가능하다. 두 접근자의 차이점은 Open 은 다른 모듈에서 오버라이드와 서브클래싱이 가능하지만 Public 접근자로 선언된 것은 다른 모듈에서는 오버라이드와 서브클래싱이 불가능하다.
  • Internal : 기본 접근레벨로 아무 접근레벨을 선언하지 않으면 Internal 로 간주된다. Internal 레벨로 선언되면 해당 모듈 전체에서 사용 가능하다.
  • File-private : 특정 엔티티를 선언한 파일 안에서만 사용 가능하다.
  • Private : 특정 엔티티가 선언된 괄호 {} 안에서만 사용 가능하다.

접근레벨 가이드 원칙

Swift에서 접근 레벨은 더 낮은 레벨을 갖고 있는 다른 엔티티를 특정 엔티티에 선언해 사용할 수 없다는 일반 가이드 원칙을 따른다.

  • public 변수는 다른 internal, file-private 혹은 private 타입에서 정의될 수 없다. 왜냐하면 그 타입은 public 변수가 사용되는 모든 곳에서 사용될 수 없을 것이기 때문이다.
  • 함수는 그 함수의 파라미터 타입이나 리턴값 타입보다 더 높은 접근레벨을 갖을 수 없다. 왜냐하면 함수에는 접근 가능하지만 파라미터에 접근이 불가능하거나 혹은 반환값 타입보다 접근 레벨이 낮아 함수를 사용하는 관련 코드에서 이용할 수 없을 수 있기 때문이다.

기본 접근레벨

위에서 설명한 것과 같이 아무런 접근레벨을 명시하지 않은 경우 internal 을 갖게 된다.

단일 타겟 앱을 위한 접근레벨

단일 타겟 앱에서는 특별히 접근레벨을 명시할 필요가 없지만 필요에 따란 file-private , private 등을 사용해 앱내에서 구현 세부사항을 숨길 수 있다.

프레임위크를 위한 접근레벨

프레임워크를 개발한다면 public 혹은 open 으로 지정해서 다른 모듈에서 볼 수 있고 접근 가능하도록 만들어야한다.

유닛테스트 타겟을 위한 접근레벨

기본적으로 open 이나 public 으로 지정된 엔티티만 다른 모듈에서 접근 가능하다. 하지만 유닛테스트를 하는 경우 모듈을 import할 때 앞에 @textable 이라는 에트리뷰트를 붙여주면 해당 모듈을 테스트가 가능한 모듈로 컴파일해 사용한다.


접근제어 문법

각 접근자를 사용해 클래스와 변수, 상수를 선언한 예제이다.

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

public var somePublicVariable = 0
internal let someInternalConstant = 0
fileprivate func someFilePrivateFunction() {}
private func somePrivateFunction() {}

커스텀 타입

커스텀 클래스에 특정 접근레벨을 지정할 수 있다. 그 레벨을 지정하면 그 클래스 안의 프로퍼티와 리턴 타입의 접근레벨은 클래스 레벨의 접근 권한만 사용할 수 있다. 예를 들어, 클래스를 file-private 클래스로 선언하면 그 안의 프로퍼티와 함수 파라미터 그리고 반환 타입도 file-private 접근 권한을 갖는다. 다시말해 특정 타입의 접근레벨의 지정은 그 타입의 멤버에 기본 접근레벨에 영향을 미친다.

// explicitly public class
public class SomePublicClass {
    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
}

// implicitly internal class
class SomeInternalClass {                       
    var someInternalProperty = 0                 
    // implicitly internal class member
    fileprivate func someFilePrivateMethod() {}  
    // explicitly file-private class member
    private func somePrivateMethod() {}          
    // explicitly private class member
}

// explicitly file-private class
fileprivate class SomeFilePrivateClass {        
    func someFilePrivateMethod() {}              
    // implicitly file-private class member
    private func somePrivateMethod() {}          
    // explicitly private class member
}

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

튜플타입

튜플타입의 접근레벨은 튜플에서 사용되는 모든 타입의 접근레벨 중 가장 제한적인 접근레벨을 갖는다. 만약 하나는 internal 다른 하나는 private 접근권한을 갖는 2개의 타입을 구성된 튜플은 더 낮은 레벨인 private 접근레벨을 갖는다.

함수타입

함수 타입의 접근레벨은 함수의 파라미터 타입과 리턴타입의 접근레벨 중 최소의 접근레벨로 계산돼 사용된다. 그래서 그것에 맞는 접근레벨을 함수 앞에 명시해 줘야 한다. 다음 코드는 명시적인 접근레벨을 명시하지 않은 함수인데, 컴파일시 에러가 발생한다. 이유는 반환값 중에 접근레벨이 privateSomePrivateClass 가 존재하기 때문에 someFuction()internal 접근레벨로 선언될 수 없기 때문이다.

func someFunction() -> (SomeInternalClass, SomePrivateClass) {
    // function implementation goes here
}

이 함수를 적절히 선언하기 위해서는 아래와 같이 함수 앞에 private 접근레벨을 지정해야한다.

private func someFunction() -> (SomeInternalClass, SomePrivateClass) {
    // function implementation goes here
}

열거형 타입

열거형에서 각 case는 enum의 접근레벨을 따르고 개별적으로 다른 접근레벨을 지정할 수 없다. 다음 코드의 각 case는 enum의 접근레벨 public 을 따라 모두 public 접근레벨을 갖는다.

public enum CompassPoint {
    case north
    case south
    case east
    case west
}

고유값과 연관값

고유값과 연관값을 사용하는 타입의 경우 반드시 그 타입보다 높은 접근레벨을 가져야한다. 즉, internal 접근레벨을 갖고 있는 열거형 타입에서 private 접근레벨을 갖는 고유값을 사용할 수 없다.

중첩타입

private 로 선언된 타입의 중첩 타입은 자동으로 private 접근레벨을 갖는다. file-private 으로 선언된 경우 중첩타입은 file-private 를 갖는다. public 혹은 internal 로 선언된 타입에서 중첩타입은 자동으로 internal 접근레벨을 갖는다. public 으로 선언된 타입에서 public 으로 선언된 중첩타입을 사용하고 싶으면 명시적으로 public 접근자를 중첩타입에 적어줘야한다.


서브클래싱

서브클래스는 슈퍼클래스보다 더 높은 접근레벨을 갖을 수 없다. 슈퍼클래스가 internal 를 갖는데 그것을 서브클래싱해서 public 서브클래스를 만들 수 없다. 하지만 메소드는 서브클래스에서 더 높은 접근레벨을 갖는 메소드로 오버라이드 할 수 있다. 아래 예제에서는 class BsomeMethod() 는 슈퍼클래스의 file-private 보다 더 높은 접근레벨인 internal 을 갖도록 오버라이드 된다.

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

internal class B: A {
    override internal func someMethod() {}
}

이게 가능한 이유는 class Aclass B 가 같은 소스 파일에 선언되어 있어서 class BsomeMethod 구현에서 super.someMethod() 를 호출하는 것이 유효하기 때문이다.

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

internal class B: A {
    override internal func someMethod() {
        super.someMethod()
    }
}

상수, 변수, 프로퍼티 그리고 서브스크립트

상수, 변수, 프로퍼티 등은 그 타입보타 더 높은 접근레벨을 갖을 수 없다. 즉, private 타입에서 public 프로퍼티를 선언할 수 없다. 아래와 같이 private 클래스 변수의 접근레벨은 private 이 돼야 한다.

private var privateInstance = SomePrivateClass()

Getter와 Setter

상수, 변수, 프로퍼티 그리고 서브스크립트의 Getter와 Setter는 자동으로 해당 상수, 변수, 프로퍼티 그리고 서브스크립트가 갖는 접근레벨을 동일하게 갖는다. 필요에 따라 Setter의 접근레벨을 Getter보다 낮게 정할 수 있다. 이를 위해 다음 키워드를 var 혹은 subscript 앞에 붙여 사용한다.
fileprivate(set) , private(set) , internal(set)

struct TrackedString {
    private(set) var numberOfEdits = 0
    var value: String = "" {
        didSet {
            numberOfEdits += 1
        }
    }
}

이 코드를 실행하면 다음과 같이 Getter만을 이용해 변경된 숫자를 확인할 수 있다.

var stringToEdit = TrackedString()
stringToEdit.value = "This string will be tracked."
stringToEdit.value += " This edit will increment numberOfEdits."
stringToEdit.value += " So will this one."
print("The number of edits is \(stringToEdit.numberOfEdits)")
// Prints "The number of edits is 3"

Setter 뿐 아니라 Getter에도 접근레벨을 지정하고 싶다면 아래 코드와 같이 public private(set) var numberOfEdits = 0 Getter 레벨도 지정할 수 있다.

public struct TrackedString {
    public private(set) var numberOfEdits = 0
    public var value: String = "" {
        didSet {
            numberOfEdits += 1
        }
    }
    public init() {}
}

초기자

초기자의 접근레벨은 타입의 레벨과 같거나 낮다. 한가지 예외상황은 지정초기자인데, 이 지정초기자는 반드시 이 초기자가 속한 타입과 접근레벨이 같아야 한다.

기본 초기자

기본 초기자는 타입의 접근레벨이 public 으로 지정돼 있지 않은 이상 타입과 같은 접근레벨을 갖는다. 만약 타입의 접근레벨이 public 으로 지정돼 있으면 기본 초기자는 internal 접근레벨을 갖는다.

구조체 타입을 위한 기본 멤버쪽 초기자

만약 모든 저장 프로퍼티가 pivate 로 지정된 경우 멤버쪽 초기자의 접근레벨은 private 를 갖고 file private 인 경우 file private 를 갖는다. 그 밖에는 internal 접근레벨을 갖는다. 만약 public 구조체 타입이 다른 모듈에서 사용될 때는 반드시 멤버쪽 초기자의 선언에 public 으로 지정해야 다른 모둘에서 그 멤버쪽 초기자를 사용할 수 있다.


프로토콜

프로토콜의 접근레벨과 그 안의 요구사항의 접근레벨은 항상 동일하다

프로토콜 상속

이미 존재하는 프로토콜을 상속받아 새로운 프로토콜을 선언하는 경우 새 프로토콜은 상속을 한 프로토콜과 같은 접근레벨을 갖는다.

프로토콜 순승

특정 타입은 그 타입보다 낮은 접근레벨을 갖는 프로토콜을 따를 수 있다. 예를들어 public 타입을 다른 모듈에서 사용하지만 internal 프로토콜은 오직 internal 프로토콜이 선언된 곳에서만 사용가능하다. 만약 타입이 public 이코 프로토콜이 internal 이라면 그 프로토콜을 따르는 타입도 internal 이다.


익스텐션

클래스, 구조체, 열거형 등에 익스텐션에서 새로운 멤버를 추가된 것은 기존에 타입이 선언된 접근레벨과 같은 레벨을 갖는다. 익스텐션에 명시적으로 접근레벨을 지정할 수도 있다. 익스텐션을 프로토콜로 사용하는 경우 명시적인 접근레벨 변경이 불가능하다. 대신 프로토콜의 접근레벨이 익스텐션의 프로토콜 구현에서 사용된다.

프라이빗 멤버

익스텐션이 클래스, 구조체 혹은 열거형과 같은 파일에 선언된 경우 다음이 가능하다.

  • 원본 선언에서 private 멤버로 선언한 것을 익스텐션에서 멤버로 접근할 수 있다.
  • 하나의 익스텐션에서 private 멤버로 선언한 것을 같은 파일의 다른 익스텐션에서 접근할 수 있다.
  • 하나의 익스텐션에서 private 멤버로 선언한 것을 원본 선언에서 멤버로 접글할 수 있다.
protocol SomeProtocol {
func doSomething()
}

위와 같이 프로토콜을 선언하고 아래와 같이 구조체를 하나 선언하고 private 변수를 하나 선언한다.

struct SomeStruct {
    private var privateVariable = 12
}

extension SomeStruct: SomeProtocol {
    func doSomething() {
        print(privateVariable)
    }
}

프로토콜을 따르는 익스텐션에서 그 타입의 private 변수에 접근할 수 있다.


제네릭

제네릭 타입 혹은 함수는 그 자체 그리고 타입 파라미터의 접근레벨의 최소 접근레벨을 갖는다.


타입 별칭

타입 별칭은 별칭을 붙인 타입보다 같거나 낮은 접근레벨을 갖는다. 예를 들어, private 타입의 별칭은 private , file-private , internal , public , open 타입의 접근권한을 갖을 수 있다.

profile
iOS 앱개발
post-custom-banner

0개의 댓글