Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CAT-92] DatabaseClient 모듈 추가 #14

Merged
merged 16 commits into from
Aug 4, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,36 +14,72 @@ import DependenciesMacros
@DependencyClient
public struct DatabaseClient {
public var initialize: @Sendable (Realm.Configuration) async throws -> Void
/// Use create<T: Persistable>(object:).
public var create: @Sendable (any Persistable) async throws -> Void
public var read: @Sendable (Object.Type, (((Query<Results<Object>.Element>) -> Query<Bool>)?)) async throws -> [Object]
/// Use read<T: Persistable>(_ type:).
public var read: @Sendable (Object.Type) async throws -> [Object]
/// Use read<T: Persistable>(_ type:, predicateFormat:, args:).
public var readWithFilter: @Sendable (Object.Type, String, Any) async throws -> [Object]
/// Use update<T: Persistable>(_ object:)
public var update: @Sendable (any Persistable) async throws -> Void
/// Use update<T: Object>(_ type:, value:).
public var updateWithValue: @Sendable (Object.Type, Any) async throws -> Void
/// Use delete<T: Object>(_ object:).
public var delete: @Sendable (Object) async throws -> Void
/// Use delete<T: Object>(_ object:).
public var deleteTable: @Sendable (Object.Type) async throws -> Void
/// Use delete<T: Object>(_ type:).
public var deleteAllTable: @Sendable () async throws -> Void

public func create<T: Persistable>(object: T) async throws -> Void {
try await create(object)
}

public func read<T: Persistable>(_ type: T.Type, where isIncluded: ((Query<Results<T.ManagedObject>.Element>) -> Query<Bool>)?) async throws -> [T] {
let result = try await self.read(type.ManagedObject, (isIncluded as! (Query<Results<Object>.Element>) -> Query<Bool>))
return result.compactMap { T(managedObject: $0 as! T.ManagedObject) }
public func read<T: Persistable>(_ type: T.Type) async throws -> [T] {
let results = try await self.read(type.ManagedObject)
return await Self.realmActor?.convertToPersistable(type: type, objects: results) ?? []
}

public func update<T: Persistable>(object: T) async throws {

public func read<T: Persistable>(_ type: T.Type, predicateFormat: String, args: Any) async throws -> [T] {
let results = try await self.readWithFilter(type.ManagedObject, predicateFormat, args)
return await Self.realmActor?.convertToPersistable(type: type, objects: results) ?? []
}

public func update<T: Persistable>(_ object: T) async throws {
try await self.update(object)
}

public func update<T: Object>(object: T.Type, value: Any) async throws {
try await self.updateWithValue(object, value)
public func update<T: Object>(_ type: T.Type, value: Any) async throws {
try await self.updateWithValue(type, value)
}

public func delete<T: Object>(_ object: T) async throws {
try await self.delete(object)
}
}

public protocol Persistable where Self: Decodable {
associatedtype ManagedObject: Object

init(managedObject: ManagedObject)
func managedObject() -> ManagedObject
public func delete<T: Object>(_ type: T.Type) async throws {
try await self.deleteTable(type)
}


// MARK: - Actor for realm

public static var realmActor: RealmActor?

public actor RealmActor {
public var realm: Realm!

public init(configuration: Realm.Configuration) async throws {
realm = try await Realm(configuration: configuration, actor: self)
}

func convertToPersistable<T: Persistable>(type: T.Type, objects: [Object]) -> [T] {
return objects.compactMap { type.init(managedObject: $0 as! T.ManagedObject) }
}
}
}


extension DatabaseClient: TestDependencyKey {
public static let previewValue = Self()
public static let testValue = Self()
Expand Down
19 changes: 19 additions & 0 deletions Projects/Core/DatabaseClient/Interface/Persistable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// Persistable.swift
// DatabaseClientInterface
//
// Created by devMinseok on 8/3/24.
// Copyright © 2024 PomoNyang. All rights reserved.
//

import Foundation

import RealmSwift

public protocol Persistable where Self: Decodable {
associatedtype ManagedObject: Object

var id: UUID { get set }
init(managedObject: ManagedObject)
func managedObject() -> ManagedObject
}
135 changes: 78 additions & 57 deletions Projects/Core/DatabaseClient/Sources/DatabaseClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,39 +15,39 @@ import Dependencies
extension DatabaseClient: DependencyKey {
public static let liveValue: DatabaseClient = .live()

private static func live() -> DatabaseClient {
public static func live() -> DatabaseClient {
devMinseok marked this conversation as resolved.
Show resolved Hide resolved
return .init(
initialize: { configuration in
if realmActor == nil {
realmActor = try await RealmActor(configuration: configuration)
} else {
throw(NSError(domain: "Realm already initialized", code: 0))
}
},
create: { object in
if let realmActor {
let threadSafeReference = ThreadSafeReference(to: object.managedObject())
try await realmActor.create(threadSafeReference)
try await realmActor.create(object.managedObject())
} else {
throw(NSError(domain: "Realm is not initialized", code: 0))
}
},
read: { type, isIncluded in
read: { type in
if let realmActor {
let results = await realmActor.read(type)
if let isIncluded {
return results.where(isIncluded).map { $0 }
} else {
return results.map { $0 }
}
return results
} else {
throw(NSError(domain: "Realm is not initialized", code: 0))
}
},
readWithFilter: { type, predicateFormat, args in
if let realmActor {
let results = await realmActor.read(type, predicateFormat: predicateFormat, args: args)
return results
} else {
throw(NSError(domain: "Realm is not initialized", code: 0))
}
},
update: { object in
if let realmActor {
let threadSafeReference = ThreadSafeReference(to: object.managedObject())
try await realmActor.update(threadSafeReference)
try await realmActor.update(object.managedObject())
} else {
throw(NSError(domain: "Realm is not initialized", code: 0))
}
Expand All @@ -58,60 +58,81 @@ extension DatabaseClient: DependencyKey {
} else {
throw(NSError(domain: "Realm is not initialized", code: 0))
}
},
delete: { object in
if let realmActor {
try await realmActor.delete(object)
} else {
throw(NSError(domain: "Realm is not initialized", code: 0))
}
},
deleteTable: { objectType in
if let realmActor {
try await realmActor.delete(objectType)
} else {
throw(NSError(domain: "Realm is not initialized", code: 0))
}
},
deleteAllTable: {
if let realmActor {
try await realmActor.deleteAll()
} else {
throw(NSError(domain: "Realm is not initialized", code: 0))
}
}
)
}
}

extension DatabaseClient.RealmActor {
public func create<T: Object>(_ object: T) async throws {
try await realm.asyncWrite {
realm.add(object, update: .error)
}
}

public func read<T: Object>(_ object: T.Type) -> [T] {
let results = realm.objects(object)
return results.map { $0 }
}

// MARK: - Realm Actor

static var realmActor: RealmActor?
public func read<T: Object>(_ object: T.Type, predicateFormat: String, args: Any...) -> [T] {
let results = realm.objects(object)
return results.filter(predicateFormat, args).map { $0 }
}

actor RealmActor {
var realm: Realm!

init(configuration: Realm.Configuration) async throws {
realm = try await Realm(configuration: configuration, actor: self)
public func update<T: Object>(_ object: T) async throws {
try await realm.asyncWrite {
realm.add(object, update: .modified)
}

func create<T: Object>(_ object: ThreadSafeReference<T>) async throws {
try await realm.asyncWrite {
if let resolved = realm.resolve(object) {
realm.add(resolved, update: .error)
} else {
throw(NSError(domain: "ThreadSafeReference resolve failed", code: 0))
}
}
}

func read<T: Object>(_ object: T.Type) -> Results<T> {
return realm.objects(T.self)
}

public func update<T: Object>(_ object: T.Type, value: Any) async throws {
try await realm.asyncWrite {
realm.create(
object,
value: value,
update: .modified
)
}

func update<T: Object>(_ object: ThreadSafeReference<T>) async throws {
try await realm.asyncWrite {
if let resolved = realm.resolve(object) {
realm.add(resolved, update: .modified)
} else {
throw(NSError(domain: "ThreadSafeReference resolve failed", code: 0))
}
}
}

public func delete<T: Object>(_ object: T) async throws {
try await realm.asyncWrite {
realm.delete(object)
}

func update<T: Object>(_ object: T.Type, value: Any) async throws {
try await realm.asyncWrite {
realm.create(
object,
value: value,
update: .modified
)
}
}

public func delete<T: Object>(_ object: T.Type) async throws {
let objects = realm.objects(object)
try await realm.asyncWrite {
realm.delete(objects)
}
func delete<T: Object>(_ object: T) async throws {
try await realm.asyncWrite {
realm.delete(object)
}
}

public func deleteAll() async throws {
try await realm.asyncWrite {
realm.deleteAll()
}
}
}