Open Phangg opened 6 months ago
런타임
에 조사할 수 있도록 하는 기능dump
그리고 TCA 에서 사용하던 ._printChanges
dump
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" )
print(phang)
// __lldb_expr_49.Person
dump(phang, name: "phang") /* ▿ phang: __lldb_expr_49.Person #0
- [`dump` 공식문서](https://developer.apple.com/documentation/swift/dump(_:name:indent:maxdepth:maxitems:)) 를 보면, `Mirror` 를 사용하여 지정된 객체의 내용을 표준 출력으로 보여주고 있다는 것을 알 수 있음
<br>
._printChanges
refelction
을 활용했다고 함..
received action:
AppContentViewFeature.Action.appStart(
.setName("fhbvgh")
)
AppContentViewFeature.State(
_books: Shared(…),
_records: #1 Shared(…),
_appStart: AppStartFeature.State(
_books: Shared(↩︎),
_records: #1 Shared(↩︎),
_isAppStarting: false,
_appLogoDegreeChange: true,
_isOnboardingCompleted: true,
_currentOnboardingPage: .last,
onboardingData: […],
_isSetName: false,
._printChanges
메서드를 타고타고 들어가보면, CustomDump
를 사용하고 있는 것을 볼 수 있음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
// property 는 (label: _, value: _) 튜플 형태
mirror.children.forEach { property in
print("\(property.label ?? "?") 의 값은 \(property.value) 입니다.")
}
/*
name 의 값은 Phang 입니다.
age 의 값은 30 입니다.
gender 의 값은 male 입니다.
address 의 값은 Seoul Seocho Seounro xxx xxx-xxxx 입니다.
*/
// 프로퍼티 변환 프로토콜 선언
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()
}
}
reflection
을 통해 해결해보자
// 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()
}
}
}
}
위에서 사용한 코드를 더 활용하는 코드도 한번 살펴보자
// Mirror 에서 확장
extension Mirror {
// target : reflection 대상
// type : value 타입 ( 제네릭 T 사용 )
// closure : 수행할 동작
static func reflectProperties<T>(
of target: Any,
matchingType type: T.Type = T.self,
using closure: (T) -> Void
) {
let mirror = Mirror(reflecting: target)
mirror.children.forEach { property in
(property.value as? T).map(closure)
}
}
}
// Mirror 의 확장 메서드 reflectProperties 활용 예시
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 하게 되는 객체의 크기나 중첩도에 따라서도 성능에 문제가 될 수 있음
Swift의 reflection에 대해 설명해주세요.