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
-----