Reflection Type in Swift

Swift에서는 (아직까지) NS...FromString이나 respondsToSelector:와 같은 편리한 메타프로그래밍 도구가 지원되지 않지만, 간접적으로 이런 것들을 지원하게끔 하고는 있다. (추가적으로 네이티브의 성격으로 지원돌 것으로 보인다. 왜냐하면 기본적인 프로토콜들이 내부적으로는 내부 프로토콜을 상속하면서 아무 것도 정의되지 않은 게 많다.)

타입 반영

타입 체크 연산자인 is 가 일종의 타입반영을 지원한다. Objective-C에서 다음과 같은 코드를 볼 수 있는데

- (void)doSomethingWithObject:(id)someObject {
    if ([object isKindOfClass:[SomeClass class]]) {
        NSLog(@"object is of type MyObject");
    }
}

이는 Swift에서 is 연산자로 똑같이 할 수 있다.

func doSomething(someObject: AnyObject) {
    if someObject is SomeClass {
        println("ohh")
    }
}

메타타입

메타타입은 클래스 그 자체를 값으로 사용하는 것이다. Objective-C의 Class 프로퍼티가 그에 해당한다.

Class objectType = [SomeClass class];

이는 코드 상에서 클래스타입과 동일한 의미가 되므로

SomeClass *myObject = [[objectType alloc] init];

과 같이 쓸 수 있다. Swift에서는 클래스의 self 프로퍼티가 메타타입 값을 내놓는다.

let objectType = SomeClass.self
let newObject = objectType()

객체인스턴스에 대해서는 .dynamicType이 같은 역할을 할 수 있다.

let objectType = newObject.dynamicType

단, 값타입의 타입들에 대해서는 .Type 속성이 같은 의미이다.

리플렉션 API

Xcode의 Swift.swift 파일에는 Reflectable 프로토콜과 MirrorType이 정의되어 있다.

protocol Reflectable {
    func getMirror() -> MirrorType
}

이 프로토콜은 다음과 같이 정의된 것으로 보인다.

protocol MirrorType {

    /// The instance being reflected
    var value: Any { get }

    /// Identical to `value.dynamicType`
    var valueType: Any.Type { get }

    /// A unique identifier for `value` if it is a class instance; `nil`
    /// otherwise.
    var objectIdentifier: ObjectIdentifier? { get }

    /// The count of `value`\ 's logical children 
    var count: Int { get }
    subscript (i: Int) -> (String, MirrorType) { get }

    /// A string description of `value`.
    var summary: String { get }

    /// A rich representation of `value` for an IDE, or `nil` if none is supplied.
    var quickLookObject: QuickLookObject? { get }

    /// How `value` should be presented in an IDE.
    var disposition: MirrorDisposition { get }
}

부연을 붙여보자면 미러타입은 내부값에 대한 “반영 인터페이스”를 제공하는 것으로 보인다. 이를 사용하는 예를 들어보자.

Inspecting Values

표준함수 reflect()는 어떤 임의의 타입의 값을 받아서, 해당 값의 타입의 반영 타입을 이용해서 여러 정보를 제공할 수 있다. 이는 특정 객체 내부의 정보를 검토할 수 있게 한다. 단 파이썬처럼 쉬운 방식은 아니고 조금 복잡한 과정을 거친다.

func reflect<T>(x: T) -> MirroType

예를 들어 음료 제조자에 대한 타입을 다음과 같이 정의했다고 하자.

struct Beer {
    let type: BeerType
    let name: String

    init(_ type: BeerType, name: String) {
        self.type = type
        self.name = name
    }
}

음료(!) 정류를 나타내는 열거타입도 하나 선언하자

enum BeerType {
    case IndiaPaleAle
    case Stout
    case Porter
}

이제 인스턴스를 하나 만들어보자.

let brew = Beer(BeerType.Porter, name: "Vanilla Java")

이 인스턴스의 반영 객체를 얻는다.

let brewMirror = reflect(brew)

이 반영 객체는 원 객체의 구조와 각 멤버들을 반영한다. 즉 반영 객체를 통해서 원객체를 들여다볼 수 있게 된다.

brewMirror --> brew를 반영하는 객체
brewMirror[0] --> brew.type을 반영하는 객체
brewMirror[1] --> brew.name을 반영하는 객체 

각 자식 반영 객체는 튜플의 형태로 돌아온다. MirrorType의 subscript는 다음과 같이 정의되어 있다.

subscript(i: Int) -> (String, MirrorType) { get }

따라서,

let (propertyName, childMirror) = brewMirror[1]
println("property name: \(propertyName)")
// "property name: name"
println("property value: \(childMirror.value)")
//  "property value: Vanilla Java"

와 같이 알 수 있게 된다.

이제 brew 객체의 내부 정보를 확인하는 코드를 작성해보자.

for i in (0..brewMirror.count) {
    let (propertyName, childMirror) = brewMirror[i]
    if childMirror.valueType is String.Type {
        println("propertys is of type String")
    }
    else if childMirror.valueType is BeerType.Type {
        prntln("property is of BeerType")
    }
    println("property name: \(propertyName)")
    println("property value: \(childMirror.value)")
    println("-")
}

실행 결과는 다음과 같다.

property is of BeerType  
property name: type  
property value: (Enum Value)  
-----
property is of type String  
property name: name  
property value: Vanilla Java  
-----