2014년 7월 23일 수요일

Swift - 액세스 컨트롤(Access Control)

액세스 컨트롤(Access Control), 대충 직역하여 '접근 제어자'라고 표현하는데 이해가 바로 가는 표현은 아니다. 이 용어는 클래스의 설계와 관련된 부분으로, 클래스의 프로퍼티나 메소드의 접근에 제한을 설정할 수 있도록 하는 기능이다.

스위프트(Swift)는 Xcode 6 Beta 4 부터 이 엑세스 컨트롤 키워드가 추가되었다. 간단히 살펴보자.

현재까지 제공되는 스위프트용 엑세스 컨트롤 키워드는 다음 3가지가 있다:
  • private
  • internal
  • public
internal을 빼고는 유명한 키워드 같이 보인다. 각 키워드 별로 살펴보자.

private

일반적으로 클래스의 private 멤버는 클래스 자기 자신이나 이 클래스를 상속받은 자식 클래스 내부에서만 접근이 가능하도록 제약하는 용도이다. 따라서 클래스 외부에서는 쓰는 것은 물론이고 읽는 것 조차 금지된다.

하지만 스위프트의 private는 '동일 파일 내에서만 접근이 허락되고 이 외에는 접근을 금지시킴' 이라는 의미로 사용된다.

private는 멤버 뿐만 아니라 클래스 자체 혹은 생성자(init)에도 사용이 가능하다. 그리고 멤버가 private class 타입 형식을 가지려면 이 멤버도 반드시 private여야한다는 제약이 있다.

추가: 'private(set)' 이라는 지시어를 통해 값을 바꾸는 것만 private로 처리하고 읽는 것은 internal 식으로 되게 하는 방법도 있다.

public

단어 의미대로, public은 어디서든 접근이 허락되는 자유로움을 주는 키워드이다. 프로젝트나 타겟을 가리지 않고 네임스페이스에 선언만 되어있다면 어디서든 접근 할 수 있다.

따라서 프로젝트가 멀티타겟이거나 혹은 테스트코드를 작성하는 경우가 아니라면 별로 쓸 일은 없을 것이다.

internal

internal은 스위프트만의 독창적인(?) 엑세스 컨트롤 키워드이다. 단어 자체의 의미와 비슷하게, internal이 명시되면 '해당 멤버나 클래스는 사용되는 프로젝트의 타겟에 소속된 곳이라면 어디서든 접근이 허락된다' 라는 의미가 된다.

사실상 프로젝트가 멀티타겟이 아니라면 public과 동급이다.

접근제어자를 명시하지 않으면 이 internal 이 기본으로 적용된다.

예제

아래와 같은 코드를 특정 파일에 코딩했다고 치자.
import Foundation

// private class 는 다른 파일에서 접근이 불가능한 클래스를 정의한다.
private class LittleSomeClass {
    var value = 0
    
    init(littleValue: Int) {
        self.value = littleValue
    }
}

class SomeClass {
    // private이기 때문에 이 파일 내부에서만 접근이 가능하다.
    private var value: Int = 0
 
    // LittleSomeClass 는 private class 이기 때문에 private를 명시해야 한다:
    // Property must be declared private because its type uses a private type.
    private var little: LittleSomeClass?
    
    // 외부에서 value를 얻으려면 이 프로퍼티를 이용해야 한다.
    var someGetter: Int {
        return self.value
    }

    // 값의 대입(set)만 private로 설정한 프로퍼티.
    private(set) var readonlyProperty: Int = 0

    init(initialValue: Int) {
        self.value = initialValue
        self.readonlyProperty = self.value * 2
    }
    
    // private init() 은 이 파일 내부에서 밖에 사용 할 수 없다.
    private init(initialValue: Int, littleValue: Int) {
        self.value = initialValue
        self.little = LittleSomeClass(littleValue: littleValue)
    }
}

// 아래 함수는 SomeClass의 private init 을 사용 하도록 의도한 코드.
func specialSomeClassConstructor() -> SomeClass {
    return SomeClass(initialValue: 100, littleValue: 200)
}
그리고 아래는 이 클래스를 사용하는 코드이다. 예제 상 위 클래스가 정의된 파일이 아닌 다른 파일에 코딩되어 있다고 가정하자.
// 문제없음
let sc = SomeClass(initialValue: 100)

// 아래 코드는 private 멤버 엑세스 시도라서 에러 발생:
// 'SomeClass' does not have a member named 'value'.
println(sc.value)

// 아래 코드는 정상 동작함(콘솔에 100이 찍힘)
println(sc.someGetter)

// 아래 코드는 정상 동작함(콘솔에 200이 찍힘)
println(sc.readonlyProperty)

// 아래 코드는 private(set)에 의해 에러 발생:
// Cannot assign to 'readonlyProperty' in 'sc'
sc.readonlyProperty = 100

// 아래 코드는 private init(생성자)를 호출하기 때문에 에러 발생:
// Extra argument 'littleValue' in call
let sc2 = SomeClass(initialValue: 100, littleValue: 200)

// 문제없음.
let sc3 = specialSomeClassConstructor()
대충 주석으로 설명했으니 이해는 될 것이다.

에러 내용을 영문으로 달아놨는데 표현을 보면 '접근 할 수 없다' 라는게 아니라 '찾을 수 없다' 라는 식의 표현이다. 이걸 중점으로 살펴보면, 스위프트의 엑세스 컨트롤은 기존 Objective-C 방식에서 헤더파일이 하던 '정의를 알려주기' 기능을 대신하고 있다고 생각 할 수 있다.

정리

스위프트의 액세스 컨트롤은 일반적인 OOP 언어들이 제공하는 것과는 개념이 좀 다르다. 접근 제한의 스코프를 파일이나 타겟이라는 단위에 두고 있기 때문이다.

이런 차원이 다른 개념 덕분에 익숙하지 않을 수도 있는데, C나 Objective-C를 좀 써 봤다면 오히려 익숙한 개념일지도 모른다. 헤더파일이 해야 하는 역활을 접근제어자로 대신하는 형태이기 때문이다. 헤더파일의 역활은 정의(Definition and Declaration)를 다른 곳에 알려줌으로써 '이런 것이 있으니 써라' 라고 표현하는 파일이다.

만약 클래스에서 private로 표기한다면 이는 헤더파일에서 정의를 명시하지 않은 private API 등과 동급의 의미가 된다. 따라서 스위프트로 만든 모듈 소스코드를 외부에 전달 할 때 써야 할 것과 쓰지 말아야 할 것을 어느정도 구분해 줄 수 있는 척도가 된다.

단지, 이런 개념이 무조건 좋다라고는 할 순 없을 것이다. 분명히 장단점이 있을 것이다. 그보다 내가 걱정하는건 또 바뀌지 않을까 하는 점이다. 스위프트는 계속 바뀌고 있으니까.

[관련글] Swift 프로젝트의 유닛테스트(Unit Test)
[관련글] 스위프트(Swift) 가이드

댓글 없음 :