개발하는 뚝딱이

[swift] 접근제어 본문

swift

[swift] 접근제어

개발자뚝딱이 2020. 5. 2. 15:06

책 <스위프트 프로그래밍 3판 ;야곰 지음> 을 정리한 글입니다.

 


 

 

1. 접근제어

- 접근제어 ; 코드끼리 상호작용을 할 때 파일 간 또는 모듈 간에 접근을 제한할 수 있는 기능

- 객체지향 패러다임의 Hiding (은닉화) 개념 중 하나

- 접근제어를 통해 코드의 상세구현은 숨기고 허용된 기능만 사용하는 인터페이스를 제공

 

1.1-1 접근제어의 필요성

- 불필요한 접근으로 의도치 않은 결과를 초래하거나 꼭 필요한 부분만 제공을 해야하는데 전체 코드가 노출될 가능성이 있을 때 접근제어를 이용

 

1.1-2 모듈과 소스파일

- 모듈 (Module) : 배포할 코드의 묶음 단위. 하나의 프레임워크나 라이브러리 또는 애플리케이션이 모듈 단위가 될 수 있음. 스위프트에서는 import 키워드를 사용하여 불러움

- 소스파일 : 하나의 스위프트 소스 코드 파일. 보통 파일 하나에 타입 하나를 정의하지만 때로는 소스파일 하나에 여러 타입이나 함수 등 많은 것을 정의하거나 구현 가능

 

 

 

2. 접근수준

- 접근제어는 접근수준 (Access Level) 키워드를 통해 구현

- 각 타입 (클래스, 구조체, 열거형 등)에 접근수준을 지정하고 타입 내부의 프로퍼티, 메서드, 이니셜라이저, 서브스크립트 각각에도 접근수준을 지정할 수 있음

접근수준 키워드 접근도 범위 비고
개방 open 높음 모듈 외부까지 클래스에서만 사용
공개 public

모듈 외부까지  
내부 internal 모듈 내부  
파일외부비공개 fileprivate 파일 내부  
비공개 private 낮음 기능 정의 내부  

 

2.1 공개 접근 수준 (Public)

- public 키워드로 접근수준이 지정된 요소는 어디서든 쓰일 수 있음

- 자신이 구현된 소스 파일, 소스파일이 속해 있는 모듈, 그 모듈을 가져다 쓰는 모듈 등

- 공개(Public) 접근수준은 주로 프레임워크에서 외부와 연결될 인터페이스를 구현하는데 많이 사용됨

- 우리가 사용하는 스위프트의 기본 요스는 모두 공개 수준으로 구현되어 있음

// 스위프트 표준 라이브러리에 정의되어 있는 Bool 타입
/// A value type whose instances are 'true' or 'false
public struct Bool {
    /// Default-initialize Boolean value to 'false'
    public init()
}

 

 

2.2 개방 접근 수준 (open)

- 클래스와 클래스의 멤버에서만 사용 가능

- 클래스를 개방 접근 수준으로 명시한 것은, 그 클래스를 다른 모듈에서도 부모 클래스로 사용하겠다는 목적으로 클래스를 설계하고 코드를 작성했다는 의미

- 공개 (public) 접근 수준과 비슷하지만 아래의 차이점이 있음

  • 개방 접근 수준을 제외한 다른 모든 접근수준의 클래스는 그 클래스가 정의된 모듈 안에서만 상속 가능
  • 개방 접근 수준을 제외한 다른 모든 접근수준의 클래스 멤버는 해당 멤버가 정의된 모듈 안에서만 재정의할 수 있음
  • 개방 접근 수준의 클래스는 그 클래스가 정의된 모듈 밖의 다른 모듈에서도 상속 가능
  • 개방 접근 수준의 클래스 멤버는 해당 멤버가 정의된 모듈 밖의 다른 모듈에서도 정의 가능
// Foundation 프레임워크에 정의되어 있는 개방수준의 NSString 클래스

open class NSString: NSObject, NSCopying, NSMutableCopying, NSSecureCoding {
    open var length: Int { get }
    open func character(at index: Int) -> unichar
    public init()
    public init?(coder aDecoder: NSCoder)
}

 

 

2.3 내부 접근 수준 (internal)

- 기본적으로 모든 요소에 암묵적으로 지정하는 기본 접근 수준

- 소스파일이 속해 있는 모듈 어디에서든 쓰일 수 잇음

- 그러나 그 모듈을 가져다 쓰는 외부 모듈에서는 접근 불가

- 보통 외부에서 사용할 클래스나 구조체가 아니며, 모듈 내부에서 광역적으로 사용할 경우 내부 접근수준을 지정

 

 

2.4 파일외부 비공개 접근 수준 (fileprivate)

- 그 요소가 구현된 소스파일 내부에서만 사용가능

- 해당 소스파일 외부에서 값이 변경되거나 함수를 호출하면 부작용이 생길 때 사용하면 좋음

 

 

2.5 비공개 접근 수준 (private)

- 그 기능을 정의하고 구현한 범위 내에서만 사용

- 비공개 접근 수준으로 지정한 기능은 심지어 같은 소스파일 내에 구현한 다른 타입이나 기능에서도 사용 불가능

 

 

3. 접근제어 구현

- internal의 경우 안적어줘도 됨

open class OpenClass {
    open var openProperty: Int = 0
    public var publicProperty: Int = 0
    internal var internalProperty: Int = 0
    private var privateProperty: Int = 0
    
    open func openMethod() {}
    public func publicMethod() {}
    internal func internalMethod() {}
    fileprivate func fileprivateMethod() {}
    private func privateMethod() {}
}

public class PublicClass {}
public struct PublicStruct {}
public enum PublicEnum {}
public var publicVariable = 0
public let publicConstant = 0
public func publicFunction() {}

internal class InternalClass {}
internal struct InternalStruct {}
internal var internalVariable = 0
internal let internalConstant = 0
internal func internalFunction() {}

fileprivate class FilePrivateClass {}
fileprivate struct FilePrivateStruct {}
fileprivate enum FilePrivateEnum {}
fileprivate var filePrivateVariable = 0
fileprivate let filePrivateConstant = 0
fileprivate func filePrivateFunction() {}

private class PrivateClass {}
private struct PrivateStruct {}
private enum PrivateEnum {}
private var privateVariable = 0
private let privateConstant = 0
private func privateFunction() {}

 

 

4. 접근제어 구현 참고사항

- '상위 요소보다 하위 요소가 더 높은 수준을 가질 수 없다'

// 잘못된 접근수준 부여

private class AClass {
    // 공개 접근수준을 보여해도 AClass의 접근수준이 비공개이므로
    // 이 메서드의 접근수준도 비공개 접근수준으로 취급
    public func someMethod() {
        // ...
    }
}

// AClass의 접근수준이 비공개 접근수준이므로
// 공개 접근수준 함수의 매개변수나 반환 값 타입으로 사용할 수 없음
public func someFunction(a: AClass) -> AClass {
    return a
}

 

- 튜플의 내부 요소 타입이 튜플의 접근 수준보다 같거나 높아야 함

internal class InternalClass {} // 내부 접근수준 클래스
private struct PrivateStruct {} // 비공개 접근수준 구조체

// 요소로 사용되는 InternalCalss와 PrivateStruct의 접근수준이
// publicTuple보다 낮기 때문에 사용할 수 없음
public var publicTuple: (first: InternalClass, second: PrivateStruct) = (InternalClass(), PrivateStruct())

// 요소로 사용되는 InternalClass와 PrivateStruct의 접근수준이
// privateTuple과 같거나 높기 때문에 사용할 수 있음
private var privateTuple: (first: InternalClass, second: PrivateStruct) = (InternalClass(), PrivateStruct())

 

- 접근수준에 따른 접근 결과

- 프레임워크를 만들 때는 다른 모듈에서 특정 기능에 접근할 수 있도록 public 공개 접근 수준으로 지정해야 함

// AClass.swift 파일과 Common.swift 파일이 같은 모듈에 속해 있는 경우

// AClass.swift 파일
class AClass {
    func internalMethod() {}
    fileprivate func filePrivateMethod() {}
    var internalProperty = 0
    fileprivate var filePrivateProperty = 0
}

// Common.swift 파일
let aInstance: AClass = AClass()
aInstance.internalMethod()  // 같은 모듈이므로 호출 가능
aInstance.filePrivateMethod()  // 다른 파일이므로 호출 불가 - 오류
aInstance.internalProperty = 1  // 같은 모듈이므로 접근 가능
aInstance.filePrivateProperty = 1  // 다른 파일이므로 접근 불가 - 오류

 

- 각 case의 접근수준은 열거형 자체의 접근수준

- 열거형의 원시 값 타입으로 열거형의 수준보다 낮은 접근수준의 타입이 올 수 없음

private typealias PointValue = Int

// 오류 - PointValue가 Point보다 접근수준이 낮기 때문에 원시값으로 사용 불가
enum Point: PointValue {
    case x, y
}

 

 

5. private와 fileprivate

- fileprivate 접근수준으로 지정한 요소는 같은 파일 어떤 코드에서도 접근이 가능

- private 접근수준으로 지정한 요소는 같은 파일 내부에 다른 타입의 코드가 있더라도 접근이 불가능

- 그러나 자신을 확장하는 익스텐션 코드가 같은 파일에 존재하는 경우 접근 가능

public struct SomeType {
    private var privateVariable = 0
    fileprivate var fileprivateVariable = 0
}

// 같은 타입의 익스텐션에서는 private 요소에 접근 가능
extension SomeType {
    public func publicMethod() {
        print("\(self.privateVariable), \(self.fileprivateVariable)")
    }
    
    fileprivate func fileprivateMethod() {
           print("\(self.privateVariable), \(self.fileprivateVariable)")
    }
    
    private func privateMethod() {
        print("\(self.privateVariable), \(self.fileprivateVariable)")
    }
}

struct AnotherType {
    var someInstance: SomeType = SomeType()
    
    mutating func someMethod() {
        // public 접근수준에는 어디서든 접근 가능
        self.someInstance.publicMethod() // 0, 0
        
        // 같은 파일에 속해 있는 코드이므로 fileprivate 접근 수준 요소에 접근 가능
        self.someInstance.fileprivateVariable = 100
        self.someInstance.fileprivateMethod() // 0, 100
        
        // 다른 타입 내부의 코드이므로 private 요소에 접근 불가! 오류!
//        self.someInstance.privateVariable = 100
//        self.someInstance.privateMethod()
    }
}

var anotherInstance = AnotherType()
anotherInstance.someMethod()

 

 

6. 읽기 전용 구현

public struct SomeType {
    // private 저장 프로퍼티
    private var count: Int = 0
    
    // public 저장 프로퍼티
    public var publicStoredProperty: Int = 0
    
    // public 저장 프로퍼티
    // setter는 private
    public private(set) var publicGetOnlyStoredProperty: Int = 0
    
    
    // internal 연산 프로퍼티
    internal var internalComputedProperty: Int {
        get {
            return count
        }
        
        set(newValue) {
            count += newValue
        }
    }
    
    // internal 연산 프로퍼티
    // setter는 private
    internal private(set) var internalGetOnlyComputedProperty: Int {
        get {
            return count
        }
        
        set {
            count += 1
        }
    }
    
    // public 서브스크립트
    public subscript() -> Int {
        get {
            return count
        }
        
        set {
            count += 1
        }
    }
    
    // public 서브스크립트
    // setter는 internal
    public internal(set) subscript(some: Int) -> Int {
        get {
            return count
        }
        
        set {
            count += 1
        }
    }
}

var someInstance: SomeType = SomeType()

// 외부에서 접근자, 설정자 모두 사용 가능
someInstance.publicStoredProperty = 10
print(someInstance.publicStoredProperty) // 10

//someInstance.publicGetOnlyStoredProperty = 10 // 오류 발생
print(someInstance.publicGetOnlyStoredProperty) // 0

someInstance.internalComputedProperty = 10
print(someInstance.internalComputedProperty) // 10

//someInstance.internalGetOnlyComputedProperty = 10 // 오류 발생
print(someInstance.internalGetOnlyComputedProperty) // 1


// 외부에서 접근자, 설정자 모두 사용 가능
print(someInstance[]) // 10
someInstance[] = 100

// 외부에서 접근자만, 같은 모듈 내에서는 설정자도 사용 가능
print(someInstance[0]) // 11
someInstance[0] = 100

'swift' 카테고리의 다른 글

[swift] 옵셔널 체이닝과 빠른 종료  (0) 2020.05.08
[swift] 클로저  (0) 2020.05.07
[swift] 인스턴스 생성 및 소멸  (0) 2020.05.01
[swift] 옵셔널  (0) 2020.04.28
[swift] 메소드 (method)  (0) 2020.04.27