본문 바로가기
YAGOM CAREER STARTER

[TIL] 20230508: Mirror

by Rhode 2023. 5. 9.

본문은 야곰 아카데미 커리어 스타터 캠프를 통해 학습한 내용을 회고한 글입니다.


Mirror

모든 유형의 인스턴스에 대한 하위 구조 및 표시 스타일의 표현

iOS 8.0+ iPadOS 8.0+ macOS 10.10+ Mac Catalyst 13.0+ tvOS 9.0+ watchOS 2.0+


Declaration

struct Mirror

Overview

mirror는 인스턴스의 저장 프로퍼티, 컬렉션이나 튜플 요소들 혹은 active enumeration case와 같은 특정 인스턴스를 구성하는 부분을 설명한다. Mirror는 또한 이 mirror가 렌더링 될 수 있는 방법을 제안하는 “display style” 프로퍼티를 제공한다.

Playgrounds와 debugger는 Mirror 타입을 사용하여 모든 유형의 값 표현을 표시한다. 예를 들어서, 인스턴스를 dump(::::) 함수에 전달하면, 해당 인스턴스의 런타임 콘텐츠를 렌더링하는 데에 mirror가 사용된다.

struct Point {
    let x: Int, y: Int
}

let p = Point(x: 21, y: 30)
print(String(reflecting: p))
// Prints "▿ Point
//           - x: 21
//           - y: 30"

mirror 표현의 커스텀 타입을 커스텀하려면 CustomReflectable 프로토콜에의 적합성을 추가하라.


--- # init(reflecting: Any)

주어진 인스턴스를 반영하는 mirror를 만든다.

iOS 8.0+ iPadOS 8.0+ macOS 10.10+ Mac Catalyst 13.0+ tvOS 9.0+ watchOS 2.0+


Declaration

init(reflecting subject: Any)

Parameters

subject

mirror를 생성할 인스턴스

Discussion

subject의 dynamic type이 CustomReflectable을 준수하는 경우, mirror는 customMirror 프로퍼티에 의해 결정된다. 그렇지 않으면, 결과는 언어에 의해 생성된다.

만약 subject의 dynamic type에 값 의미론이 있는 경우, subject의 그 다음의 변화는 Mirror에서 관찰 될 수 없다. 하지만, 일반적으로, 변화의 관찰가능성은 지정되지 않는다.



children: Mirror.Children

반영된 subject의 구조를 설명하는 Child 요소의 컬렉션.

iOS 8.0+ iPadOS 8.0+ macOS 10.10+ Mac Catalyst 13.0+ tvOS 9.0+ watchOS 2.0+


Declaration

let children: Mirror.Children


제네릭한 CoreDataManager를 만들고 싶어서 용쓰다가 찾아본 친구..

struct Dog {
    let name: String
    let sex: String
    let birthday: Int
    var age: Int
}

let hodoo = Dog(name: "hodoo", sex: "girl", birthday: 170227, age: 6)

let hodooMirror = Mirror(reflecting: hodoo)
let hodooMirrorChildren = hodooMirror.children

print(hodooMirrorChildren.compactMap { $0.label })  //["name", "sex", "birthday", "age"]
print(hodooMirrorChildren.compactMap { $0.value })  //["hodoo", "girl", 170227, 6]
print(hodooMirrorChildren.compactMap({ $0.self }))  //[(label: Optional("name"), value: "hodoo"), (label: Optional("sex"), value: "girl"), (label: Optional("birthday"), value: 170227), (label: Optional("age"), value: 6)]

for children in hodooMirrorChildren {
    print(children)
}
/*
 (label: Optional("name"), value: "hodoo")
 (label: Optional("sex"), value: "girl")
 (label: Optional("birthday"), value: 170227)
 (label: Optional("age"), value: 6)
 */

요런 결과가 나왔달까..
그래서 어떻게 사용했냐면..

import CoreData
import Foundation

class CoreDataManager<T> {
    lazy var persistentContainer: NSPersistentContainer = {
        let container = NSPersistentContainer(name: "Diary")
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        })

        return container
    }()

    var context: NSManagedObjectContext {
        return persistentContainer.viewContext
    }

    var diaryEntity: NSEntityDescription? {
        return  NSEntityDescription.entity(forEntityName: "Entity", in: context)
    }

    func createDiary(_ diary: T) {
        if let entity = diaryEntity {
            let managedObject = NSManagedObject(entity: entity, insertInto: context)

            let properties = Mirror(reflecting: diary).children.filter { $0.label != nil }

            for property in properties {
                guard let label = property.label else { return }
                managedObject.setValue(property.value, forKey: label)
            }

            do {
                try self.context.save()
            } catch {
                print(error.localizedDescription)
            }
        }
    }

    func readDiary() -> [Diary]? {
        ...
        return diaries
    }

    func updateDiary(diary: T) {
        let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Entity")

        let properties = Mirror(reflecting: diary).children.filter { $0.label != nil }

        guard let id = properties[0].value as? any CVarArg else { return }

        fetchRequest.predicate = NSPredicate(format: "id == %@", id)

        do {
            let objects = try context.fetch(fetchRequest)
            let objectToUpdate = objects[0]

            let properties = Mirror(reflecting: diary).children.filter { $0.label != nil && $0.label != "id" }

            for property in properties {
                guard let label = property.label else { return }
                objectToUpdate.setValue(property.value, forKey: label)
            }

            do {
                try self.context.save()
            } catch {
                print(error.localizedDescription)
            }
        } catch {
            print(error.localizedDescription)
        }
    }

    func deleteDiary(diary: T) throws {
        let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Entity")

        let properties = Mirror(reflecting: diary).children.filter { $0.label != nil }

        guard let id = properties[0].value as? any CVarArg else { return }

        fetchRequest.predicate = NSPredicate(format: "id == %@", id)

        do {
            let objects = try context.fetch(fetchRequest)
            let objectToDelete = objects[0]
            context.delete(objectToDelete)

            do {
                try context.save()
            } catch {
                throw error
            }
        } catch {
            throw error
        }
    }
}

이런식으로 read를 제외한 나머지 아이들을 T로 사용할 수 있게 만들었다.
위의 코드가 돌아가긴 한다.. 신기하게도...
mirror를 사용할 때 중요한 건 인스턴스를 반영한다는 것..
근데 이해 안 가는건..
value가 죄다 String인데, Diary의 각각의 프로퍼티들은 다양한 타입이란 말이지:

struct Diary {
    let id: UUID
    var title: String
    var body: String
    var date: Double
}

id는 제외하고 넣었다고 쳐도.. date는 어떻게 들어간거지...?
약간 공부를 하고도 잘 모르겠는 것 투성이다🥲😂