본문 바로가기

문법

[Swift]접근 제어(Access Control)를 알아보자

주의 : 배우는 과정이기에  정확하지않는 정보가 있을 수 있습니다.

 

접근제어란?

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

 

접근제어
- 접근제어
- 접근제어의 필요성
- 모듈과 소스파일
- 접근수준
- 접근제어 구현 참고사항
- private 와 fileprivate
- 읽기 전용 구현
- 포인트 정리
- 궁금증

 

접근제어

객체지향 프로그래밍 패러다임에서 은닉화는 중요한 개념중 하나이다. 이번장에서 이를 구현하기 위한 핵심 기능인 접근제어에 대해 알아보자

접근제어란?

접근제어는 코드끼리 상호작용을 할때 파일간 또는 모듈간에 접근을 제한할 수 있는 기능이다. 접근제어를 통해 코드의 상세구현은 숨기고 허용된 기능만 사용하는 인터페이스를 제공할 수 있다.

 

접근제어의 필요성

 

객체지향 프로그래밍 패러다임에서 중요한 캡슐화, 은닉화를 구현하는 이유는 외부에서 보거나 접근하면 안되는 코드가 있기때문이다.

불필요한 접근으로 의도치 않은 결과를 초래하거나 꼭 필요한 부분만 제공을 해야하는데 전체코드가 노출될 가능성이 있을때 접근제어를 이용한다. 프로그래머에게 우리가 의도한 대로 코드를 작성하도록 유도할 수 있다.

 

 

모듈과 소스파일

스위프트의 접근제어는 모듈과 소스파일을 기반으로 설계가 되어있다. 모듈은 배포할 코드의 묶음단위이다. 통상 하나의 프레임워크나 라이브러리 또는 애플리케이션이 모듈단위가 될 수 있다. 스위프트에서는 import키워드를 사용해 불러온다.

 

소스파일은 하나의 스위프트 소스코드 파일을 의미한다. 자바나 Objective-C와 같은 기존의 프로그래밍 언어에서는 통상 파일하나에 타입을 하나만 정의한다. 스위프트에서도 보통 파일 하나에 타입하나만 정의하지만 때로는 소스파일 하나에 여러타입이나 함수등 많은 것을 정의하거나 구현할 수 있다.

 

 

접근 수준

접근제어는 접근수준 키워드를 통해 구현할 수 있다. 각 타입에 특정 접근 수준을 지정할 수 있다. 접근 수준을 명시 할 수 있는 키워드는 open, public, internal, fileprivate, private 다섯가지가 있다.

 

스위프트의 접근 수준은 기본적으로 모듈과 소스파일에 따라 구분한다.

 

공개접근수준 - public

공개접근수준이 지정된 요소는 어디서든 쓰일 수 있다. 자신이 구현된 소스파일은 물론 그 소스파일이 속해 있는 모듈, 그 모듈을 가져다 쓰는 모듈등 모든 곳에서 사용할 수 있다. 공개 접근수준은 주로 프레임워크에서 외부와 연결될 인터페이스를 구현하는데 많이 쓰인다. 우리가 사용하는 스위프트의 기본요소는 모두 공개 접근수준으로  구현되있다.

 

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

 

 

개방 접근 수준 - open

open 키워드로 지정할 수 있는 개방 접근수준은 공개 접근수준으로 높은 접근수준이며, 클래스와 클래스의 멤버에서만 사용할 수 있다. 기본적으로 공개 접근수준과 비슷하지만 다음과 같은 차이점이 있다.

 

- 개방 접근수준을 제외한 다른 접근수준의 클래스는 다른 클래스에 상속할 수 있다.

- 개방 접근수준을 제외한 다른 모든 접근수준의 클래스 멤버는 해당멤버가 정의된 모듈안에서만 재정의 할 수 있다.

- 개방 접근수준의 클래스는 그 클래스가 정의된 모듈밖의 다른 모듈에서도 상속할 수 있다.

- 개방 접근수준의 클래스멤버는 해당 멤버가 정의된 모듈밖의 다른 모듈에서도 재정의가 가능하다.

 

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

 

내부 접근수준(기본값) -internal

internal 키워드로 지정하는 내부 접근수준은 기본적으로 모든 요소에 암묵적으로 지정하는 기본 접근수준이다. 내부 접근수준으로 지정된 요소는 소스파일이 속해 있는 모듈어디에서든 쓰일 수 있다. 다만 그 모듈을 가져다 쓰는 외부모듈에서는 접근이 불가하다. 보통 외부에서 사용할 클래스나 구조체가 아니며, 모듈내부에서 광역적으로 사용할 경우 내부접근수준을 지정한다.

 

파일 외부비공개 접근수준 - fileprivate

파일외부비공개 접근수준으로 지정된 요소는 그 요소가 구현된 소스파일 내부에서만 사용할 수 있다. 해당 소스파일 외부에서 값이 변경되거나 함수를 호출하면 부작용이 생길 수 있는 경우에 사용하면 좋다.

 

비공개 접근수준 - private

비공개 접근수준은 가장 한정적인 범위이다. 비공개 접근수준을 지정된 요소는 그 기능을 정의하고 구현한 범위 내에서만 사용할 수 있다. 비공개 접근수준으로 지정한 기능은 심지어 같은 소스파일안에 구현한 다른 타입이나 기능에서도 사용할 수 없다.

example

 

기능 정의 - private {}

소스파일 - fileprivate

모듈 - internal

모듈 외 - public / open

 

접근제어 구현

접근제어는 접근수준을 지정해서 구현할 수 있다. 각각의 접근수준을 요소앞에 지정해주기만 하면된다. internal은 기본 접근수준이므로 굳이 표기해주지 않아도 된다.

 

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

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

internal class InternalClass {}
internal struct InternalStrict {}
internal enum InternalEnum {}
internal var InternalVariable = 0
internal let InternalConstant = 0
internal func InternalFunction() {}

fileprivate class FileprivateClass {}
fileprivate struct FileprivateStrict {}
fileprivate enum FileprivateEnum {}
fileprivate var FileprivateVariable = 0
fileprivate let FileprivateConstant = 0
fileprivate func FileprivateFunction() {}

private class PrivateClass {}
private struct PrivateStrict {}
private enum PrivateEnum {}
private var PrivateVariable = 0
private let PrivateConstant = 0
private func PrivateFunction() {}

 

 

접근제어 구현 참고사항

모든 타입에 적용되는 접근수준의 규칙은 '상위 요소보다 하위 요소가 더 높은 접근 수준을 가질 수 없다.' 이다. 앞서 살펴봤던것처럼 비공개 접근수준으로 정의한 구조체 내부의 프로퍼티 내부수준이나 공개수준을 갖는프로퍼티를 정의할 수 없다. 또 함수의 매개변수로 특정 접근수준이 부여된 타입이 전달되거나 반환된다면, 그 타입의 접근수준보다 함수의 접근수준이 높게 설정될 수 없다.

 

잘못된 예를 살펴보자

잘못된 접근수준 부여

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

// AClass의 접근수준이 비공개 접근수준이므로
// 공개 접근수준 함수의 매개변수나 반환 값 타입으로 사용할 수 없다.
public func someFunction(a: AClass) -> AClass {
    // 오류발생 - Function cannot be declared public because its parameter uses a private type
    return a
}

 

 

 

함수 뿐만 아니라 튜플의 내부요소 타입또한 튜플의 접근수준보다 같거나 높아야 한다.

튜플의 접근수준 부여

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

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

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

 

 

접근수준에 따른 접근결과

// AClass.swift 파일과 Common.swift 파일이 같은 모듈에 속해 있을 경우
//AClass.swift 파일
class AClass2 {
    func internalMethod(){}
    fileprivate func filePrivateMethid() {}
    var internalProperty = 0
    fileprivate var filePrivatePropetry = 0
}

// Common.swift 파일
let aInstance = AClass2()
aInstance.internalMethod() // 같은 모듈이므로 호출 가능
aInstance.filePrivateMethid() // 다른모듈이므로 호출 불가
aInstance.internalProperty = 1 // 같은 모듈이므로 접근 가능
aInstance.filePrivatePropetry = 1 //다른 파일이므로 접근 불가

 

접근수준에 따라 접근이 불가능한 경우가 생긴다. 그렇기 때문에 프레임워크를 만들때는 다른 모듈에서 특정 기능에 접근 할 수 있도록 API로 사용할 시능을 공개접근수준으로 지정해주어야 한다. 그 외의 요소는 내부접근 수준 또는 비공개 접근수준으로 적절히 설정하면 된다.

 

열거형의 접근수준을 구현할때 열거형 내부의 각 case별로 따로 접근수준을 부여할 수는 없다. 각 case의 접근수준은 열거형 자체의 접근수준을 따른다. 또한 열거형의 원시값 타입으로 열거형의 접근 수준보다 낮은 접근수준의 타입이 올 수는 없다. 연관 값의 타입 또한 마찬가지이다. 

 

열거형의 접근수준

private typealias PointValue = Int

// 오류 - PointValue가 Point보다 접근수준이 낮기 때문에 원시값으로 사용할 수 없다.
fileprivate enum Point: PointValue { //오류발생 - Enum must be declared private or fileprivate because its raw type uses a private type => private이나 filePrivate으로 설정해야한다라고 설명이나온다.
    case x, y
}

 

 

private와 fileprivate

 

같은 파일 내부에서 private 접근수준과 fileprivate 접근수준은 사용할 때 분명한 차이가 있다. fileprivate접근수준으로 지정한 요소는 같은 파일 어떤 코드에서도 접근할 수 있다. 반면에 private접근수준으로 지정한 요소는 같은 파일내부에 다른 타입의 코드가 있더라도 접근이 불가능하다. 그러나 자신을 확장하는 익스텐션 코드가 같은 파일에 존재하는 경우에는 접근이 가능하다.

// 같은 파일에서의 private와 fileprivate의 동작
public struct SomeType {
    private var privateVariable = 0
    fileprivate var fileprivateVariable = 0
}

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

struct AnotherType {
    var someInstance = 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// 'privateVariable' is inaccessible due to 'private' protection level
//        self.someInstance.privateMethod()// 'privateMethod' is inaccessible due to 'private' protection level
    }
}

var anotherInstance = AnotherType()
anotherInstance.someMethod()

 

 

읽기 전용 구현

구조체 또는 클래스를 사용하여 저장 프로퍼티를 구현할 때는 허용된 접근수준에서 프로퍼티 값을 가져갈 수 있다. 그러면 값을 변경할 수 없도록 구현하고 싶다면 어떻게 해야할까 ? 또 서브스크립트도 읽기만 가능하도록 제한하려면 어떻게 해야할까?

 

그럴 때는 설정자 Setter만 더 낮은 접근수준을 갖도록 제한할 수 있다. 요소의 접근 수준키워드 뒤에 접근수준(set)처럼 표현하면 설정자의 접근수준만 더 낮도록 지정해줄 수 있다. 설정자 접근수준 제한은 프로퍼티, 서브스크립트, 변수 등에 적용될 수 있으며, 해당 쇼오의 접근수준보다 같거나 낮은 수준으로 제한해주어야 한다.

print("===설정자의 접근수준 지정===")
// 설정자의 접근수준 지정

public struct SomeType2 {
    //비공개 접근수준 저장 프로퍼티 count
    private var count: Int = 0
    
    //공개 접근수준 저장 프로퍼티 publicStoredProperty
    public var publicStoredProperty: Int = 0
    
    //공개 접근수준 저장프로퍼티 publicGetOnlyStoredProperty
    // 설정자는 비공개 접근수준
    public private(set) var publicGetOnlyStoredProperty: Int = 0
    
    // 내부 접근수준 저장프로퍼티 internalComuptedProperty
    var internalComputedProperty: Int {
        get {
            return count
        }
        set {
            count += 1
        }
    }
    
    // 내부 접근수준 저장 프로퍼티 internalGetOnlyComputedProperty
    // 설정자는 비공개 접근수준
    private(set) var internalGetOnlyComptutedProperty: Int {
        get {
            return count
        }
        set {
            count += 1
        }
    }
    
    //공개 접근수준 서브스크립트
    public subscript() -> Int {
        get {
            return count
        }
        set {
            count += 1
        }
    }
    
    //공개 접근 수준 서브스크립트
    //설정자는 내부 접근수준
    public internal(set) subscript(some: Int) -> Int {
        get {
            return count
        }
        set {
            count += 1
        }
    }
}

var someInstance = SomeType2()

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

//외부에서 접근자만 사용 가능
print(someInstance.publicGetOnlyStoredProperty) // 0
//someInstance.publicGetOnlyStoredProperty = 100
// 오류 발생 - Cannot assign to property: 'publicGetOnlyStoredProperty' setter is inaccessible

// 외부에서 접근자, 설정자 모두 사용 가능
print(someInstance.internalComputedProperty) // 0
someInstance.internalComputedProperty = 100// 100이라는 값을 할당하여 set설정자를 작동시켜 1이라는 값이 호출될것이다.
print(someInstance.internalComputedProperty) // 1

// 외부에서 접근자만 사용가능
print(someInstance.internalGetOnlyComptutedProperty) // 1
//someInstance.internalGetOnlyComptutedProperty = 100// 설정자(set)는 private이기때문에 오류발생

//외부에서 접근자, 설정자 모두 사용가능
print("==")
print(someInstance) //SomeType2(count: 1, publicStoredProperty: 100, publicGetOnlyStoredProperty: 0)

print(someInstance[]) // 1
print(someInstance[0]) // 1
print(someInstance[1]) // 1
print(someInstance[2]) // 1
someInstance.internalComputedProperty = 100
print(someInstance[]) // 2
print(someInstance[0]) // 2
print(someInstance[1]) // 2
print(someInstance[2]) // 2

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

 

포인트정리

- 접근제어는 코드끼리 상호작용을 할때 파일간 또는 모듈간에 접근을 제한하는 기능이며 불필요한 기능을 외부에 노출하는걸 막을 수 있다.

- 모듈이란 코드 묶음의 단위이며 스위프트에서 import로 불러오고 소스파일은 .swift파일 하나를 의미한다.

- open, public, internal, fileprivate, private 다섯가지의 접근수준이 존재하고 internal은 기본값이다.

- 주로 public은 외부인터페이스와연결할때 open은 클래스를 다른모듈에 상속할때 internal은 모듈내에서 사용할게할때 fileprivate은 .swift 파일안에서만 사용할때 private 타입이나 기능을 정의한 범위에서만 사용할때 사용된다.

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

- 함수 매개변수로 특정 접근수준이 부여된 타입이 전달되거나 반환되면 그 타입의 접근수준보다 함수의 접근수준이 높게 설정될 수 없다.

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

- private접근 수준을 지정한 요소는 파일내부에 다른 코드가 있더라도 접근이 불가한데.. 자신을 확장하는 익스텐션 코드가 같은 파일내 존재하는 경우 접근이 가능하다.

- 읽기전용으로만 구현하고 값을 변경할 수 없도록 구현하고 싶다면 private(set)으로 설정할 수 있다.

 

궁금증

- AnotherType 구조체가 internal접근수준이라서 someInstance 프로퍼티또한 internal수준일텐데 SomeType은 public이다.

  작은 레벨이 큰레벨을 가질 수없는데 어떻게 someIstance는 SomeType타입을 인스턴스화 하였을까 ?

- someInstance초기자 값을 SomeType2(count: 0, publicStoredProperty: 0, publicGetOnlyStoredProperty: 0)의 초기자 값에서 

- 마지막 예제) 에서 //외부에서 접근자, 설정자 모두 사용가능

                  someInstance[] = 100

                  print(someInstance[]) // 2

  이 []은 배열일 경우에 사용하는것으로 알고있는데 왜 []이게 등장했으며 어떻한 로직을 걸쳐 2이 출력되는것일까 ?

  => someInstance[] 인스턴스를 호출하면 1이나오고 someInstance.internalComputedProperty = 100을

     할당하면 이러한 someInstance[]의 값은 2로 나온다. 연산프로퍼티를 통해 count에 +=1 이 적용된것같은데

     someInstance[]는 0번째인덱스를 가르키는 표현일까 ? someInstance[0]과 someInstance[]은 같은표현인가?

Reference

- 야곰책 3판 Swift 프로그래밍