런타임에 Swift 객체 분석하기: Mirror의 모든 것

작성일 :

런타임에 Swift 객체 분석하기: Mirror의 모든 것

Swift는 강력하고 다재다능한 언어로, 다양한 기능들을 제공합니다. 그중에서도 특히 주목할 만한 기능이 바로 Mirror입니다. 이 글에서는 Mirror를 사용하여 런타임에 Swift 객체를 분석하는 방법을 알아보겠습니다. Mirror는 객체의 속성, 메소드 등 다양한 정보를 제공하여 디버깅이나 테스트, 리플렉션(reflection) 작업에 많은 도움을 줍니다.

Mirror란 무엇인가?

Mirror는 Swift 언어에서 제공하는 리플렉션 도구입니다. 리플렉션이란 프로그램이 런타임에 자기 자신을 검사하거나 수정할 수 있는 능력을 말합니다. Mirror를 사용하면 클래스, 구조체, 열거형 등의 객체에 대한 메타데이터를 추출할 수 있습니다. 이를 통해 객체의 속성, 타입, 값 등을 동적으로 탐색할 수 있게 됩니다.

swift
struct Person {
    var name: String
    var age: Int
}

let person = Person(name: "Alice", age: 30)
let mirror = Mirror(reflecting: person)

위 코드 예제에서 Person 구조체의 인스턴스를 생성하고, 이를 Mirror를 통해 반영(reflect)하고 있습니다. 이제 mirror 객체를 통해 person 객체의 정보를 얻을 수 있습니다.

Mirror의 기본 사용법

Mirror의 가장 기본적인 사용법은 객체의 속성을 나열하는 것입니다. Mirror 인스턴스는 children 프로퍼티를 통해 객체의 각 속성을 나타내는 튜플의 배열을 제공합니다.

swift
for child in mirror.children {
    print("\(child.label!): \(child.value)")
}

위 코드는 Mirror 객체의 children 프로퍼티를 순회하며, 각 속성의 이름과 값을 출력합니다. 출력 결과는 다음과 같습니다:

name: Alice
age: 30

이를 통해 person 객체의 내부 구조를 런타임에 동적으로 파악할 수 있습니다.

심화 기능: Mirror를 활용한 객체 분석

Mirror는 단순히 속성 나열뿐만 아니라 다양한 기능을 제공합니다. 예를 들어, 객체의 타입, 부모 클래스, 디스플레이 스타일 등을 확인할 수 있습니다.

객체의 타입 확인하기

Mirror 객체의 subjectType 프로퍼티를 사용하면 해당 객체의 타입을 알 수 있습니다.

swift
print("Type of person: \(mirror.subjectType)")

출력 결과는 다음과 같습니다:

Type of person: Person

부모 클래스 탐색하기

객체가 클래스인 경우, superclassMirror 프로퍼티를 통해 부모 클래스의 Mirror를 얻을 수 있습니다. 이를 통해 클래스 계층 구조를 탐색할 수 있습니다.

swift
if let superclassMirror = mirror.superclassMirror {
    for child in superclassMirror.children {
        print("Superclass property: \(child.label!): \(child.value)")
    }
}

이 방법을 사용하면 상속 계층 구조에서 상위 클래스의 속성도 분석할 수 있습니다.

CustomReflectable 프로토콜

객체의 리플렉션 출력을 커스터마이징하고 싶다면 CustomReflectable 프로토콜을 사용할 수 있습니다. 이 프로토콜을 채택한 객체는 자신만의 Mirror를 제공할 수 있습니다.

swift
struct CustomPerson: CustomReflectable {
    var name: String
    var age: Int
    var customMirror: Mirror {
        return Mirror(self, children: ["customName": name, "customAge": age])
    }
}

let customPerson = CustomPerson(name: "Bob", age: 25)
let customMirror = Mirror(reflecting: customPerson)
for child in customMirror.children {
    print("\(child.label!): \(child.value)")
}

출력 결과는 다음과 같습니다:

customName: Bob
customAge: 25

실용적인 사용 사례

Mirror는 다양한 상황에서 유용하게 사용될 수 있습니다. 예를 들어, JSON 직렬화 및 역직렬화, 디버깅 및 테스트, 그리고 게터 및 세터를 동적으로 호출하는 등의 작업에 활용될 수 있습니다.

JSON 직렬화 지원

리플렉션을 통해 객체를 JSON으로 직렬화할 수 있는 유틸리티를 쉽게 작성할 수 있습니다.

swift
func toJSON(_ value: Any) -> String? {
    let mirror = Mirror(reflecting: value)
    var dict = [String: Any]()
    for child in mirror.children {
        guard let label = child.label else { continue }
        dict[label] = child.value
    }
    if let jsonData = try? JSONSerialization.data(withJSONObject: dict, options: .prettyPrinted) {
        return String(data: jsonData, encoding: .utf8)
    }
    return nil
}

let jsonString = toJSON(person)
print(jsonString ?? "Failed to serialize to JSON")

디버깅 및 테스트

디버깅 시 객체 상태를 빠르게 파악하는 데 Mirror를 사용할 수 있습니다. 예를 들어, 변경이 많이 이루어지는 객체의 현재 상태를 검증할 때 유용합니다.

swift
func debugPrint(_ value: Any) {
    let mirror = Mirror(reflecting: value)
    for child in mirror.children {
        print("\(child.label!): \(child.value)")
    }
}

debugPrint(person)

결론

Swift의 Mirror는 런타임 객체 분석을 가능하게 하는 매우 강력한 도구입니다. 이를 사용하여 동적인 런타임 정보를 얻고, 디버깅, 테스트, JSON 직렬화 등 다양한 작업을 효율적으로 수행할 수 있습니다. 이 글에서는 Mirror의 기본 개념과 사용법, 그리고 다양한 실용적인 사용 사례를 다루었습니다. Mirror를 적절히 활용하면 Swift 프로그래밍에서 한층 더 높은 수준의 유연성과 효율성을 달성할 수 있을 것입니다.