4T2F / ThinkBig2

🌟씽크빅 2팀 스터디 🌟
2 stars 0 forks source link

Swift의 reflection에 대해 설명해주세요. #37

Open Phangg opened 6 months ago

Phangg commented 6 months ago

Swift의 reflection에 대해 설명해주세요.

Phangg commented 6 months ago

Swift 의 Reflection



Reflection 인줄 모르고 사용하던, dump 그리고 TCA 에서 사용하던 ._printChanges

dump

class Person { let name: String var age: Int let gender: Gender var address: String

init(
    name: String,
    age: Int,
    gender: Gender,
    address: String
) {
    self.name = name
    self.age = age
    self.gender = gender
    self.address = address
}

static let species = "사람"

var description: String {
    "\(name): \(address)에 사는 \(age)살의 \(gender.rawValue)"
}

}

let phang = Person( name: "Phang", age: 30, gender: .male, address: "Seoul Seocho Seounro xxx xxx-xxxx" )

print(phang)
// __lldb_expr_49.Person

dump(phang, name: "phang") /* ▿ phang: __lldb_expr_49.Person #0

._printChanges



그렇다면 Mirror 는 무엇인가?

enum Gender: String {
    case male = "남성"
    case female = "여성"
}

class Person {
    let name: String
    var age: Int
    let gender: Gender
    var address: String

    init(
        name: String,
        age: Int,
        gender: Gender,
        address: String
    ) {
        self.name = name
        self.age = age
        self.gender = gender
        self.address = address
    }

    static let species = "사람"

    var description: String {
        "\(name): \(address)에 사는 \(age)살의 \(gender.rawValue)"
    }
}

let phang = Person(
    name: "Phang",
    age: 30,
    gender: .male,
    address: "Seoul Seocho Seounro xxx xxx-xxxx"
)

let mirror = Mirror(reflecting: phang)

print(mirror)                       // Mirror for Person
print(mirror.displayStyle)          // Optional(Swift.Mirror.DisplayStyle.class)
print(mirror.subjectType)           // Person
print(mirror.children)              // AnyCollection<(label: Optional<String>, value: Any)>(_box: Swift._RandomAccessCollectionBox<Swift.LazyMapSequence<Swift.Range<Swift.Int>, (label: Swift.Optional<Swift.String>, value: Any)>>)
print(mirror.children.map { $0 })   // [(label: Optional("name"), value: "Phang"), (label: Optional("age"), value: 30), (label: Optional("gender"), value: __lldb_expr_85.Gender.male), (label: Optional("address"), value: "Seoul Seocho Seounro xxx xxx-xxxx")]
print(mirror.superclassMirror)      // nil
print(mirror.description)           // Mirror for Person

하나의 예시로, 최근 Clean Architecture 를 학습하던 중 프로퍼티를 Dictionary 형태로 변환하는 코드를 만든 적이 있음

// 프로퍼티 변환 프로토콜 선언
protocol PropertyIterator { }

extension PropertyIterator {
    // Dictionary 로 만들기
    func dictionaryProperties() -> [String: Any] {
        // Mirror 로 메서드가 사용될, self 자체를 reflecting 
        let mirror = Mirror(reflecting: self)
        var dictionary: [String: Any] = [:]
        mirror.children.forEach { property in
            dictionary[property.label ?? String()] = property.value
        }
        return dictionary
    }
}


다른 예시도 하나 살펴보자 [ 해당 예시 참고 사이트 ]

// 로그인한 유저의 세션을 확인하기 위한 class 
class UserSession {
    let credentials: CredentialsStorage
    let favorites: FavoritesStorage
    let settings: SettingsStorage
}

// 로그아웃 시, 각각 reset() 을 실행하도록 함
extension UserSession {
    func logOut() {
        credentials.reset()
        favorites.reset()
        settings.reset()
    }
}
// reset 에 약항을 할 프로토콜 선언
protocol Resettable {
    func reset()
}

extension CredentialsStorage: Resettable {}
extension FavoritesStorage: Resettable {}
extension SettingsStorage: Resettable {}
// UserSession 의 logOut 메서드자체에 refelction 을 사용한 Mirror 를 적용
extension UserSession {
    func logOut() {
        let mirror = Mirror(reflecting: self)

        // 현재 value 는 CredentialsStorage, SettingsStorage, SettingsStorage 타입 값
        // 각자 Resettable 프로토콜을 받아서, 구현한 reset 메서드를 실행
        mirror.children.forEach { property in
            if let resettable = property.value as? Resettable {
                resettable.reset()
            }
        }
    }
}


extension UserSession { func logOut() { Mirror.reflectProperties(of: self) { (property: Resettable) in property.reset() } } }

extension DataController { func preload() { Mirror.reflectProperties(of: self) { (property: Preloadable) in property.preload() } } }

extension CacheController { func warmUp() { Mirror.reflectProperties(of: self) { (property: Cache) in property.warmUp() } } }


<br>

### 그렇다면, 이렇게 Mirror 를 통해 만드는 것의 장점은?

1. 객체의 속성을 자동으로 검사 할 수 있음
객체의 타입이나 구조를 알지 못하는 상황에서도 mirror 를 통해, 찾을 수 있음
2. 객체의 속성을 동적으로 처리 할 수 있음
mirror 를 통해 얻은 정보를 기반으로, 속성을 조작할 수 있음
3. 객체의 타입에 영향을 받지 않음
일관된 방식으로 속성을 다루기 때문에 재사용성을 높일 수 있음
<br>

### reflection 을 사용할 때, 고려해야 할 점이 있다면?

- 런타임에서 동작하기 때문에, 오버헤드가 발생할 수 있음
- 반복적으로 많은 객체를 검사하거나, 일정 동작을 수행해야 한다면 성능이 떨어질 것
- reflecting 하게 되는 객체의 크기나 중첩도에 따라서도 성능에 문제가 될 수 있음