인스턴스를 들여다보는 Mirror

SteadySlower·2024년 2월 2일
0

iOS Development

목록 보기
37/38

오늘은 swift에서 reflection을 할 수 있도록 해주는 Mirror라는 객체에 대해서 소개해보도록 한다.

reflection이란?

reflection을 위한 객체가 mirror라고 했다. 그렇다면 reflection의 정의를 알고 가야 Mirror에 대한 설명을 이어갈 수 있다. reflection은 프로그램이 자기자신의 구조를 분석하고 수정하는데 사용되는 프로그래밍 기법을 의미한다. 구체적으로 프로그램이 이미 실행되는 런타임에서 동적으로 객체의 정보를 얻을 수 있는 조사할 수 있는 능력을 의미한다. 코드, 컴파일 단계에서 미리 정해진 객체의 정보가 아니라 런타임에서 객체의 정보를 얻을 수 있기 때문에 Swift처럼 정적 타입의 언어는 핵심적으로 사용되는 기능은 아니다.

Swift에서는 reflection을 통해서 객체 (class, struct, enum)의 내부를 살펴볼 수 있도록 mirror 객체를 제공한다.

Mirror 구현해보기

객체 선언하기

자 일단은 아래와 같이 3가지 객체 유형 enum, struct와 class을 모두 선언했다. 특히 class에는 type property와 computed property도 함께 선언했는데, Mirror를 통해 보면 각각이 어떻게 보이는지 살펴보자.

enum Subject {
    case english, math, science
}

struct Student {
    let name: String
    let grade: Int
    let favoriteSubject: Subject
}

class ClassRoom {
    let grade: Int
    var students: [Student]
    
    init(grade: Int, students: [Student]) {
        self.grade = grade
        self.students = students
    }
    
    static let school: String = "Some School"
    
    var description: String {
        "This is class for grade \(grade)!"
    }
}

Mirror 인스턴스 만들기

mirror를 통해 reflect할 수 있는 대상은 객체 그 자체가 아니라 인스턴스이다. 따라서 객체들을 인스턴스화한 이후에 이를 활용해 mirror의 인스턴스를 만든다.

let subject = Subject.english
let student = Student(name: "Kim", grade: 1, favoriteSubject: subject)
let classRoom = ClassRoom(grade: 1, students: [student])

let enumMirror = Mirror(reflecting: subject)
let structMirror = Mirror(reflecting: student)
let classMirror = Mirror(reflecting: classRoom)

children

가장 먼저 살펴볼 기능은 children이다. 이는 인스턴스가 가지고 있는 모든 “stored property”와 이름과 값을 튜플로 짝지어 알려준다.

“stored property”에 대한 정보만을 제공하기 때문에 “stored property”를 가질 수 없는 children이 없다. 또한 “type property”나 “computed property”에 대한 정보 역시 children에는 포함되지 않는다.

for c in enumMirror.children {
    print(c)
}
/*
 🖨️ 출력 결과
 (없음)
 */

for c in structMirror.children {
    print(c)
}
/*
 🖨️ 출력 결과
 (label: Optional("name"), value: "Kim")
 (label: Optional("grade"), value: 1)
 (label: Optional("favoriteSubject"), value: algorithm_study.Subject.english)
 */

for c in classMirror.children {
    print(c)
}
/*
 🖨️ 출력 결과
 (label: Optional("grade"), value: 1)
 (label: Optional("students"), value: [algorithm_study.Student(name: "Kim", grade: 1, favoriteSubject: algorithm_study.Subject.english)])
 */

description

description은 mirror가 어떤 객체의 mirror인지 알려준다. 즉 인스턴스가 어떤 객체의 인스턴스인지 알 수 있다.

print(enumMirror.description)
// 🖨️ 출력 결과: Mirror for Subject
print(structMirror.description)
// 🖨️ 출력 결과: Mirror for Student
print(classMirror.description)
// 🖨️ 출력 결과: Mirror for ClassRoom

displayStyle

마지막으로 알아볼 속성은 desplayStyle이다. 이는 해당 인스턴스의 객체의 Type을 알려준다.

print(enumMirror.displayStyle)
// 🖨️ 출력 결과: Optional(Swift.Mirror.DisplayStyle.enum)
print(structMirror.displayStyle)
// 🖨️ 출력 결과: Optional(Swift.Mirror.DisplayStyle.struct)
print(classMirror.displayStyle)
// 🖨️ 출력 결과: Optional(Swift.Mirror.DisplayStyle.class)

Mirror의 용도

이렇게 보면 Mirror 도대체 뭐에 쓸모가 있을까 싶다. 변수에 아무 타입이나 재할당할 수 있는 동적 타입의 언어 (파이썬, 자바스크립트 등)에서는 귀중한 기능이 될 수도 있느나 Swift는 어차피 정적 타입의 언어이다. 하지만 Mirror도 나름의 용도가 존재한다.

디버깅 및 테스팅

디버깅 및 테스팅을 할 때 객체의 내부 상태를 추적할 수 있는 로그가 있다면 매우 유용할 것이다. mirror는 객체 내부의 정보를 동적으로 살펴볼 수 있고 String으로 풀어서 제공하기 때문에 로깅을 할 때 유용하다.

동적 데이터 처리

property의 이름을 String으로 전달 받을 수 있기 때문에 JSON 객체를 동적으로 만들 수 있다. (근데 이 부분은 이미 Codable이 있어서 별로…)

문서 자동화

리플렉션을 활용해서 객체의 속성을 들여다 볼 수 있기 때문에 문서 작성을 자동화할 수 있다.

마치며: 나는 왜 Mirror에 주목하게 되었는가?

요즘 우리 회사는 개발 환경의 대혁명(?)이 일어나고 있다. 시니어 분이 한명 오시면서 좀 더 체계적인 개발 문화를 만들어가기 위해 노력하고 계시고 기존의 주니어들도 열심히 참여하고 있다. 이 과정에서 좀 더 협업에 최적화된 코드와 디버깅이 쉬운 로깅을 도입하고 있는데 그 과정에서 Mirror에 대해서 알게 되었다.

사실 아직까지는 Mirror가 얼마나 유용하게 쓸 수 있을지는 모르겠다. 특히 성능에 영향을 줄 수 있다는 점도 있어서 모든 로깅에 사용하면 문제가 발생할 수 있을 것 같다. 좀 더 공부해봐야겠다.

profile
백과사전 보다 항해일지(혹은 표류일지)를 지향합니다.

0개의 댓글