-
Notifications
You must be signed in to change notification settings - Fork 4
Persistable 추상화
구병조(GEN) edited this page Nov 19, 2024
·
4 revisions
데이터를 로컬에 저장할 수 있음에 대한 추상화로 Persistable
이라는 프로토콜을 생성하였습니다.
protocol Persistable {
/// CREATE
func add<Entity: NSManagedObject>(entityProvider: (NSManagedObjectContext) -> Entity) async throws -> Entity
/// READ
func fetch<Entity: NSManagedObject>(by request: NSFetchRequest<Entity>) throws -> [NSManagedObject]
/// UPDATE
func update<Entity: NSManagedObject>(entity: Entity, updateHandler: (Entity) -> Void) throws -> Entity
/// DELETE
func delete<Entity: NSManagedObject>(entity: Entity) throws
func delete<Entity: NSManagedObject>(by request: NSFetchRequest<Entity>) throws
}
다만 로컬에 데이터를 저장하는 방식이 Core Data만 있는 것이 아니기에, 좀 더 통합적인 추상으로 표현할 수 없을까 하는 고민에서 시작되었습니다.
문제는 NSManagedObject
생성에 있습니다.
NSManagedObject
를 생성하여 Core Data에 추가할 때는 반드시 NSManagedObjectContext
를 지정해야 합니다.
NSManagedObjectContext
는 Core Data의 데이터 관리 단위이며, 생성한 NSManagedObject
인스턴스가 Core Data 스택의 컨텍스트에 연결되어야 해당 객체가 영구 저장소와의 상호작용을 할 수 있습니다.
관건은 비즈니스 로직의 엔티티와 NSManagedObject
를 어떻게 매핑할 것인가 입니다.
힌트는 일괄 연산 API에서 얻었습니다.
코어 데이터의 일괄 추가 요청(NSBatchInsertRequest
)을 만들때, 매칭되는 엔티티의 이름과 각 속성에 넣을 값을 딕셔너리 형태로 제공해서 생성할 수 있습니다.
💡 각 속성을
key
, 값을value
로 하여 딕셔너리를 통해 변환하는 것을 각 레이어(비즈니스 로직 - 저장소)의 엔티티 매핑에서도 활용합니다.
다음과 같은 엔티티 요구사항을 만듭니다.
protocol EntityRepresentable: Sendable {
/// 현재의 데이터를 기반으로 변환한 딕셔너리 값.
var dictionaryValue: [String: Any] { get }
/// 해당 엔티티를 식별하기 위해 필요한 속성 딕셔너리 값.
var matchingAttributes: [String: Any] { get }
init(dictinary: [String: Any])
static var entityName: String { get }
}
딕셔너리를 통해 속성값을 다룰 수 있도록 합니다.
그리고 CRUD의 R(fetch) 연산을 할 때 필요한 NSFetchRequest
로 범용으로 사용할 수 있도록 추상화를 수행합니다.
protocol PersistFetchRequestable<Entity> {
associatedtype Entity: EntityRepresentable
var predicate: NSPredicate? { get }
var sortDescriptors: [NSSortDescriptor] { get }
/// 검색을 통해 최대로 가져올 수 있는 수.
var fetchLimit: Int { get }
/// 검색 후 데이터를 가져오는 시작점.
var fetchOffset: Int { get }
}
protocol Persistable {
/// 로컬 저장소에 엔티티 데이터를 추가합니다.
/// - Parameter entities: 추가할 엔티티 배열.
/// - Returns: 추가된 데이터.
func add<Entity>(contentsOf entities: [Entity]) async throws -> [Entity] where Entity: EntityRepresentable
/// 로컬 저장소에서 요청 조건에 맞는 엔티티 데이터를 불러옵니다.
/// - Parameter request: 요청 조건.
/// - Returns: 엔티티 데이터.
func fetch<Entity>(
by request: any PersistFetchRequestable<Entity>
) async throws -> [Entity] where Entity: EntityRepresentable
/// 로컬 저장소의 엔티티 데이터에 대한 최신화를 수행합니다.
/// - Parameters:
/// - entity: 최신화할 엔티티 값.
/// - predicate: 최신화 할 엔티티 데이터를 찾기 위한 조건문.
/// - Returns: 최신화 된 엔티티 데이터.
func update<Entity>(
to entity: Entity,
forMatching predicate: NSPredicate
) async throws -> Entity where Entity: EntityRepresentable
/// 로컬 저장소에서 엔티티 데이터를 제거합니다.
/// - Parameter entities: 제거할 엔티티 데이터.
func delete<Entity>(contentsOf entities: [Entity]) async throws where Entity: EntityRepresentable
}