This commit is contained in:
John Estropia
2026-07-24 11:51:36 +09:00
parent 1ea9ad7c4a
commit 13093d72d4
51 changed files with 1083 additions and 243 deletions
@@ -34,7 +34,7 @@ extension Modern.PokedexDemo {
// MARK: ImportableObject
typealias ImportSource = Dictionary<String, Any>
typealias ImportSource = Dictionary<String, any Sendable>
// MARK: ImportableUniqueObject
@@ -11,6 +11,7 @@ extension Modern.PokedexDemo {
// MARK: - Modern.PokedexDemo.MainView
@MainActor
struct MainView<ListView: View>: View {
// MARK: Internal
@@ -36,7 +36,7 @@ extension Modern.PokedexDemo {
// MARK: ImportableObject
typealias ImportSource = (index: Int, json: Dictionary<String, Any>)
typealias ImportSource = (index: Int, json: Dictionary<String, any Sendable>)
func didInsert(from source: ImportSource, in transaction: BaseDataTransaction) throws {
@@ -21,7 +21,7 @@ extension Modern.PokedexDemo {
/**
Sample 1: Importing a list of JSON data into `ImportableUniqueObject`s whose `ImportSource` are tuples
*/
private static func importPokedexEntries(from data: Data) async throws {
private static func importPokedexEntries(from data: Data) async throws(Modern.PokedexDemo.Service.Error) {
do {
@@ -43,7 +43,7 @@ extension Modern.PokedexDemo {
}
catch {
throw self.mapError(error)
throw self.mapError(.init(error))
}
}
@@ -51,31 +51,31 @@ extension Modern.PokedexDemo {
Sample 2: Importing a single JSON data into an `ImportableUniqueObject` whose `ImportSource` is a JSON `Dictionary`
*/
private static func importSpecies(
for detailsObjectID: NSManagedObjectID,
for detailsPersistentID: DynamicObjectID<Modern.PokedexDemo.Details>,
from data: Data
) async throws -> ObjectSnapshot<Modern.PokedexDemo.Species> {
let speciesObjectID = try await Modern.PokedexDemo.dataStack.async.perform { transaction -> NSManagedObjectID in
let speciesPersistentID = try await Modern.PokedexDemo.dataStack.async.perform { transaction in
let json: Dictionary<String, Any> = try self.parseJSON(
try JSONSerialization.jsonObject(with: data, options: [])
)
guard
let species = try transaction.importUniqueObject(
Into<Modern.PokedexDemo.Species>(),
source: json
let json: Dictionary<String, Any> = try self.parseJSON(
try JSONSerialization.jsonObject(with: data, options: [])
)
else {
guard
let species = try transaction.importUniqueObject(
Into<Modern.PokedexDemo.Species>(),
source: .init(json: json)
)
else {
throw Modern.PokedexDemo.Service.Error.unexpected
}
transaction
.edit(Into<Modern.PokedexDemo.Details>(), detailsObjectID)?
.edit(Into<Modern.PokedexDemo.Details>(), detailsPersistentID)?
.species = species
return species.objectID()
return species.persistentID()
}
guard
let species: Modern.PokedexDemo.Species = Modern.PokedexDemo.dataStack.fetchExisting(speciesObjectID),
let species: Modern.PokedexDemo.Species = Modern.PokedexDemo.dataStack.fetchExisting(speciesPersistentID),
let snapshot = species.asSnapshot()
else {
@@ -88,7 +88,7 @@ extension Modern.PokedexDemo {
Sample 3: Importing a list of JSON data into `ImportableUniqueObject`s whose `ImportSource` are JSON `Dictionary`s
*/
private static func importForms(
for detailsObjectID: NSManagedObjectID,
for detailsPersistentID: DynamicObjectID<Modern.PokedexDemo.Details>,
from dataArray: [Data]
) async throws {
@@ -110,13 +110,13 @@ extension Modern.PokedexDemo {
throw Modern.PokedexDemo.Service.Error.unexpected
}
transaction
.edit(Into<Modern.PokedexDemo.Details>(), detailsObjectID)?
.edit(Into<Modern.PokedexDemo.Details>(), detailsPersistentID)?
.forms = forms
}
}
catch {
throw self.mapError(error)
throw self.mapError(.init(error))
}
}
@@ -195,8 +195,8 @@ extension Modern.PokedexDemo {
if let species = details.$species?.snapshot {
self.fetchFormsIfNeeded(
key: species.$id,
detailsObjectID: details.objectID(),
key: String(species.$id),
detailsPersistentID: details.persistentID(),
species: species
)
return
@@ -207,7 +207,7 @@ extension Modern.PokedexDemo {
return
}
let speciesURL = pokedexEntry.$speciesURL
let detailsObjectID = details.objectID()
let detailsPersistentID = details.persistentID()
self.detailTasks[key] = Task { [weak self] in
guard let self else {
@@ -220,7 +220,7 @@ extension Modern.PokedexDemo {
}
await self.fetchSpecies(
key: key,
detailsObjectID: detailsObjectID,
detailsPersistentID: detailsPersistentID,
speciesURL: speciesURL
)
}
@@ -289,7 +289,7 @@ extension Modern.PokedexDemo {
private func fetchSpecies(
key: String,
detailsObjectID: NSManagedObjectID,
detailsPersistentID: DynamicObjectID<Modern.PokedexDemo.Details>,
speciesURL: URL
) async {
@@ -299,7 +299,7 @@ extension Modern.PokedexDemo {
try Task.checkCancellation()
let species = try await Self.importSpecies(
for: detailsObjectID,
for: detailsPersistentID,
from: data
)
guard species.$details?.snapshot?.$forms.isEmpty == true else {
@@ -307,7 +307,7 @@ extension Modern.PokedexDemo {
return
}
await self.fetchForms(
detailsObjectID: detailsObjectID,
detailsPersistentID: detailsPersistentID,
formsURLs: species.$formsURLs
)
}
@@ -331,7 +331,7 @@ extension Modern.PokedexDemo {
private func fetchFormsIfNeeded(
key: String,
detailsObjectID: NSManagedObjectID,
detailsPersistentID: DynamicObjectID<Modern.PokedexDemo.Details>,
species: ObjectSnapshot<Modern.PokedexDemo.Species>
) {
@@ -356,14 +356,14 @@ extension Modern.PokedexDemo {
self.detailTasks.removeValue(forKey: key)
}
await self.fetchForms(
detailsObjectID: detailsObjectID,
detailsPersistentID: detailsPersistentID,
formsURLs: formsURLs
)
}
}
private func fetchForms(
detailsObjectID: NSManagedObjectID,
detailsPersistentID: DynamicObjectID<Modern.PokedexDemo.Details>,
formsURLs: [URL]
) async {
@@ -378,7 +378,7 @@ extension Modern.PokedexDemo {
dataArray.append(data)
}
try await Self.importForms(
for: detailsObjectID,
for: detailsPersistentID,
from: dataArray
)
}
@@ -408,7 +408,7 @@ extension Modern.PokedexDemo {
case networkError(URLError)
case parseError(expected: Any.Type, actual: Any.Type, file: String)
case saveError(CoreStoreError)
case otherError(Swift.Error)
case otherError(Swift::Error)
case unexpected
}
}
@@ -65,7 +65,10 @@ extension Modern.PokedexDemo {
// MARK: ImportableObject
typealias ImportSource = Dictionary<String, Any>
struct ImportSource: @unchecked Sendable {
let json: Dictionary<String, Any>
}
// MARK: ImportableUniqueObject
@@ -82,14 +85,14 @@ extension Modern.PokedexDemo {
static func uniqueID(from source: ImportSource, in transaction: BaseDataTransaction) throws -> UniqueIDType? {
let json = source
let json = source.json
return try Modern.PokedexDemo.Service.parseJSON(json["id"])
}
func update(from source: ImportSource, in transaction: BaseDataTransaction) throws {
typealias Service = Modern.PokedexDemo.Service
let json = source
let json = source.json
self.name = try Service.parseJSON(json["name"])
self.weight = try Service.parseJSON(json["weight"])
@@ -50,7 +50,7 @@ extension Modern.PokedexDemo.UIKit {
fatalError()
}
deinit {
isolated deinit {
self.listPublisher.removeObserver(self)
}
@@ -94,13 +94,25 @@ extension Modern.PokedexDemo.UIKit {
private func startObservingList() {
self.listPublisher.addObserver(self) { (listPublisher) in
self.dataSource.apply(
listPublisher.snapshot,
animatingDifferences: true
)
}
self.listPublisher.addObserver(
self,
notifyInitial: false,
{ [weak self] (listPublisher) in
let snapshot = listPublisher.snapshot
withMainActorImmediate {
guard let self else {
return
}
self.dataSource.apply(
snapshot,
animatingDifferences: true
)
}
}
)
self.dataSource.apply(
self.listPublisher.snapshot,
animatingDifferences: false