mirror of
https://github.com/JohnEstropia/CoreStore.git
synced 2026-09-10 11:51:49 +02:00
WIP
This commit is contained in:
@@ -20,6 +20,7 @@ protocol Advanced_EvolutionDemo_CreatureType: DynamicObject, CustomStringConvert
|
||||
|
||||
var dnaCode: Int64 { get set }
|
||||
|
||||
@MainActor
|
||||
static func dataSource(in dataStack: DataStack) -> Advanced.EvolutionDemo.CreaturesDataSource
|
||||
|
||||
static func count(in transaction: BaseDataTransaction) throws -> Int
|
||||
|
||||
+48
-87
@@ -3,27 +3,29 @@
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import CoreStore
|
||||
import Combine
|
||||
import Observation
|
||||
|
||||
|
||||
// MARK: - Advanced.EvolutionDemo
|
||||
|
||||
extension Advanced.EvolutionDemo {
|
||||
|
||||
|
||||
// MARK: - Advanced.EvolutionDemo.CreaturesDataSource
|
||||
|
||||
|
||||
/**
|
||||
A type-erasing adapter to support different `ListPublisher` types
|
||||
*/
|
||||
final class CreaturesDataSource: ObservableObject {
|
||||
|
||||
@MainActor
|
||||
@Observable
|
||||
final class CreaturesDataSource {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
init<T: NSManagedObject & Advanced.EvolutionDemo.CreatureType>(
|
||||
|
||||
init<T: DynamicObject & Advanced.EvolutionDemo.CreatureType>(
|
||||
listPublisher: ListPublisher<T>,
|
||||
dataStack: DataStack
|
||||
) {
|
||||
|
||||
|
||||
self.numberOfItems = {
|
||||
listPublisher.snapshot.numberOfItems
|
||||
}
|
||||
@@ -31,13 +33,13 @@ extension Advanced.EvolutionDemo {
|
||||
listPublisher.snapshot[index].object?.description
|
||||
}
|
||||
self.addItems = { count in
|
||||
|
||||
|
||||
dataStack.perform(
|
||||
asynchronous: { transaction in
|
||||
|
||||
|
||||
let nextDNACode = try transaction.fetchCount(From<T>())
|
||||
for offset in 0 ..< count {
|
||||
|
||||
|
||||
let object = transaction.create(Into<T>())
|
||||
object.dnaCode = .init(nextDNACode + offset)
|
||||
object.mutate(in: transaction)
|
||||
@@ -47,11 +49,11 @@ extension Advanced.EvolutionDemo {
|
||||
)
|
||||
}
|
||||
self.mutateItemAtIndex = { index in
|
||||
|
||||
|
||||
let object = listPublisher.snapshot[index]
|
||||
dataStack.perform(
|
||||
asynchronous: { transaction in
|
||||
|
||||
|
||||
object
|
||||
.asEditable(in: transaction)?
|
||||
.mutate(in: transaction)
|
||||
@@ -60,109 +62,68 @@ extension Advanced.EvolutionDemo {
|
||||
)
|
||||
}
|
||||
self.deleteAllItems = {
|
||||
|
||||
|
||||
dataStack.perform(
|
||||
asynchronous: { transaction in
|
||||
|
||||
|
||||
try transaction.deleteAll(From<T>())
|
||||
},
|
||||
completion: { _ in }
|
||||
)
|
||||
}
|
||||
listPublisher.addObserver(self) { [weak self] (listPublisher) in
|
||||
|
||||
self?.objectWillChange.send()
|
||||
listPublisher.addObserver(self) { [weak self] _ in
|
||||
|
||||
Task { @MainActor in
|
||||
self?.refreshID += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init<T: CoreStoreObject & Advanced.EvolutionDemo.CreatureType>(
|
||||
listPublisher: ListPublisher<T>,
|
||||
dataStack: DataStack
|
||||
) {
|
||||
|
||||
self.numberOfItems = {
|
||||
listPublisher.snapshot.numberOfItems
|
||||
}
|
||||
self.itemDescriptionAtIndex = { index in
|
||||
listPublisher.snapshot[index].object?.description
|
||||
}
|
||||
self.addItems = { count in
|
||||
|
||||
dataStack.perform(
|
||||
asynchronous: { transaction in
|
||||
|
||||
let nextDNACode = try transaction.fetchCount(From<T>())
|
||||
for offset in 0 ..< count {
|
||||
|
||||
let object = transaction.create(Into<T>())
|
||||
object.dnaCode = .init(nextDNACode + offset)
|
||||
object.mutate(in: transaction)
|
||||
}
|
||||
},
|
||||
completion: { _ in }
|
||||
)
|
||||
}
|
||||
self.mutateItemAtIndex = { index in
|
||||
|
||||
let object = listPublisher.snapshot[index]
|
||||
dataStack.perform(
|
||||
asynchronous: { transaction in
|
||||
|
||||
object
|
||||
.asEditable(in: transaction)?
|
||||
.mutate(in: transaction)
|
||||
},
|
||||
completion: { _ in }
|
||||
)
|
||||
}
|
||||
self.deleteAllItems = {
|
||||
|
||||
dataStack.perform(
|
||||
asynchronous: { transaction in
|
||||
|
||||
try transaction.deleteAll(From<T>())
|
||||
},
|
||||
completion: { _ in }
|
||||
)
|
||||
}
|
||||
listPublisher.addObserver(self) { [weak self] (listPublisher) in
|
||||
|
||||
self?.objectWillChange.send()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func numberOfCreatures() -> Int {
|
||||
|
||||
|
||||
_ = self.refreshID
|
||||
return self.numberOfItems()
|
||||
}
|
||||
|
||||
|
||||
func creatureDescription(at index: Int) -> String? {
|
||||
|
||||
|
||||
_ = self.refreshID
|
||||
return self.itemDescriptionAtIndex(index)
|
||||
}
|
||||
|
||||
|
||||
func mutate(at index: Int) {
|
||||
|
||||
|
||||
self.mutateItemAtIndex(index)
|
||||
}
|
||||
|
||||
|
||||
func add(count: Int) {
|
||||
|
||||
|
||||
self.addItems(count)
|
||||
}
|
||||
|
||||
|
||||
func clear() {
|
||||
|
||||
|
||||
self.deleteAllItems()
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
|
||||
private var refreshID: Int = 0
|
||||
|
||||
@ObservationIgnored
|
||||
private let numberOfItems: () -> Int
|
||||
|
||||
@ObservationIgnored
|
||||
private let itemDescriptionAtIndex: (Int) -> String?
|
||||
|
||||
@ObservationIgnored
|
||||
private let mutateItemAtIndex: (Int) -> Void
|
||||
|
||||
@ObservationIgnored
|
||||
private let addItems: (Int) -> Void
|
||||
|
||||
@ObservationIgnored
|
||||
private let deleteAllItems: () -> Void
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,69 +7,50 @@ import SwiftUI
|
||||
// MARK: - Advanced.EvolutionDemo
|
||||
|
||||
extension Advanced.EvolutionDemo {
|
||||
|
||||
|
||||
// MARK: - Advanced.EvolutionDemo.ItemView
|
||||
|
||||
|
||||
struct ItemView: View {
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
|
||||
init(description: String?, mutate: @escaping () -> Void) {
|
||||
|
||||
self.description = description
|
||||
self.mutate = mutate
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// MARK: View
|
||||
|
||||
|
||||
var body: some View {
|
||||
|
||||
HStack {
|
||||
|
||||
Text(self.description ?? "")
|
||||
.font(.footnote)
|
||||
.foregroundColor(.primary)
|
||||
|
||||
Spacer()
|
||||
|
||||
Button(
|
||||
action: self.mutate,
|
||||
label: {
|
||||
|
||||
Text("Mutate")
|
||||
.foregroundColor(.accentColor)
|
||||
.fontWeight(.bold)
|
||||
}
|
||||
)
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
.disabled(self.description == nil)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// MARK: FilePrivate
|
||||
|
||||
|
||||
fileprivate let description: String?
|
||||
fileprivate let mutate: () -> Void
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
|
||||
struct _Demo_Advanced_EvolutionDemo_ItemView_Preview: PreviewProvider {
|
||||
|
||||
// MARK: PreviewProvider
|
||||
|
||||
static var previews: some View {
|
||||
Advanced.EvolutionDemo.ItemView(
|
||||
description: """
|
||||
dnaCode: 123
|
||||
numberOfLimbs: 4
|
||||
hasVertebrae: true
|
||||
hasHead: true
|
||||
hasTail: true
|
||||
habitat: land
|
||||
hasWings: false
|
||||
""",
|
||||
mutate: {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -8,92 +8,51 @@ import SwiftUI
|
||||
// MARK: - Advanced.EvolutionDemo
|
||||
|
||||
extension Advanced.EvolutionDemo {
|
||||
|
||||
|
||||
// MARK: - Advanced.EvolutionDemo.ListView
|
||||
|
||||
|
||||
struct ListView: View {
|
||||
|
||||
// MARK: View
|
||||
|
||||
var body: some View {
|
||||
let dataSource = self.dataSource
|
||||
return List {
|
||||
ForEach(0 ..< dataSource.numberOfCreatures(), id: \.self) { (index) in
|
||||
Advanced.EvolutionDemo.ItemView(
|
||||
description: dataSource.creatureDescription(at: index),
|
||||
mutate: {
|
||||
|
||||
dataSource.mutate(at: index)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
.listStyle(PlainListStyle())
|
||||
}
|
||||
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
|
||||
init(
|
||||
period: Advanced.EvolutionDemo.GeologicalPeriod,
|
||||
dataStack: DataStack,
|
||||
dataSource: Advanced.EvolutionDemo.CreaturesDataSource
|
||||
) {
|
||||
|
||||
|
||||
self.period = period
|
||||
self.dataStack = dataStack
|
||||
self.dataSource = dataSource
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let period: Advanced.EvolutionDemo.GeologicalPeriod
|
||||
|
||||
private let dataStack: DataStack
|
||||
|
||||
@ObservedObject
|
||||
private var dataSource: Advanced.EvolutionDemo.CreaturesDataSource
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#if DEBUG
|
||||
|
||||
struct _Demo_Advanced_EvolutionDemo_ListView_Preview: PreviewProvider {
|
||||
|
||||
// MARK: PreviewProvider
|
||||
|
||||
static var previews: some View {
|
||||
|
||||
let dataStack = DataStack(
|
||||
CoreStoreSchema(
|
||||
modelVersion: Advanced.EvolutionDemo.V4.name,
|
||||
entities: [
|
||||
Entity<Advanced.EvolutionDemo.V4.Creature>("Creature")
|
||||
]
|
||||
)
|
||||
)
|
||||
try! dataStack.addStorageAndWait(
|
||||
SQLiteStore(fileName: "Advanced.EvolutionDemo.ListView.Preview.sqlite")
|
||||
)
|
||||
try! dataStack.perform(
|
||||
synchronous: { transaction in
|
||||
|
||||
for dnaCode in 0 ..< 10 as Range<Int64> {
|
||||
|
||||
let object = transaction.create(Into<Advanced.EvolutionDemo.V4.Creature>())
|
||||
object.dnaCode = dnaCode
|
||||
object.mutate(in: transaction)
|
||||
|
||||
|
||||
// MARK: View
|
||||
|
||||
var body: some View {
|
||||
|
||||
List {
|
||||
ForEach(0 ..< self.dataSource.numberOfCreatures(), id: \.self) { index in
|
||||
|
||||
Advanced.EvolutionDemo.ItemView(
|
||||
description: self.dataSource.creatureDescription(at: index),
|
||||
mutate: {
|
||||
|
||||
self.dataSource.mutate(at: index)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
return Advanced.EvolutionDemo.ListView(
|
||||
period: .ageOfMammals,
|
||||
dataStack: dataStack,
|
||||
dataSource: Advanced.EvolutionDemo.V4.Creature.dataSource(in: dataStack)
|
||||
)
|
||||
.listStyle(.plain)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let period: Advanced.EvolutionDemo.GeologicalPeriod
|
||||
|
||||
private let dataStack: DataStack
|
||||
|
||||
private let dataSource: Advanced.EvolutionDemo.CreaturesDataSource
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -8,71 +8,54 @@ import SwiftUI
|
||||
// MARK: - Advanced.EvolutionDemo
|
||||
|
||||
extension Advanced.EvolutionDemo {
|
||||
|
||||
|
||||
// MARK: - Advanced.EvolutionDemo.MainView
|
||||
|
||||
|
||||
struct MainView: View {
|
||||
|
||||
|
||||
@State
|
||||
private var migrator = Advanced.EvolutionDemo.Migrator()
|
||||
|
||||
|
||||
// MARK: View
|
||||
|
||||
|
||||
var body: some View {
|
||||
let migrator = self.migrator
|
||||
let listView: AnyView
|
||||
if let current = migrator.current {
|
||||
|
||||
listView = AnyView(
|
||||
|
||||
VStack(spacing: 0) {
|
||||
|
||||
HStack(alignment: .center, spacing: 0) {
|
||||
|
||||
Text("Age of")
|
||||
.padding(.trailing)
|
||||
|
||||
Picker(selection: $migrator.currentPeriod, label: EmptyView()) {
|
||||
|
||||
ForEach(Advanced.EvolutionDemo.GeologicalPeriod.allCases, id: \.self) { period in
|
||||
|
||||
Text(period.description).tag(period)
|
||||
}
|
||||
}
|
||||
.pickerStyle(.segmented)
|
||||
}
|
||||
.padding()
|
||||
|
||||
if let current = migrator.current {
|
||||
|
||||
Advanced.EvolutionDemo.ListView(
|
||||
period: current.period,
|
||||
dataStack: current.dataStack,
|
||||
dataSource: current.dataSource
|
||||
)
|
||||
)
|
||||
}
|
||||
else {
|
||||
|
||||
listView = AnyView(
|
||||
Advanced.EvolutionDemo.ProgressView(progress: migrator.progress)
|
||||
)
|
||||
}
|
||||
|
||||
return VStack(spacing: 0) {
|
||||
HStack(alignment: .center, spacing: 0) {
|
||||
Text("Age of")
|
||||
.padding(.trailing)
|
||||
Picker(selection: self.$migrator.currentPeriod, label: EmptyView()) {
|
||||
ForEach(Advanced.EvolutionDemo.GeologicalPeriod.allCases, id: \.self) { period in
|
||||
Text(period.description).tag(period)
|
||||
}
|
||||
}
|
||||
.pickerStyle(SegmentedPickerStyle())
|
||||
}
|
||||
.padding()
|
||||
listView
|
||||
.edgesIgnoringSafeArea(.vertical)
|
||||
.ignoresSafeArea(.container, edges: .vertical)
|
||||
}
|
||||
.navigationBarTitle("Evolution")
|
||||
.disabled(migrator.isBusy || migrator.current == nil)
|
||||
else {
|
||||
|
||||
Advanced.EvolutionDemo.ProgressView(progress: migrator.progress)
|
||||
.ignoresSafeArea(.container, edges: .vertical)
|
||||
}
|
||||
}
|
||||
.navigationTitle("Evolution")
|
||||
.disabled(migrator.isBusy || migrator.current == nil)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
@ObservedObject
|
||||
private var migrator: Advanced.EvolutionDemo.Migrator = .init()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#if DEBUG
|
||||
|
||||
struct _Demo_Advanced_EvolutionDemo_MainView_Preview: PreviewProvider {
|
||||
|
||||
// MARK: PreviewProvider
|
||||
|
||||
static var previews: some View {
|
||||
|
||||
Advanced.EvolutionDemo.MainView()
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
import CoreStore
|
||||
import Foundation
|
||||
import Combine
|
||||
import Observation
|
||||
|
||||
|
||||
// MARK: - Advanced.EvolutionDemo
|
||||
@@ -13,8 +13,10 @@ extension Advanced.EvolutionDemo {
|
||||
|
||||
// MARK: - Advanced.EvolutionDemo.Migrator
|
||||
|
||||
final class Migrator: ObservableObject {
|
||||
|
||||
@MainActor
|
||||
@Observable
|
||||
final class Migrator {
|
||||
|
||||
/**
|
||||
⭐️ Sample 1: Creating a complex `DataStack` that contains all schema histories. The `exactCurrentModelVersion` will specify the target version (if required), and `migrationChain` will provide the upgrade/downgrade progressive migration path.
|
||||
*/
|
||||
@@ -22,7 +24,7 @@ extension Advanced.EvolutionDemo {
|
||||
exactCurrentModelVersion: ModelVersion?,
|
||||
migrationChain: MigrationChain
|
||||
) -> DataStack {
|
||||
|
||||
|
||||
let xcodeV1ToV2ModelSchema = XcodeDataModelSchema.from(
|
||||
modelName: "Advanced.EvolutionDemo.V1",
|
||||
bundle: Bundle(for: Advanced.EvolutionDemo.V1.Creature.self)
|
||||
@@ -49,12 +51,12 @@ extension Advanced.EvolutionDemo {
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
⭐️ Sample 2: Creating a complex `SQLiteStore` that contains all schema mappings for both upgrade and downgrade cases.
|
||||
*/
|
||||
private func accessSQLiteStore() -> SQLiteStore {
|
||||
|
||||
|
||||
let upgradeMappings: [SchemaMappingProvider] = [
|
||||
Advanced.EvolutionDemo.V2.FromV1.mapping,
|
||||
Advanced.EvolutionDemo.V3.FromV2.mapping,
|
||||
@@ -72,15 +74,15 @@ extension Advanced.EvolutionDemo {
|
||||
localStorageOptions: []
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
⭐️ Sample 3: Find the model version used by an existing `SQLiteStore`, or just return the latest version if the store is not created yet.
|
||||
*/
|
||||
private func findCurrentVersion() -> ModelVersion {
|
||||
|
||||
|
||||
let allVersions = Advanced.EvolutionDemo.GeologicalPeriod.allCases
|
||||
.map({ $0.version })
|
||||
|
||||
.map(\.version)
|
||||
|
||||
// Since we are only interested in finding current version, we'll assume an upgrading `MigrationChain`
|
||||
let dataStack = self.createDataStack(
|
||||
exactCurrentModelVersion: nil,
|
||||
@@ -89,42 +91,36 @@ extension Advanced.EvolutionDemo {
|
||||
let migrations = try! dataStack.requiredMigrationsForStorage(
|
||||
self.accessSQLiteStore()
|
||||
)
|
||||
|
||||
|
||||
// If no migrations are needed, it means either the store is not created yet, or the store is already at the latest model version. In either case, we already know that the store will use the latest version
|
||||
return migrations.first?.sourceVersion
|
||||
?? allVersions.last!
|
||||
?? allVersions.last!
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
|
||||
var currentPeriod: Advanced.EvolutionDemo.GeologicalPeriod = Advanced.EvolutionDemo.GeologicalPeriod.allCases.last! {
|
||||
|
||||
|
||||
didSet {
|
||||
|
||||
|
||||
self.selectModelVersion(self.currentPeriod)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private(set) var current: (
|
||||
period: Advanced.EvolutionDemo.GeologicalPeriod,
|
||||
dataStack: DataStack,
|
||||
dataSource: Advanced.EvolutionDemo.CreaturesDataSource
|
||||
)? {
|
||||
|
||||
willSet {
|
||||
|
||||
self.objectWillChange.send()
|
||||
}
|
||||
}
|
||||
|
||||
)?
|
||||
|
||||
private(set) var isBusy: Bool = false
|
||||
|
||||
|
||||
private(set) var progress: Progress?
|
||||
|
||||
|
||||
|
||||
|
||||
init() {
|
||||
|
||||
|
||||
self.synchronizeCurrentVersion()
|
||||
}
|
||||
|
||||
@@ -132,11 +128,11 @@ extension Advanced.EvolutionDemo {
|
||||
// MARK: Private
|
||||
|
||||
private func synchronizeCurrentVersion() {
|
||||
|
||||
|
||||
guard
|
||||
let currentPeriod = Advanced.EvolutionDemo.GeologicalPeriod(rawValue: self.findCurrentVersion())
|
||||
else {
|
||||
|
||||
|
||||
self.selectModelVersion(self.currentPeriod)
|
||||
return
|
||||
}
|
||||
@@ -144,106 +140,106 @@ extension Advanced.EvolutionDemo {
|
||||
}
|
||||
|
||||
private func selectModelVersion(_ period: Advanced.EvolutionDemo.GeologicalPeriod) {
|
||||
|
||||
|
||||
let currentPeriod = self.current?.period
|
||||
guard period != currentPeriod else {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
self.objectWillChange.send()
|
||||
|
||||
|
||||
self.isBusy = true
|
||||
|
||||
// explicitly trigger `NSPersistentStore` cleanup by deallocating the `DataStack`
|
||||
self.current = nil
|
||||
|
||||
|
||||
let migrationChain: MigrationChain
|
||||
switch (currentPeriod?.version, period.version) {
|
||||
|
||||
|
||||
case (nil, let newVersion):
|
||||
migrationChain = [newVersion]
|
||||
|
||||
|
||||
case (let currentVersion?, let newVersion):
|
||||
let upgradeMigrationChain = Advanced.EvolutionDemo.GeologicalPeriod.allCases
|
||||
.map({ $0.version })
|
||||
.map(\.version)
|
||||
let currentVersionIndex = upgradeMigrationChain.firstIndex(of: currentVersion)!
|
||||
let newVersionIndex = upgradeMigrationChain.firstIndex(of: newVersion)!
|
||||
|
||||
|
||||
migrationChain = MigrationChain(
|
||||
currentVersionIndex > newVersionIndex
|
||||
? upgradeMigrationChain.reversed()
|
||||
: upgradeMigrationChain
|
||||
? upgradeMigrationChain.reversed()
|
||||
: upgradeMigrationChain
|
||||
)
|
||||
}
|
||||
let dataStack = self.createDataStack(
|
||||
exactCurrentModelVersion: period.version,
|
||||
migrationChain: migrationChain
|
||||
)
|
||||
|
||||
let completion = { [weak self] () -> Void in
|
||||
|
||||
guard let self = self else {
|
||||
|
||||
return
|
||||
}
|
||||
self.objectWillChange.send()
|
||||
defer {
|
||||
|
||||
self.isBusy = false
|
||||
}
|
||||
self.current = (
|
||||
period: period,
|
||||
dataStack: dataStack,
|
||||
dataSource: period.creatureType.dataSource(in: dataStack)
|
||||
)
|
||||
self.currentPeriod = period
|
||||
}
|
||||
|
||||
|
||||
self.progress = dataStack.addStorage(
|
||||
self.accessSQLiteStore(),
|
||||
completion: { [weak self] result in
|
||||
|
||||
guard let self = self else {
|
||||
|
||||
|
||||
guard let self else {
|
||||
|
||||
return
|
||||
}
|
||||
guard case .success = result else {
|
||||
|
||||
self.objectWillChange.send()
|
||||
|
||||
self.isBusy = false
|
||||
return
|
||||
}
|
||||
if self.progress == nil {
|
||||
|
||||
self.spawnCreatures(in: dataStack, period: period, completion: completion)
|
||||
|
||||
self.spawnCreatures(in: dataStack, period: period) { [weak self] in
|
||||
self?.completeSelection(period: period, dataStack: dataStack)
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
completion()
|
||||
|
||||
self.completeSelection(period: period, dataStack: dataStack)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
private func completeSelection(
|
||||
period: Advanced.EvolutionDemo.GeologicalPeriod,
|
||||
dataStack: DataStack
|
||||
) {
|
||||
defer {
|
||||
|
||||
self.isBusy = false
|
||||
}
|
||||
self.current = (
|
||||
period: period,
|
||||
dataStack: dataStack,
|
||||
dataSource: period.creatureType.dataSource(in: dataStack)
|
||||
)
|
||||
self.currentPeriod = period
|
||||
self.progress = nil
|
||||
}
|
||||
|
||||
private func spawnCreatures(
|
||||
in dataStack: DataStack,
|
||||
period: Advanced.EvolutionDemo.GeologicalPeriod,
|
||||
completion: @escaping () -> Void
|
||||
completion: @escaping @MainActor @Sendable () -> Void
|
||||
) {
|
||||
|
||||
|
||||
dataStack.perform(
|
||||
asynchronous: { (transaction) in
|
||||
|
||||
asynchronous: { transaction in
|
||||
|
||||
let creatureType = period.creatureType
|
||||
for dnaCode in try creatureType.count(in: transaction) ..< 10000 {
|
||||
|
||||
|
||||
let object = creatureType.create(in: transaction)
|
||||
object.dnaCode = Int64(dnaCode)
|
||||
object.mutate(in: transaction)
|
||||
}
|
||||
},
|
||||
completion: { _ in completion() }
|
||||
completion: { _ in
|
||||
|
||||
completion()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+92
-86
@@ -2,125 +2,131 @@
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import Observation
|
||||
import SwiftUI
|
||||
|
||||
// MARK: - Advanced.EvolutionDemo
|
||||
|
||||
extension Advanced.EvolutionDemo {
|
||||
|
||||
|
||||
// MARK: - Advanced.EvolutionDemo.ProgressView
|
||||
|
||||
|
||||
struct ProgressView: View {
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
|
||||
init(progress: Progress?) {
|
||||
|
||||
self.progressObserver = .init(progress)
|
||||
|
||||
self.progress = progress
|
||||
self._progressObserver = State(initialValue: .init(progress))
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// MARK: View
|
||||
|
||||
|
||||
var body: some View {
|
||||
|
||||
guard self.progressObserver.isMigrating else {
|
||||
|
||||
return AnyView(
|
||||
|
||||
Group {
|
||||
|
||||
if self.progressObserver.isMigrating {
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
|
||||
Text("Migrating: \(self.progressObserver.localizedDescription)")
|
||||
.font(.headline)
|
||||
.padding([.top, .horizontal])
|
||||
|
||||
Text("Progressive step: \(self.progressObserver.localizedAdditionalDescription)")
|
||||
.font(.subheadline)
|
||||
.padding(.horizontal)
|
||||
|
||||
GeometryReader { geometry in
|
||||
|
||||
ZStack(alignment: .leading) {
|
||||
|
||||
RoundedRectangle(cornerRadius: 4, style: .continuous)
|
||||
.fill(Color.gray.opacity(0.2))
|
||||
.frame(width: geometry.size.width, height: 8)
|
||||
|
||||
RoundedRectangle(cornerRadius: 4, style: .continuous)
|
||||
.fill(Color.blue)
|
||||
.frame(
|
||||
width: geometry.size.width
|
||||
* self.progressObserver.fractionCompleted,
|
||||
height: 8
|
||||
)
|
||||
}
|
||||
}
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.padding()
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
else {
|
||||
|
||||
VStack(alignment: .center) {
|
||||
Text("Preparing creatures...")
|
||||
.padding()
|
||||
Spacer()
|
||||
}
|
||||
.padding()
|
||||
)
|
||||
}
|
||||
return AnyView(
|
||||
VStack(alignment: .leading) {
|
||||
Text("Migrating: \(self.progressObserver.localizedDescription)")
|
||||
.font(.headline)
|
||||
.padding([.top, .horizontal])
|
||||
Text("Progressive step: \(self.progressObserver.localizedAdditionalDescription)")
|
||||
.font(.subheadline)
|
||||
.padding(.horizontal)
|
||||
GeometryReader { geometry in
|
||||
ZStack(alignment: .leading) {
|
||||
RoundedRectangle(cornerRadius: 4, style: .continuous)
|
||||
.fill(Color.gray.opacity(0.2))
|
||||
.frame(width: geometry.size.width, height: 8)
|
||||
RoundedRectangle(cornerRadius: 4, style: .continuous)
|
||||
.fill(Color.blue)
|
||||
.frame(
|
||||
width: geometry.size.width
|
||||
* self.progressObserver.fractionCompleted,
|
||||
height: 8
|
||||
)
|
||||
}
|
||||
}
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.padding()
|
||||
Spacer()
|
||||
}
|
||||
.padding()
|
||||
)
|
||||
}
|
||||
.task(id: self.progress.map { ObjectIdentifier($0) }) {
|
||||
|
||||
self.progressObserver = .init(self.progress)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: FilePrivate
|
||||
|
||||
@ObservedObject
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let progress: Progress?
|
||||
|
||||
@State
|
||||
private var progressObserver: ProgressObserver
|
||||
|
||||
|
||||
|
||||
|
||||
// MARK: - ProgressObserver
|
||||
|
||||
fileprivate final class ProgressObserver: ObservableObject {
|
||||
|
||||
|
||||
@MainActor
|
||||
@Observable
|
||||
fileprivate final class ProgressObserver {
|
||||
|
||||
private(set) var fractionCompleted: CGFloat = 0
|
||||
private(set) var localizedDescription: String = ""
|
||||
private(set) var localizedAdditionalDescription: String = ""
|
||||
|
||||
|
||||
var isMigrating: Bool {
|
||||
|
||||
return self.progress != nil
|
||||
|
||||
self.progress != nil
|
||||
}
|
||||
|
||||
|
||||
init(_ progress: Progress?) {
|
||||
|
||||
|
||||
self.progress = progress
|
||||
|
||||
progress?.setProgressHandler { [weak self] (progess) in
|
||||
|
||||
guard let self = self else {
|
||||
return
|
||||
}
|
||||
self.objectWillChange.send()
|
||||
self.fractionCompleted = CGFloat(progress?.fractionCompleted ?? 0)
|
||||
self.localizedDescription = progress?.localizedDescription ?? ""
|
||||
self.localizedAdditionalDescription = progress?.localizedAdditionalDescription ?? ""
|
||||
self.syncValues(from: progress)
|
||||
|
||||
progress?.setProgressHandler { [weak self] progress in
|
||||
|
||||
self?.syncValues(from: progress)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
|
||||
@ObservationIgnored
|
||||
private let progress: Progress?
|
||||
|
||||
private func syncValues(from progress: Progress?) {
|
||||
|
||||
self.fractionCompleted = CGFloat(progress?.fractionCompleted ?? 0)
|
||||
self.localizedDescription = progress?.localizedDescription ?? ""
|
||||
self.localizedAdditionalDescription = progress?.localizedAdditionalDescription ?? ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
|
||||
struct _Demo_Advanced_EvolutionDemo_ProgressView_Preview: PreviewProvider {
|
||||
|
||||
// MARK: PreviewProvider
|
||||
|
||||
static var previews: some View {
|
||||
let progress = Progress(totalUnitCount: 10)
|
||||
progress.completedUnitCount = 3
|
||||
return Advanced.EvolutionDemo.ProgressView(
|
||||
progress: progress
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -10,7 +10,7 @@ import CoreStore
|
||||
|
||||
@objc(Advanced_EvolutionDemo_V1_Creature)
|
||||
final class Advanced_EvolutionDemo_V1_Creature: NSManagedObject, Advanced.EvolutionDemo.CreatureType {
|
||||
|
||||
|
||||
@NSManaged
|
||||
dynamic var dnaCode: Int64
|
||||
|
||||
@@ -30,9 +30,10 @@ final class Advanced_EvolutionDemo_V1_Creature: NSManagedObject, Advanced.Evolut
|
||||
|
||||
|
||||
// MARK: Advanced.EvolutionDemo.CreatureType
|
||||
|
||||
|
||||
@MainActor
|
||||
static func dataSource(in dataStack: DataStack) -> Advanced.EvolutionDemo.CreaturesDataSource {
|
||||
|
||||
|
||||
return .init(
|
||||
listPublisher: dataStack.publishList(
|
||||
From<Advanced.EvolutionDemo.V1.Creature>()
|
||||
@@ -41,16 +42,16 @@ final class Advanced_EvolutionDemo_V1_Creature: NSManagedObject, Advanced.Evolut
|
||||
dataStack: dataStack
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
static func count(in transaction: BaseDataTransaction) throws -> Int {
|
||||
|
||||
|
||||
return try transaction.fetchCount(
|
||||
From<Advanced.EvolutionDemo.V1.Creature>()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
static func create(in transaction: BaseDataTransaction) -> Advanced.EvolutionDemo.V1.Creature {
|
||||
|
||||
|
||||
return transaction.create(
|
||||
Into<Advanced.EvolutionDemo.V1.Creature>()
|
||||
)
|
||||
|
||||
@@ -10,7 +10,7 @@ import CoreStore
|
||||
|
||||
@objc(Advanced_EvolutionDemo_V2_Creature)
|
||||
final class Advanced_EvolutionDemo_V2_Creature: NSManagedObject, Advanced.EvolutionDemo.CreatureType {
|
||||
|
||||
|
||||
@NSManaged
|
||||
dynamic var dnaCode: Int64
|
||||
|
||||
@@ -42,9 +42,10 @@ final class Advanced_EvolutionDemo_V2_Creature: NSManagedObject, Advanced.Evolut
|
||||
|
||||
|
||||
// MARK: Advanced.EvolutionDemo.CreatureType
|
||||
|
||||
|
||||
@MainActor
|
||||
static func dataSource(in dataStack: DataStack) -> Advanced.EvolutionDemo.CreaturesDataSource {
|
||||
|
||||
|
||||
return .init(
|
||||
listPublisher: dataStack.publishList(
|
||||
From<Advanced.EvolutionDemo.V2.Creature>()
|
||||
@@ -53,16 +54,16 @@ final class Advanced_EvolutionDemo_V2_Creature: NSManagedObject, Advanced.Evolut
|
||||
dataStack: dataStack
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
static func count(in transaction: BaseDataTransaction) throws -> Int {
|
||||
|
||||
|
||||
return try transaction.fetchCount(
|
||||
From<Advanced.EvolutionDemo.V2.Creature>()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
static func create(in transaction: BaseDataTransaction) -> Advanced.EvolutionDemo.V2.Creature {
|
||||
|
||||
|
||||
return transaction.create(
|
||||
Into<Advanced.EvolutionDemo.V2.Creature>()
|
||||
)
|
||||
|
||||
+16
-15
@@ -8,31 +8,31 @@ import CoreStore
|
||||
// MARK: - Advanced.EvolutionDemo.V3
|
||||
|
||||
extension Advanced.EvolutionDemo.V3 {
|
||||
|
||||
|
||||
// MARK: - Advanced.EvolutionDemo.V3.Creature
|
||||
|
||||
|
||||
final class Creature: CoreStoreObject, Advanced.EvolutionDemo.CreatureType {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
|
||||
@Field.Stored("dnaCode")
|
||||
var dnaCode: Int64 = 0
|
||||
|
||||
|
||||
@Field.Stored("numberOfLimbs")
|
||||
var numberOfLimbs: Int32 = 0
|
||||
|
||||
|
||||
@Field.Stored("hasVertebrae")
|
||||
var hasVertebrae: Bool = false
|
||||
|
||||
|
||||
@Field.Stored("hasHead")
|
||||
var hasHead: Bool = true
|
||||
|
||||
|
||||
@Field.Stored("hasTail")
|
||||
var hasTail: Bool = true
|
||||
|
||||
|
||||
@Field.Stored("hasWings")
|
||||
var hasWings: Bool = false
|
||||
|
||||
|
||||
@Field.Stored("habitat")
|
||||
var habitat: Habitat = .water
|
||||
|
||||
@@ -64,9 +64,10 @@ extension Advanced.EvolutionDemo.V3 {
|
||||
|
||||
|
||||
// MARK: Advanced.EvolutionDemo.CreatureType
|
||||
|
||||
|
||||
@MainActor
|
||||
static func dataSource(in dataStack: DataStack) -> Advanced.EvolutionDemo.CreaturesDataSource {
|
||||
|
||||
|
||||
return .init(
|
||||
listPublisher: dataStack.publishList(
|
||||
From<Advanced.EvolutionDemo.V3.Creature>()
|
||||
@@ -75,16 +76,16 @@ extension Advanced.EvolutionDemo.V3 {
|
||||
dataStack: dataStack
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
static func count(in transaction: BaseDataTransaction) throws -> Int {
|
||||
|
||||
|
||||
return try transaction.fetchCount(
|
||||
From<Advanced.EvolutionDemo.V3.Creature>()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
static func create(in transaction: BaseDataTransaction) -> Advanced.EvolutionDemo.V3.Creature {
|
||||
|
||||
|
||||
return transaction.create(
|
||||
Into<Advanced.EvolutionDemo.V3.Creature>()
|
||||
)
|
||||
|
||||
+17
-16
@@ -8,37 +8,37 @@ import CoreStore
|
||||
// MARK: - Advanced.EvolutionDemo.V4
|
||||
|
||||
extension Advanced.EvolutionDemo.V4 {
|
||||
|
||||
|
||||
// MARK: - Advanced.EvolutionDemo.V4.Creature
|
||||
|
||||
|
||||
final class Creature: CoreStoreObject, Advanced.EvolutionDemo.CreatureType {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
|
||||
@Field.Stored("dnaCode")
|
||||
var dnaCode: Int64 = 0
|
||||
|
||||
|
||||
@Field.Stored("numberOfLimbs")
|
||||
var numberOfLimbs: Int32 = 0
|
||||
|
||||
|
||||
@Field.Stored("hasVertebrae")
|
||||
var hasVertebrae: Bool = false
|
||||
|
||||
|
||||
@Field.Stored("hasHead")
|
||||
var hasHead: Bool = true
|
||||
|
||||
|
||||
@Field.Stored("hasTail")
|
||||
var hasTail: Bool = false
|
||||
|
||||
|
||||
@Field.Stored("hasWings")
|
||||
var hasWings: Bool = false
|
||||
|
||||
|
||||
typealias Habitat = Advanced.EvolutionDemo.V3.Creature.Habitat
|
||||
|
||||
|
||||
@Field.Stored("habitat")
|
||||
var habitat: Habitat = .water
|
||||
|
||||
|
||||
@Field.Stored("isWarmBlooded")
|
||||
var isWarmBlooded: Bool = true
|
||||
|
||||
@@ -60,9 +60,10 @@ extension Advanced.EvolutionDemo.V4 {
|
||||
|
||||
|
||||
// MARK: Advanced.EvolutionDemo.CreatureType
|
||||
|
||||
|
||||
@MainActor
|
||||
static func dataSource(in dataStack: DataStack) -> Advanced.EvolutionDemo.CreaturesDataSource {
|
||||
|
||||
|
||||
return .init(
|
||||
listPublisher: dataStack.publishList(
|
||||
From<Advanced.EvolutionDemo.V4.Creature>()
|
||||
@@ -71,16 +72,16 @@ extension Advanced.EvolutionDemo.V4 {
|
||||
dataStack: dataStack
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
static func count(in transaction: BaseDataTransaction) throws -> Int {
|
||||
|
||||
|
||||
return try transaction.fetchCount(
|
||||
From<Advanced.EvolutionDemo.V4.Creature>()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
static func create(in transaction: BaseDataTransaction) -> Advanced.EvolutionDemo.V4.Creature {
|
||||
|
||||
|
||||
return transaction.create(
|
||||
Into<Advanced.EvolutionDemo.V4.Creature>()
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user