mirror of
https://github.com/JohnEstropia/CoreStore.git
synced 2026-09-03 04:37:20 +02:00
cleanup
This commit is contained in:
@@ -1060,17 +1060,17 @@ class ImportTests: BaseTestDataTestCase {
|
|||||||
|
|
||||||
// MARK: - TestInsertError
|
// MARK: - TestInsertError
|
||||||
|
|
||||||
private struct TestInsertError: Error {}
|
private struct TestInsertError: Swift::Error {}
|
||||||
|
|
||||||
|
|
||||||
// MARK: - TestUpdateError
|
// MARK: - TestUpdateError
|
||||||
|
|
||||||
private struct TestUpdateError: Error {}
|
private struct TestUpdateError: Swift::Error {}
|
||||||
|
|
||||||
|
|
||||||
// MARK: - TestIDError
|
// MARK: - TestIDError
|
||||||
|
|
||||||
private struct TestIDError: Error {}
|
private struct TestIDError: Swift::Error {}
|
||||||
|
|
||||||
|
|
||||||
// MARK: - TestEntity1
|
// MARK: - TestEntity1
|
||||||
|
|||||||
@@ -0,0 +1,545 @@
|
|||||||
|
#if canImport(SwiftUI) && canImport(AppKit)
|
||||||
|
|
||||||
|
import AppKit
|
||||||
|
import Combine
|
||||||
|
import SwiftUI
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
@testable
|
||||||
|
import CoreStore
|
||||||
|
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
private final class ObjectPublisherModel: ObservableObject {
|
||||||
|
|
||||||
|
@Published var objectPublisher: ObjectPublisher<TestEntity1>?
|
||||||
|
|
||||||
|
init(_ objectPublisher: ObjectPublisher<TestEntity1>?) {
|
||||||
|
|
||||||
|
self.objectPublisher = objectPublisher
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
private final class ListPublisherModel: ObservableObject {
|
||||||
|
|
||||||
|
@Published var listPublisher: ListPublisher<TestEntity1>
|
||||||
|
|
||||||
|
init(_ listPublisher: ListPublisher<TestEntity1>) {
|
||||||
|
|
||||||
|
self.listPublisher = listPublisher
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private struct SignalView<Value: Equatable>: View {
|
||||||
|
|
||||||
|
let value: Value
|
||||||
|
let report: (Value) -> Void
|
||||||
|
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
|
||||||
|
SwiftUI.Color.clear
|
||||||
|
.frame(width: 1, height: 1)
|
||||||
|
.onChange(of: self.value, initial: true) { _, value in
|
||||||
|
|
||||||
|
self.report(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
private struct ObjectStateHarness: View {
|
||||||
|
|
||||||
|
@ObservedObject var model: ObjectPublisherModel
|
||||||
|
|
||||||
|
let report: (String) -> Void
|
||||||
|
|
||||||
|
@ObjectState
|
||||||
|
private var object: ObjectSnapshot<TestEntity1>?
|
||||||
|
|
||||||
|
init(
|
||||||
|
model: ObjectPublisherModel,
|
||||||
|
report: @escaping (String) -> Void
|
||||||
|
) {
|
||||||
|
|
||||||
|
self.model = model
|
||||||
|
self.report = report
|
||||||
|
self._object = .init(model.objectPublisher)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
|
||||||
|
SignalView(
|
||||||
|
value: self.object?.testString ?? "<nil>",
|
||||||
|
report: self.report
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
private struct ListStateHarness: View {
|
||||||
|
|
||||||
|
@ObservedObject var model: ListPublisherModel
|
||||||
|
|
||||||
|
let report: (String) -> Void
|
||||||
|
|
||||||
|
@ListState
|
||||||
|
private var list: ListSnapshot<TestEntity1>
|
||||||
|
|
||||||
|
init(
|
||||||
|
model: ListPublisherModel,
|
||||||
|
report: @escaping (String) -> Void
|
||||||
|
) {
|
||||||
|
|
||||||
|
self.model = model
|
||||||
|
self.report = report
|
||||||
|
self._list = .init(model.listPublisher)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
|
||||||
|
SignalView(
|
||||||
|
value: listSignature(self.list),
|
||||||
|
report: self.report
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
private struct ObjectReaderHarness: View {
|
||||||
|
|
||||||
|
@ObservedObject var model: ObjectPublisherModel
|
||||||
|
|
||||||
|
let report: (String) -> Void
|
||||||
|
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
|
||||||
|
ObjectReader(
|
||||||
|
self.model.objectPublisher,
|
||||||
|
keyPath: \.testString,
|
||||||
|
content: { value in
|
||||||
|
|
||||||
|
SignalView(
|
||||||
|
value: value ?? "<nil>",
|
||||||
|
report: self.report
|
||||||
|
)
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
|
||||||
|
SignalView(
|
||||||
|
value: "<placeholder>",
|
||||||
|
report: self.report
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
private struct ListReaderHarness: View {
|
||||||
|
|
||||||
|
@ObservedObject var model: ListPublisherModel
|
||||||
|
|
||||||
|
let report: (String) -> Void
|
||||||
|
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
|
||||||
|
ListReader(self.model.listPublisher) { list in
|
||||||
|
|
||||||
|
SignalView(
|
||||||
|
value: listSignature(list),
|
||||||
|
report: self.report
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
private func listSignature(_ list: ListSnapshot<TestEntity1>) -> String {
|
||||||
|
|
||||||
|
let ids = list.map {
|
||||||
|
|
||||||
|
String($0.testEntityID?.intValue ?? -1)
|
||||||
|
}
|
||||||
|
.joined(separator: ",")
|
||||||
|
return ids.isEmpty ? "<empty>" : ids
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - SwiftUIReaderStateTests
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
final class SwiftUIReaderStateTests: BaseTestDataTestCase {
|
||||||
|
|
||||||
|
@objc
|
||||||
|
dynamic func test_ThatObjectState_RebindsWhenPublisherChanges() {
|
||||||
|
|
||||||
|
self.prepareStack { stack in
|
||||||
|
|
||||||
|
self.prepareTestDataForStack(stack)
|
||||||
|
|
||||||
|
let firstPublisher = self.objectPublisher(withID: 101, in: stack)
|
||||||
|
let secondPublisher = self.objectPublisher(withID: 102, in: stack)
|
||||||
|
let model = ObjectPublisherModel(firstPublisher)
|
||||||
|
|
||||||
|
let initialExpectation = self.expectation(description: "initial")
|
||||||
|
let swappedExpectation = self.expectation(description: "swapped")
|
||||||
|
let finalExpectation = self.expectation(description: "final")
|
||||||
|
let staleExpectation = self.expectation(description: "stale")
|
||||||
|
staleExpectation.isInverted = true
|
||||||
|
|
||||||
|
var didSeeInitial = false
|
||||||
|
var didSeeSwapped = false
|
||||||
|
var didSeeFinal = false
|
||||||
|
var values: [String] = []
|
||||||
|
|
||||||
|
let window = self.host(
|
||||||
|
ObjectStateHarness(model: model) { value in
|
||||||
|
|
||||||
|
values.append(value)
|
||||||
|
switch value {
|
||||||
|
|
||||||
|
case "nil:TestEntity1:1" where !didSeeInitial:
|
||||||
|
didSeeInitial = true
|
||||||
|
initialExpectation.fulfill()
|
||||||
|
|
||||||
|
case "nil:TestEntity1:2" where !didSeeSwapped:
|
||||||
|
didSeeSwapped = true
|
||||||
|
swappedExpectation.fulfill()
|
||||||
|
|
||||||
|
case "new-bound" where !didSeeFinal:
|
||||||
|
didSeeFinal = true
|
||||||
|
finalExpectation.fulfill()
|
||||||
|
|
||||||
|
case "old-bound":
|
||||||
|
staleExpectation.fulfill()
|
||||||
|
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.wait(for: [initialExpectation], timeout: 10)
|
||||||
|
|
||||||
|
model.objectPublisher = secondPublisher
|
||||||
|
self.wait(for: [swappedExpectation], timeout: 10)
|
||||||
|
|
||||||
|
self.updateObjectString(withID: 101, to: "old-bound", in: stack)
|
||||||
|
self.updateObjectString(withID: 102, to: "new-bound", in: stack)
|
||||||
|
self.wait(for: [finalExpectation, staleExpectation], timeout: 10)
|
||||||
|
|
||||||
|
XCTAssertFalse(values.contains("old-bound"))
|
||||||
|
withExtendedLifetime(window, {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc
|
||||||
|
dynamic func test_ThatListState_RebindsWhenPublisherChanges() {
|
||||||
|
|
||||||
|
self.prepareStack { stack in
|
||||||
|
|
||||||
|
self.prepareTestDataForStack(stack)
|
||||||
|
|
||||||
|
let firstPublisher = self.listPublisher(matching: true, in: stack)
|
||||||
|
let secondPublisher = self.listPublisher(matching: false, in: stack)
|
||||||
|
let model = ListPublisherModel(firstPublisher)
|
||||||
|
|
||||||
|
let initialExpectation = self.expectation(description: "initial")
|
||||||
|
let swappedExpectation = self.expectation(description: "swapped")
|
||||||
|
let finalExpectation = self.expectation(description: "final")
|
||||||
|
let staleExpectation = self.expectation(description: "stale")
|
||||||
|
staleExpectation.isInverted = true
|
||||||
|
|
||||||
|
var didSeeInitial = false
|
||||||
|
var didSeeSwapped = false
|
||||||
|
var didSeeFinal = false
|
||||||
|
var values: [String] = []
|
||||||
|
|
||||||
|
let window = self.host(
|
||||||
|
ListStateHarness(model: model) { value in
|
||||||
|
|
||||||
|
values.append(value)
|
||||||
|
switch value {
|
||||||
|
|
||||||
|
case "101,103,105" where !didSeeInitial:
|
||||||
|
didSeeInitial = true
|
||||||
|
initialExpectation.fulfill()
|
||||||
|
|
||||||
|
case "102,104" where !didSeeSwapped:
|
||||||
|
didSeeSwapped = true
|
||||||
|
swappedExpectation.fulfill()
|
||||||
|
|
||||||
|
case "102,104,108" where !didSeeFinal:
|
||||||
|
didSeeFinal = true
|
||||||
|
finalExpectation.fulfill()
|
||||||
|
|
||||||
|
case "101,103,105,107":
|
||||||
|
staleExpectation.fulfill()
|
||||||
|
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.wait(for: [initialExpectation], timeout: 10)
|
||||||
|
|
||||||
|
model.listPublisher = secondPublisher
|
||||||
|
self.wait(for: [swappedExpectation], timeout: 10)
|
||||||
|
|
||||||
|
self.insertObject(withID: 107, string: "old-list", boolean: true, in: stack)
|
||||||
|
self.insertObject(withID: 108, string: "new-list", boolean: false, in: stack)
|
||||||
|
self.wait(for: [finalExpectation, staleExpectation], timeout: 10)
|
||||||
|
|
||||||
|
XCTAssertFalse(values.contains("101,103,105,107"))
|
||||||
|
withExtendedLifetime(window, {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc
|
||||||
|
dynamic func test_ThatObjectReaders_RebindAndShowCustomPlaceholders() {
|
||||||
|
|
||||||
|
self.prepareStack { stack in
|
||||||
|
|
||||||
|
self.prepareTestDataForStack(stack)
|
||||||
|
|
||||||
|
let firstPublisher = self.objectPublisher(withID: 101, in: stack)
|
||||||
|
let secondPublisher = self.objectPublisher(withID: 102, in: stack)
|
||||||
|
let model = ObjectPublisherModel(firstPublisher)
|
||||||
|
|
||||||
|
let initialExpectation = self.expectation(description: "initial")
|
||||||
|
let swappedExpectation = self.expectation(description: "swapped")
|
||||||
|
let placeholderExpectation = self.expectation(description: "placeholder")
|
||||||
|
let staleExpectation = self.expectation(description: "stale")
|
||||||
|
staleExpectation.isInverted = true
|
||||||
|
|
||||||
|
var didSeeInitial = false
|
||||||
|
var didSeeSwapped = false
|
||||||
|
var didSeePlaceholder = false
|
||||||
|
var values: [String] = []
|
||||||
|
|
||||||
|
let window = self.host(
|
||||||
|
ObjectReaderHarness(model: model) { value in
|
||||||
|
|
||||||
|
values.append(value)
|
||||||
|
switch value {
|
||||||
|
|
||||||
|
case "nil:TestEntity1:1" where !didSeeInitial:
|
||||||
|
didSeeInitial = true
|
||||||
|
initialExpectation.fulfill()
|
||||||
|
|
||||||
|
case "nil:TestEntity1:2" where !didSeeSwapped:
|
||||||
|
didSeeSwapped = true
|
||||||
|
swappedExpectation.fulfill()
|
||||||
|
|
||||||
|
case "<placeholder>" where !didSeePlaceholder:
|
||||||
|
didSeePlaceholder = true
|
||||||
|
placeholderExpectation.fulfill()
|
||||||
|
|
||||||
|
case "old-reader":
|
||||||
|
staleExpectation.fulfill()
|
||||||
|
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.wait(for: [initialExpectation], timeout: 10)
|
||||||
|
|
||||||
|
model.objectPublisher = secondPublisher
|
||||||
|
self.wait(for: [swappedExpectation], timeout: 10)
|
||||||
|
|
||||||
|
self.deleteObject(withID: 102, in: stack)
|
||||||
|
self.updateObjectString(withID: 101, to: "old-reader", in: stack)
|
||||||
|
self.wait(for: [placeholderExpectation, staleExpectation], timeout: 10)
|
||||||
|
|
||||||
|
XCTAssertFalse(values.contains("old-reader"))
|
||||||
|
withExtendedLifetime(window, {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc
|
||||||
|
dynamic func test_ThatListReaders_RebindWhenPublisherChanges() {
|
||||||
|
|
||||||
|
self.prepareStack { stack in
|
||||||
|
|
||||||
|
self.prepareTestDataForStack(stack)
|
||||||
|
|
||||||
|
let firstPublisher = self.listPublisher(matching: true, in: stack)
|
||||||
|
let secondPublisher = self.listPublisher(matching: false, in: stack)
|
||||||
|
let model = ListPublisherModel(firstPublisher)
|
||||||
|
|
||||||
|
let initialExpectation = self.expectation(description: "initial")
|
||||||
|
let swappedExpectation = self.expectation(description: "swapped")
|
||||||
|
let finalExpectation = self.expectation(description: "final")
|
||||||
|
let staleExpectation = self.expectation(description: "stale")
|
||||||
|
staleExpectation.isInverted = true
|
||||||
|
|
||||||
|
var didSeeInitial = false
|
||||||
|
var didSeeSwapped = false
|
||||||
|
var didSeeFinal = false
|
||||||
|
var values: [String] = []
|
||||||
|
|
||||||
|
let window = self.host(
|
||||||
|
ListReaderHarness(model: model) { value in
|
||||||
|
|
||||||
|
values.append(value)
|
||||||
|
switch value {
|
||||||
|
|
||||||
|
case "101,103,105" where !didSeeInitial:
|
||||||
|
didSeeInitial = true
|
||||||
|
initialExpectation.fulfill()
|
||||||
|
|
||||||
|
case "102,104" where !didSeeSwapped:
|
||||||
|
didSeeSwapped = true
|
||||||
|
swappedExpectation.fulfill()
|
||||||
|
|
||||||
|
case "102,104,110" where !didSeeFinal:
|
||||||
|
didSeeFinal = true
|
||||||
|
finalExpectation.fulfill()
|
||||||
|
|
||||||
|
case "101,103,105,109":
|
||||||
|
staleExpectation.fulfill()
|
||||||
|
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.wait(for: [initialExpectation], timeout: 10)
|
||||||
|
|
||||||
|
model.listPublisher = secondPublisher
|
||||||
|
self.wait(for: [swappedExpectation], timeout: 10)
|
||||||
|
|
||||||
|
self.insertObject(withID: 109, string: "old-reader", boolean: true, in: stack)
|
||||||
|
self.insertObject(withID: 110, string: "new-reader", boolean: false, in: stack)
|
||||||
|
self.wait(for: [finalExpectation, staleExpectation], timeout: 10)
|
||||||
|
|
||||||
|
XCTAssertFalse(values.contains("101,103,105,109"))
|
||||||
|
withExtendedLifetime(window, {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: Private
|
||||||
|
|
||||||
|
private func host<Content: View>(_ view: Content) -> NSWindow {
|
||||||
|
|
||||||
|
let window = NSWindow(
|
||||||
|
contentRect: NSRect(x: 0, y: 0, width: 8, height: 8),
|
||||||
|
styleMask: [.borderless],
|
||||||
|
backing: .buffered,
|
||||||
|
defer: false
|
||||||
|
)
|
||||||
|
window.contentView = NSHostingView(rootView: view)
|
||||||
|
window.contentView?.layoutSubtreeIfNeeded()
|
||||||
|
window.displayIfNeeded()
|
||||||
|
return window
|
||||||
|
}
|
||||||
|
|
||||||
|
private func objectPublisher(
|
||||||
|
withID identifier: Int,
|
||||||
|
in stack: DataStack
|
||||||
|
) -> ObjectPublisher<TestEntity1> {
|
||||||
|
|
||||||
|
let object = try! stack.fetchOne(
|
||||||
|
From<TestEntity1>(),
|
||||||
|
Where<TestEntity1>(
|
||||||
|
#keyPath(TestEntity1.testEntityID),
|
||||||
|
isEqualTo: NSNumber(value: identifier)
|
||||||
|
)
|
||||||
|
)!
|
||||||
|
return stack.publishObject(object)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func listPublisher(
|
||||||
|
matching boolean: Bool,
|
||||||
|
in stack: DataStack
|
||||||
|
) -> ListPublisher<TestEntity1> {
|
||||||
|
|
||||||
|
return stack.publishList(
|
||||||
|
From<TestEntity1>(),
|
||||||
|
Where<TestEntity1>(
|
||||||
|
#keyPath(TestEntity1.testBoolean),
|
||||||
|
isEqualTo: NSNumber(value: boolean)
|
||||||
|
),
|
||||||
|
OrderBy<TestEntity1>(.ascending(#keyPath(TestEntity1.testEntityID)))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateObjectString(
|
||||||
|
withID identifier: Int,
|
||||||
|
to string: String,
|
||||||
|
in stack: DataStack
|
||||||
|
) {
|
||||||
|
|
||||||
|
try! stack.perform(
|
||||||
|
synchronous: { transaction in
|
||||||
|
|
||||||
|
let object = try transaction.fetchOne(
|
||||||
|
From<TestEntity1>(),
|
||||||
|
Where<TestEntity1>(
|
||||||
|
#keyPath(TestEntity1.testEntityID),
|
||||||
|
isEqualTo: NSNumber(value: identifier)
|
||||||
|
)
|
||||||
|
)!
|
||||||
|
object.testString = string
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func deleteObject(
|
||||||
|
withID identifier: Int,
|
||||||
|
in stack: DataStack
|
||||||
|
) {
|
||||||
|
|
||||||
|
try! stack.perform(
|
||||||
|
synchronous: { transaction in
|
||||||
|
|
||||||
|
let object = try transaction.fetchOne(
|
||||||
|
From<TestEntity1>(),
|
||||||
|
Where<TestEntity1>(
|
||||||
|
#keyPath(TestEntity1.testEntityID),
|
||||||
|
isEqualTo: NSNumber(value: identifier)
|
||||||
|
)
|
||||||
|
)!
|
||||||
|
transaction.delete(object)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func insertObject(
|
||||||
|
withID identifier: Int,
|
||||||
|
string: String,
|
||||||
|
boolean: Bool,
|
||||||
|
in stack: DataStack
|
||||||
|
) {
|
||||||
|
|
||||||
|
try! stack.perform(
|
||||||
|
synchronous: { transaction in
|
||||||
|
|
||||||
|
let object = transaction.create(Into<TestEntity1>())
|
||||||
|
object.testEntityID = NSNumber(value: identifier)
|
||||||
|
object.testString = string
|
||||||
|
object.testBoolean = NSNumber(value: boolean)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
|
5F035E4F300F726700E98F8F /* WithMainActorImmediate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F035E4E300F725800E98F8F /* WithMainActorImmediate.swift */; };
|
||||||
B531EFE724EA762D005F247D /* Menu.PlaceholderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B531EFE624EA762D005F247D /* Menu.PlaceholderView.swift */; };
|
B531EFE724EA762D005F247D /* Menu.PlaceholderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B531EFE624EA762D005F247D /* Menu.PlaceholderView.swift */; };
|
||||||
B531EFE924EB5A53005F247D /* Modern.PokedexDemo.PokedexEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = B531EFE824EB5A52005F247D /* Modern.PokedexDemo.PokedexEntry.swift */; };
|
B531EFE924EB5A53005F247D /* Modern.PokedexDemo.PokedexEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = B531EFE824EB5A52005F247D /* Modern.PokedexDemo.PokedexEntry.swift */; };
|
||||||
B531EFEB24EB5ECD005F247D /* Modern.PokedexDemo.Service.swift in Sources */ = {isa = PBXBuildFile; fileRef = B531EFEA24EB5ECD005F247D /* Modern.PokedexDemo.Service.swift */; };
|
B531EFEB24EB5ECD005F247D /* Modern.PokedexDemo.Service.swift in Sources */ = {isa = PBXBuildFile; fileRef = B531EFEA24EB5ECD005F247D /* Modern.PokedexDemo.Service.swift */; };
|
||||||
@@ -38,7 +39,7 @@
|
|||||||
B5A3916024E6925900E7E8BD /* Modern.PlacemarksDemo.MapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3915F24E6925900E7E8BD /* Modern.PlacemarksDemo.MapView.swift */; };
|
B5A3916024E6925900E7E8BD /* Modern.PlacemarksDemo.MapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3915F24E6925900E7E8BD /* Modern.PlacemarksDemo.MapView.swift */; };
|
||||||
B5A3916224E697BA00E7E8BD /* Modern.PlacemarksDemo.MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3916124E697BA00E7E8BD /* Modern.PlacemarksDemo.MainView.swift */; };
|
B5A3916224E697BA00E7E8BD /* Modern.PlacemarksDemo.MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3916124E697BA00E7E8BD /* Modern.PlacemarksDemo.MainView.swift */; };
|
||||||
B5A3916524E698C700E7E8BD /* Modern.PlacemarksDemo.Place.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3916424E698C700E7E8BD /* Modern.PlacemarksDemo.Place.swift */; };
|
B5A3916524E698C700E7E8BD /* Modern.PlacemarksDemo.Place.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3916424E698C700E7E8BD /* Modern.PlacemarksDemo.Place.swift */; };
|
||||||
B5A3916B24E698F900E7E8BD /* CoreStore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5A3916724E698F900E7E8BD /* CoreStore.framework */; };
|
B5A3916B24E698F900E7E8BD /* CoreStore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5A3916724E698F900E7E8BD /* CoreStore.framework */; platformFilter = ios; };
|
||||||
B5A3916C24E698F900E7E8BD /* CoreStore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B5A3916724E698F900E7E8BD /* CoreStore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
B5A3916C24E698F900E7E8BD /* CoreStore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B5A3916724E698F900E7E8BD /* CoreStore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||||
B5A3917524E6990200E7E8BD /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5A3917424E6990200E7E8BD /* MapKit.framework */; };
|
B5A3917524E6990200E7E8BD /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5A3917424E6990200E7E8BD /* MapKit.framework */; };
|
||||||
B5A3917724E6990700E7E8BD /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5A3917624E6990700E7E8BD /* CoreLocation.framework */; };
|
B5A3917724E6990700E7E8BD /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5A3917624E6990700E7E8BD /* CoreLocation.framework */; };
|
||||||
@@ -115,6 +116,7 @@
|
|||||||
/* End PBXCopyFilesBuildPhase section */
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
|
5F035E4E300F725800E98F8F /* WithMainActorImmediate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WithMainActorImmediate.swift; sourceTree = "<group>"; };
|
||||||
B531EFE624EA762D005F247D /* Menu.PlaceholderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Menu.PlaceholderView.swift; sourceTree = "<group>"; };
|
B531EFE624EA762D005F247D /* Menu.PlaceholderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Menu.PlaceholderView.swift; sourceTree = "<group>"; };
|
||||||
B531EFE824EB5A52005F247D /* Modern.PokedexDemo.PokedexEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PokedexDemo.PokedexEntry.swift; sourceTree = "<group>"; };
|
B531EFE824EB5A52005F247D /* Modern.PokedexDemo.PokedexEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PokedexDemo.PokedexEntry.swift; sourceTree = "<group>"; };
|
||||||
B531EFEA24EB5ECD005F247D /* Modern.PokedexDemo.Service.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PokedexDemo.Service.swift; sourceTree = "<group>"; };
|
B531EFEA24EB5ECD005F247D /* Modern.PokedexDemo.Service.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PokedexDemo.Service.swift; sourceTree = "<group>"; };
|
||||||
@@ -366,6 +368,7 @@
|
|||||||
B5A3917A24E6A75F00E7E8BD /* Helpers */ = {
|
B5A3917A24E6A75F00E7E8BD /* Helpers */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
5F035E4E300F725800E98F8F /* WithMainActorImmediate.swift */,
|
||||||
B5A3917F24E787D900E7E8BD /* InstructionsView.swift */,
|
B5A3917F24E787D900E7E8BD /* InstructionsView.swift */,
|
||||||
B5E32C8F24FA41F9003F46AD /* ImageDownloader.swift */,
|
B5E32C8F24FA41F9003F46AD /* ImageDownloader.swift */,
|
||||||
B5A3915424E6857F00E7E8BD /* Menu */,
|
B5A3915424E6857F00E7E8BD /* Menu */,
|
||||||
@@ -685,6 +688,7 @@
|
|||||||
B5A54401250487C7000DC5E3 /* Advanced.EvolutionDemo.ListView.swift in Sources */,
|
B5A54401250487C7000DC5E3 /* Advanced.EvolutionDemo.ListView.swift in Sources */,
|
||||||
B5A543FF250487B1000DC5E3 /* Advanced.EvolutionDemo.MainView.swift in Sources */,
|
B5A543FF250487B1000DC5E3 /* Advanced.EvolutionDemo.MainView.swift in Sources */,
|
||||||
B5D6F209250E14AA00DF5D2F /* Advanced.EvolutionDemo.Migrator.swift in Sources */,
|
B5D6F209250E14AA00DF5D2F /* Advanced.EvolutionDemo.Migrator.swift in Sources */,
|
||||||
|
5F035E4F300F726700E98F8F /* WithMainActorImmediate.swift in Sources */,
|
||||||
B5C18F3325138700001BEFB3 /* Advanced.EvolutionDemo.ProgressView.swift in Sources */,
|
B5C18F3325138700001BEFB3 /* Advanced.EvolutionDemo.ProgressView.swift in Sources */,
|
||||||
B5D6F1F8250E07FD00DF5D2F /* Advanced.EvolutionDemo.V1.swift in Sources */,
|
B5D6F1F8250E07FD00DF5D2F /* Advanced.EvolutionDemo.V1.swift in Sources */,
|
||||||
B5D6F210250E1E3200DF5D2F /* Advanced.EvolutionDemo.V1.xcdatamodeld in Sources */,
|
B5D6F210250E1E3200DF5D2F /* Advanced.EvolutionDemo.V1.xcdatamodeld in Sources */,
|
||||||
|
|||||||
@@ -3,9 +3,23 @@
|
|||||||
LastUpgradeVersion = "1600"
|
LastUpgradeVersion = "1600"
|
||||||
version = "1.3">
|
version = "1.3">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "NO"
|
||||||
buildImplicitDependencies = "YES">
|
buildImplicitDependencies = "NO">
|
||||||
<BuildActionEntries>
|
<BuildActionEntries>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "2F03A52F19C5C6DA005002A5"
|
||||||
|
BuildableName = "CoreStore.framework"
|
||||||
|
BlueprintName = "CoreStore iOS"
|
||||||
|
ReferencedContainer = "container:../CoreStore.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
<BuildActionEntry
|
<BuildActionEntry
|
||||||
buildForTesting = "YES"
|
buildForTesting = "YES"
|
||||||
buildForRunning = "YES"
|
buildForRunning = "YES"
|
||||||
|
|||||||
+16
-10
@@ -48,7 +48,7 @@ extension Classic.ColorsDemo {
|
|||||||
/**
|
/**
|
||||||
⭐️ Sample 3: We can end monitoring updates anytime. `removeObserver()` was called here for illustration purposes only. `ObjectMonitor`s safely remove deallocated observers automatically.
|
⭐️ Sample 3: We can end monitoring updates anytime. `removeObserver()` was called here for illustration purposes only. `ObjectMonitor`s safely remove deallocated observers automatically.
|
||||||
*/
|
*/
|
||||||
deinit {
|
isolated deinit {
|
||||||
|
|
||||||
self.palette.removeObserver(self)
|
self.palette.removeObserver(self)
|
||||||
}
|
}
|
||||||
@@ -87,13 +87,16 @@ extension Classic.ColorsDemo {
|
|||||||
|
|
||||||
// MARK: ObjectObserver
|
// MARK: ObjectObserver
|
||||||
|
|
||||||
func objectMonitor(
|
nonisolated func objectMonitor(
|
||||||
_ monitor: ObjectMonitor<Classic.ColorsDemo.Palette>,
|
_ monitor: ObjectMonitor<Classic.ColorsDemo.Palette>,
|
||||||
didUpdateObject object: Classic.ColorsDemo.Palette,
|
didUpdateObject object: sending Classic.ColorsDemo.Palette,
|
||||||
changedPersistentKeys: Set<KeyPathString>
|
changedPersistentKeys: Set<KeyPathString>
|
||||||
) {
|
) {
|
||||||
|
|
||||||
self.reloadPaletteInfo(object, changedKeys: changedPersistentKeys)
|
withMainActorImmediate {
|
||||||
|
|
||||||
|
self.reloadPaletteInfo(object, changedKeys: changedPersistentKeys)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -248,10 +251,11 @@ extension Classic.ColorsDemo {
|
|||||||
private dynamic func hueSliderValueDidChange(_ sender: UISlider) {
|
private dynamic func hueSliderValueDidChange(_ sender: UISlider) {
|
||||||
|
|
||||||
let value = sender.value
|
let value = sender.value
|
||||||
|
let persistentID = self.palette.object?.persistentID()
|
||||||
Classic.ColorsDemo.dataStack.perform(
|
Classic.ColorsDemo.dataStack.perform(
|
||||||
asynchronous: { [weak self] (transaction) in
|
asynchronous: { (transaction) in
|
||||||
|
|
||||||
let palette = transaction.edit(self?.palette.object)
|
let palette = transaction.edit(persistentID)
|
||||||
palette?.hue = value
|
palette?.hue = value
|
||||||
},
|
},
|
||||||
completion: { _ in }
|
completion: { _ in }
|
||||||
@@ -262,10 +266,11 @@ extension Classic.ColorsDemo {
|
|||||||
private dynamic func saturationSliderValueDidChange(_ sender: UISlider) {
|
private dynamic func saturationSliderValueDidChange(_ sender: UISlider) {
|
||||||
|
|
||||||
let value = sender.value
|
let value = sender.value
|
||||||
|
let persistentID = self.palette.object?.persistentID()
|
||||||
Classic.ColorsDemo.dataStack.perform(
|
Classic.ColorsDemo.dataStack.perform(
|
||||||
asynchronous: { [weak self] (transaction) in
|
asynchronous: { (transaction) in
|
||||||
|
|
||||||
let palette = transaction.edit(self?.palette.object)
|
let palette = transaction.edit(persistentID)
|
||||||
palette?.saturation = value
|
palette?.saturation = value
|
||||||
},
|
},
|
||||||
completion: { _ in }
|
completion: { _ in }
|
||||||
@@ -276,10 +281,11 @@ extension Classic.ColorsDemo {
|
|||||||
private dynamic func brightnessSliderValueDidChange(_ sender: UISlider) {
|
private dynamic func brightnessSliderValueDidChange(_ sender: UISlider) {
|
||||||
|
|
||||||
let value = sender.value
|
let value = sender.value
|
||||||
|
let persistentID = self.palette.object?.persistentID()
|
||||||
Classic.ColorsDemo.dataStack.perform(
|
Classic.ColorsDemo.dataStack.perform(
|
||||||
asynchronous: { [weak self] (transaction) in
|
asynchronous: { (transaction) in
|
||||||
|
|
||||||
let palette = transaction.edit(self?.palette.object)
|
let palette = transaction.edit(persistentID)
|
||||||
palette?.brightness = value
|
palette?.brightness = value
|
||||||
},
|
},
|
||||||
completion: { _ in }
|
completion: { _ in }
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ extension Modern.ColorsDemo {
|
|||||||
|
|
||||||
// MARK: - Modern.ColorsDemo.MainView
|
// MARK: - Modern.ColorsDemo.MainView
|
||||||
|
|
||||||
|
@MainActor
|
||||||
struct MainView<ListView: View, DetailView: View>: View {
|
struct MainView<ListView: View, DetailView: View>: View {
|
||||||
|
|
||||||
// MARK: Internal
|
// MARK: Internal
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ extension Modern.ColorsDemo.SwiftUI {
|
|||||||
|
|
||||||
// MARK: - Modern.ColorsDemo.SwiftUI.DetailView
|
// MARK: - Modern.ColorsDemo.SwiftUI.DetailView
|
||||||
|
|
||||||
|
@MainActor
|
||||||
struct DetailView: View {
|
struct DetailView: View {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -33,6 +34,7 @@ extension Modern.ColorsDemo.SwiftUI {
|
|||||||
|
|
||||||
init(_ palette: ObjectPublisher<Modern.ColorsDemo.Palette>) {
|
init(_ palette: ObjectPublisher<Modern.ColorsDemo.Palette>) {
|
||||||
|
|
||||||
|
let persistentID = palette.persistentID()
|
||||||
self._palette = .init(palette)
|
self._palette = .init(palette)
|
||||||
self._hue = Binding(
|
self._hue = Binding(
|
||||||
get: { palette.hue ?? 0 },
|
get: { palette.hue ?? 0 },
|
||||||
@@ -41,7 +43,7 @@ extension Modern.ColorsDemo.SwiftUI {
|
|||||||
Modern.ColorsDemo.dataStack.perform(
|
Modern.ColorsDemo.dataStack.perform(
|
||||||
asynchronous: { (transaction) in
|
asynchronous: { (transaction) in
|
||||||
|
|
||||||
let palette = palette.asEditable(in: transaction)
|
let palette = persistentID.asEditable(in: transaction)
|
||||||
palette?.hue = percentage
|
palette?.hue = percentage
|
||||||
},
|
},
|
||||||
completion: { _ in }
|
completion: { _ in }
|
||||||
@@ -55,7 +57,7 @@ extension Modern.ColorsDemo.SwiftUI {
|
|||||||
Modern.ColorsDemo.dataStack.perform(
|
Modern.ColorsDemo.dataStack.perform(
|
||||||
asynchronous: { (transaction) in
|
asynchronous: { (transaction) in
|
||||||
|
|
||||||
let palette = palette.asEditable(in: transaction)
|
let palette = persistentID.asEditable(in: transaction)
|
||||||
palette?.saturation = percentage
|
palette?.saturation = percentage
|
||||||
},
|
},
|
||||||
completion: { _ in }
|
completion: { _ in }
|
||||||
@@ -69,7 +71,7 @@ extension Modern.ColorsDemo.SwiftUI {
|
|||||||
Modern.ColorsDemo.dataStack.perform(
|
Modern.ColorsDemo.dataStack.perform(
|
||||||
asynchronous: { (transaction) in
|
asynchronous: { (transaction) in
|
||||||
|
|
||||||
let palette = palette.asEditable(in: transaction)
|
let palette = persistentID.asEditable(in: transaction)
|
||||||
palette?.brightness = percentage
|
palette?.brightness = percentage
|
||||||
},
|
},
|
||||||
completion: { _ in }
|
completion: { _ in }
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ extension Modern.ColorsDemo.SwiftUI {
|
|||||||
|
|
||||||
// MARK: - Modern.ColorsDemo.SwiftUI.ItemView
|
// MARK: - Modern.ColorsDemo.SwiftUI.ItemView
|
||||||
|
|
||||||
|
@MainActor
|
||||||
struct ItemView: View {
|
struct ItemView: View {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ extension Modern.ColorsDemo.SwiftUI {
|
|||||||
|
|
||||||
// MARK: - Modern.ColorsDemo.SwiftUI.ListView
|
// MARK: - Modern.ColorsDemo.SwiftUI.ListView
|
||||||
|
|
||||||
|
@MainActor
|
||||||
struct ListView: View {
|
struct ListView: View {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -81,7 +82,7 @@ extension Modern.ColorsDemo.SwiftUI {
|
|||||||
Modern.ColorsDemo.dataStack.perform(
|
Modern.ColorsDemo.dataStack.perform(
|
||||||
asynchronous: { transaction in
|
asynchronous: { transaction in
|
||||||
|
|
||||||
transaction.delete(objectIDs: objectIDsToDelete)
|
transaction.delete(persistentIDs: objectIDsToDelete)
|
||||||
},
|
},
|
||||||
completion: { _ in }
|
completion: { _ in }
|
||||||
)
|
)
|
||||||
|
|||||||
+10
-9
@@ -92,7 +92,7 @@ extension Modern.ColorsDemo.UIKit {
|
|||||||
_ monitor: ObjectMonitor<Modern.ColorsDemo.Palette>,
|
_ monitor: ObjectMonitor<Modern.ColorsDemo.Palette>,
|
||||||
didUpdateObject object: sending Modern.ColorsDemo.Palette,
|
didUpdateObject object: sending Modern.ColorsDemo.Palette,
|
||||||
changedPersistentKeys: Set<KeyPathString>,
|
changedPersistentKeys: Set<KeyPathString>,
|
||||||
sourceIdentifier: Any?
|
sourceIdentifier: (any Sendable)?
|
||||||
) {
|
) {
|
||||||
|
|
||||||
MainActor.assumeIsolated {
|
MainActor.assumeIsolated {
|
||||||
@@ -253,10 +253,11 @@ extension Modern.ColorsDemo.UIKit {
|
|||||||
private dynamic func hueSliderValueDidChange(_ sender: UISlider) {
|
private dynamic func hueSliderValueDidChange(_ sender: UISlider) {
|
||||||
|
|
||||||
let value = sender.value
|
let value = sender.value
|
||||||
|
let paletteID = self.palette.object?.persistentID()
|
||||||
Modern.ColorsDemo.dataStack.perform(
|
Modern.ColorsDemo.dataStack.perform(
|
||||||
asynchronous: { [weak self] (transaction) in
|
asynchronous: { transaction in
|
||||||
|
|
||||||
let palette = transaction.edit(self?.palette.object)
|
let palette = transaction.edit(paletteID)
|
||||||
palette?.hue = value
|
palette?.hue = value
|
||||||
},
|
},
|
||||||
completion: { _ in }
|
completion: { _ in }
|
||||||
@@ -268,10 +269,11 @@ extension Modern.ColorsDemo.UIKit {
|
|||||||
private dynamic func saturationSliderValueDidChange(_ sender: UISlider) {
|
private dynamic func saturationSliderValueDidChange(_ sender: UISlider) {
|
||||||
|
|
||||||
let value = sender.value
|
let value = sender.value
|
||||||
|
let paletteID = self.palette.object?.persistentID()
|
||||||
Modern.ColorsDemo.dataStack.perform(
|
Modern.ColorsDemo.dataStack.perform(
|
||||||
asynchronous: { [weak self] (transaction) in
|
asynchronous: { transaction in
|
||||||
|
|
||||||
let palette = transaction.edit(self?.palette.object)
|
let palette = transaction.edit(paletteID)
|
||||||
palette?.saturation = value
|
palette?.saturation = value
|
||||||
},
|
},
|
||||||
completion: { _ in }
|
completion: { _ in }
|
||||||
@@ -283,10 +285,11 @@ extension Modern.ColorsDemo.UIKit {
|
|||||||
private dynamic func brightnessSliderValueDidChange(_ sender: UISlider) {
|
private dynamic func brightnessSliderValueDidChange(_ sender: UISlider) {
|
||||||
|
|
||||||
let value = sender.value
|
let value = sender.value
|
||||||
|
let paletteID = self.palette.object?.persistentID()
|
||||||
Modern.ColorsDemo.dataStack.perform(
|
Modern.ColorsDemo.dataStack.perform(
|
||||||
asynchronous: { [weak self] (transaction) in
|
asynchronous: { transaction in
|
||||||
|
|
||||||
let palette = transaction.edit(self?.palette.object)
|
let palette = transaction.edit(paletteID)
|
||||||
palette?.brightness = value
|
palette?.brightness = value
|
||||||
},
|
},
|
||||||
completion: { _ in }
|
completion: { _ in }
|
||||||
@@ -294,5 +297,3 @@ extension Modern.ColorsDemo.UIKit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -81,7 +81,7 @@ extension Modern.ColorsDemo.UIKit {
|
|||||||
self.dataStack.perform(
|
self.dataStack.perform(
|
||||||
asynchronous: { (transaction) in
|
asynchronous: { (transaction) in
|
||||||
|
|
||||||
transaction.delete(objectIDs: [itemID])
|
transaction.delete(itemID)
|
||||||
},
|
},
|
||||||
sourceIdentifier: Modern.ColorsDemo.TransactionSource.delete,
|
sourceIdentifier: Modern.ColorsDemo.TransactionSource.delete,
|
||||||
completion: { _ in }
|
completion: { _ in }
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ extension Modern.PlacemarksDemo {
|
|||||||
|
|
||||||
// MARK: - Modern.PlacemarksDemo.MainView
|
// MARK: - Modern.PlacemarksDemo.MainView
|
||||||
|
|
||||||
|
@MainActor
|
||||||
struct MainView: View {
|
struct MainView: View {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -21,10 +22,11 @@ extension Modern.PlacemarksDemo {
|
|||||||
*/
|
*/
|
||||||
private func demoAsynchronousTransaction(coordinate: CLLocationCoordinate2D) {
|
private func demoAsynchronousTransaction(coordinate: CLLocationCoordinate2D) {
|
||||||
|
|
||||||
|
let persistentID = self.$place?.persistentID()
|
||||||
Modern.PlacemarksDemo.dataStack.perform(
|
Modern.PlacemarksDemo.dataStack.perform(
|
||||||
asynchronous: { (transaction) in
|
asynchronous: { (transaction) in
|
||||||
|
|
||||||
let place = self.$place?.asEditable(in: transaction)
|
let place = persistentID?.asEditable(in: transaction)
|
||||||
place?.annotation = .init(coordinate: coordinate)
|
place?.annotation = .init(coordinate: coordinate)
|
||||||
},
|
},
|
||||||
completion: { _ in }
|
completion: { _ in }
|
||||||
@@ -107,7 +109,7 @@ extension Modern.PlacemarksDemo {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
let geocoded = await self.geocoder.geocode(place: place)
|
let geocoded = await self.geocoder.geocode(place: place)
|
||||||
guard self.place?.objectID() == place.objectID() else {
|
guard self.place?.persistentID() == place.persistentID() else {
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ extension Modern.PokedexDemo {
|
|||||||
|
|
||||||
// MARK: ImportableObject
|
// MARK: ImportableObject
|
||||||
|
|
||||||
typealias ImportSource = Dictionary<String, Any>
|
typealias ImportSource = Dictionary<String, any Sendable>
|
||||||
|
|
||||||
|
|
||||||
// MARK: ImportableUniqueObject
|
// MARK: ImportableUniqueObject
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ extension Modern.PokedexDemo {
|
|||||||
|
|
||||||
// MARK: - Modern.PokedexDemo.MainView
|
// MARK: - Modern.PokedexDemo.MainView
|
||||||
|
|
||||||
|
@MainActor
|
||||||
struct MainView<ListView: View>: View {
|
struct MainView<ListView: View>: View {
|
||||||
|
|
||||||
// MARK: Internal
|
// MARK: Internal
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ extension Modern.PokedexDemo {
|
|||||||
|
|
||||||
// MARK: ImportableObject
|
// 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 {
|
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
|
⭐️ 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 {
|
do {
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ extension Modern.PokedexDemo {
|
|||||||
}
|
}
|
||||||
catch {
|
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`
|
⭐️ Sample 2: Importing a single JSON data into an `ImportableUniqueObject` whose `ImportSource` is a JSON `Dictionary`
|
||||||
*/
|
*/
|
||||||
private static func importSpecies(
|
private static func importSpecies(
|
||||||
for detailsObjectID: NSManagedObjectID,
|
for detailsPersistentID: DynamicObjectID<Modern.PokedexDemo.Details>,
|
||||||
from data: Data
|
from data: Data
|
||||||
) async throws -> ObjectSnapshot<Modern.PokedexDemo.Species> {
|
) 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(
|
let json: Dictionary<String, Any> = try self.parseJSON(
|
||||||
try JSONSerialization.jsonObject(with: data, options: [])
|
try JSONSerialization.jsonObject(with: data, options: [])
|
||||||
)
|
|
||||||
guard
|
|
||||||
let species = try transaction.importUniqueObject(
|
|
||||||
Into<Modern.PokedexDemo.Species>(),
|
|
||||||
source: json
|
|
||||||
)
|
)
|
||||||
else {
|
guard
|
||||||
|
let species = try transaction.importUniqueObject(
|
||||||
|
Into<Modern.PokedexDemo.Species>(),
|
||||||
|
source: .init(json: json)
|
||||||
|
)
|
||||||
|
else {
|
||||||
|
|
||||||
throw Modern.PokedexDemo.Service.Error.unexpected
|
throw Modern.PokedexDemo.Service.Error.unexpected
|
||||||
}
|
}
|
||||||
transaction
|
transaction
|
||||||
.edit(Into<Modern.PokedexDemo.Details>(), detailsObjectID)?
|
.edit(Into<Modern.PokedexDemo.Details>(), detailsPersistentID)?
|
||||||
.species = species
|
.species = species
|
||||||
return species.objectID()
|
return species.persistentID()
|
||||||
}
|
}
|
||||||
guard
|
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()
|
let snapshot = species.asSnapshot()
|
||||||
else {
|
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
|
⭐️ Sample 3: Importing a list of JSON data into `ImportableUniqueObject`s whose `ImportSource` are JSON `Dictionary`s
|
||||||
*/
|
*/
|
||||||
private static func importForms(
|
private static func importForms(
|
||||||
for detailsObjectID: NSManagedObjectID,
|
for detailsPersistentID: DynamicObjectID<Modern.PokedexDemo.Details>,
|
||||||
from dataArray: [Data]
|
from dataArray: [Data]
|
||||||
) async throws {
|
) async throws {
|
||||||
|
|
||||||
@@ -110,13 +110,13 @@ extension Modern.PokedexDemo {
|
|||||||
throw Modern.PokedexDemo.Service.Error.unexpected
|
throw Modern.PokedexDemo.Service.Error.unexpected
|
||||||
}
|
}
|
||||||
transaction
|
transaction
|
||||||
.edit(Into<Modern.PokedexDemo.Details>(), detailsObjectID)?
|
.edit(Into<Modern.PokedexDemo.Details>(), detailsPersistentID)?
|
||||||
.forms = forms
|
.forms = forms
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
|
|
||||||
throw self.mapError(error)
|
throw self.mapError(.init(error))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,8 +195,8 @@ extension Modern.PokedexDemo {
|
|||||||
if let species = details.$species?.snapshot {
|
if let species = details.$species?.snapshot {
|
||||||
|
|
||||||
self.fetchFormsIfNeeded(
|
self.fetchFormsIfNeeded(
|
||||||
key: species.$id,
|
key: String(species.$id),
|
||||||
detailsObjectID: details.objectID(),
|
detailsPersistentID: details.persistentID(),
|
||||||
species: species
|
species: species
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
@@ -207,7 +207,7 @@ extension Modern.PokedexDemo {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
let speciesURL = pokedexEntry.$speciesURL
|
let speciesURL = pokedexEntry.$speciesURL
|
||||||
let detailsObjectID = details.objectID()
|
let detailsPersistentID = details.persistentID()
|
||||||
self.detailTasks[key] = Task { [weak self] in
|
self.detailTasks[key] = Task { [weak self] in
|
||||||
|
|
||||||
guard let self else {
|
guard let self else {
|
||||||
@@ -220,7 +220,7 @@ extension Modern.PokedexDemo {
|
|||||||
}
|
}
|
||||||
await self.fetchSpecies(
|
await self.fetchSpecies(
|
||||||
key: key,
|
key: key,
|
||||||
detailsObjectID: detailsObjectID,
|
detailsPersistentID: detailsPersistentID,
|
||||||
speciesURL: speciesURL
|
speciesURL: speciesURL
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -289,7 +289,7 @@ extension Modern.PokedexDemo {
|
|||||||
|
|
||||||
private func fetchSpecies(
|
private func fetchSpecies(
|
||||||
key: String,
|
key: String,
|
||||||
detailsObjectID: NSManagedObjectID,
|
detailsPersistentID: DynamicObjectID<Modern.PokedexDemo.Details>,
|
||||||
speciesURL: URL
|
speciesURL: URL
|
||||||
) async {
|
) async {
|
||||||
|
|
||||||
@@ -299,7 +299,7 @@ extension Modern.PokedexDemo {
|
|||||||
try Task.checkCancellation()
|
try Task.checkCancellation()
|
||||||
|
|
||||||
let species = try await Self.importSpecies(
|
let species = try await Self.importSpecies(
|
||||||
for: detailsObjectID,
|
for: detailsPersistentID,
|
||||||
from: data
|
from: data
|
||||||
)
|
)
|
||||||
guard species.$details?.snapshot?.$forms.isEmpty == true else {
|
guard species.$details?.snapshot?.$forms.isEmpty == true else {
|
||||||
@@ -307,7 +307,7 @@ extension Modern.PokedexDemo {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
await self.fetchForms(
|
await self.fetchForms(
|
||||||
detailsObjectID: detailsObjectID,
|
detailsPersistentID: detailsPersistentID,
|
||||||
formsURLs: species.$formsURLs
|
formsURLs: species.$formsURLs
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -331,7 +331,7 @@ extension Modern.PokedexDemo {
|
|||||||
|
|
||||||
private func fetchFormsIfNeeded(
|
private func fetchFormsIfNeeded(
|
||||||
key: String,
|
key: String,
|
||||||
detailsObjectID: NSManagedObjectID,
|
detailsPersistentID: DynamicObjectID<Modern.PokedexDemo.Details>,
|
||||||
species: ObjectSnapshot<Modern.PokedexDemo.Species>
|
species: ObjectSnapshot<Modern.PokedexDemo.Species>
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@@ -356,14 +356,14 @@ extension Modern.PokedexDemo {
|
|||||||
self.detailTasks.removeValue(forKey: key)
|
self.detailTasks.removeValue(forKey: key)
|
||||||
}
|
}
|
||||||
await self.fetchForms(
|
await self.fetchForms(
|
||||||
detailsObjectID: detailsObjectID,
|
detailsPersistentID: detailsPersistentID,
|
||||||
formsURLs: formsURLs
|
formsURLs: formsURLs
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func fetchForms(
|
private func fetchForms(
|
||||||
detailsObjectID: NSManagedObjectID,
|
detailsPersistentID: DynamicObjectID<Modern.PokedexDemo.Details>,
|
||||||
formsURLs: [URL]
|
formsURLs: [URL]
|
||||||
) async {
|
) async {
|
||||||
|
|
||||||
@@ -378,7 +378,7 @@ extension Modern.PokedexDemo {
|
|||||||
dataArray.append(data)
|
dataArray.append(data)
|
||||||
}
|
}
|
||||||
try await Self.importForms(
|
try await Self.importForms(
|
||||||
for: detailsObjectID,
|
for: detailsPersistentID,
|
||||||
from: dataArray
|
from: dataArray
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -408,7 +408,7 @@ extension Modern.PokedexDemo {
|
|||||||
case networkError(URLError)
|
case networkError(URLError)
|
||||||
case parseError(expected: Any.Type, actual: Any.Type, file: String)
|
case parseError(expected: Any.Type, actual: Any.Type, file: String)
|
||||||
case saveError(CoreStoreError)
|
case saveError(CoreStoreError)
|
||||||
case otherError(Swift.Error)
|
case otherError(Swift::Error)
|
||||||
case unexpected
|
case unexpected
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,7 +65,10 @@ extension Modern.PokedexDemo {
|
|||||||
|
|
||||||
// MARK: ImportableObject
|
// MARK: ImportableObject
|
||||||
|
|
||||||
typealias ImportSource = Dictionary<String, Any>
|
struct ImportSource: @unchecked Sendable {
|
||||||
|
|
||||||
|
let json: Dictionary<String, Any>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// MARK: ImportableUniqueObject
|
// MARK: ImportableUniqueObject
|
||||||
@@ -82,14 +85,14 @@ extension Modern.PokedexDemo {
|
|||||||
|
|
||||||
static func uniqueID(from source: ImportSource, in transaction: BaseDataTransaction) throws -> UniqueIDType? {
|
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"])
|
return try Modern.PokedexDemo.Service.parseJSON(json["id"])
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(from source: ImportSource, in transaction: BaseDataTransaction) throws {
|
func update(from source: ImportSource, in transaction: BaseDataTransaction) throws {
|
||||||
|
|
||||||
typealias Service = Modern.PokedexDemo.Service
|
typealias Service = Modern.PokedexDemo.Service
|
||||||
let json = source
|
let json = source.json
|
||||||
|
|
||||||
self.name = try Service.parseJSON(json["name"])
|
self.name = try Service.parseJSON(json["name"])
|
||||||
self.weight = try Service.parseJSON(json["weight"])
|
self.weight = try Service.parseJSON(json["weight"])
|
||||||
|
|||||||
+20
-8
@@ -50,7 +50,7 @@ extension Modern.PokedexDemo.UIKit {
|
|||||||
fatalError()
|
fatalError()
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
isolated deinit {
|
||||||
|
|
||||||
self.listPublisher.removeObserver(self)
|
self.listPublisher.removeObserver(self)
|
||||||
}
|
}
|
||||||
@@ -94,13 +94,25 @@ extension Modern.PokedexDemo.UIKit {
|
|||||||
|
|
||||||
private func startObservingList() {
|
private func startObservingList() {
|
||||||
|
|
||||||
self.listPublisher.addObserver(self) { (listPublisher) in
|
self.listPublisher.addObserver(
|
||||||
|
self,
|
||||||
self.dataSource.apply(
|
notifyInitial: false,
|
||||||
listPublisher.snapshot,
|
{ [weak self] (listPublisher) in
|
||||||
animatingDifferences: true
|
|
||||||
)
|
let snapshot = listPublisher.snapshot
|
||||||
}
|
withMainActorImmediate {
|
||||||
|
|
||||||
|
guard let self else {
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.dataSource.apply(
|
||||||
|
snapshot,
|
||||||
|
animatingDifferences: true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
self.dataSource.apply(
|
self.dataSource.apply(
|
||||||
self.listPublisher.snapshot,
|
self.listPublisher.snapshot,
|
||||||
animatingDifferences: false
|
animatingDifferences: false
|
||||||
|
|||||||
@@ -13,52 +13,70 @@ extension Menu {
|
|||||||
// MARK: - Menu.MainView
|
// MARK: - Menu.MainView
|
||||||
|
|
||||||
struct MainView: View {
|
struct MainView: View {
|
||||||
|
|
||||||
@State
|
@Environment(\.horizontalSizeClass)
|
||||||
private var selection: Menu.Route?
|
private var horizontalSizeClass
|
||||||
|
|
||||||
|
|
||||||
// MARK: View
|
// MARK: View
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
|
||||||
NavigationSplitView(
|
if self.horizontalSizeClass == .compact {
|
||||||
sidebar: {
|
NavigationStack {
|
||||||
|
self.menuList
|
||||||
List(selection: self.$selection) {
|
}
|
||||||
|
}
|
||||||
ForEach(Menu.Section.allCases, id: \.self) { section in
|
else {
|
||||||
|
NavigationSplitView(
|
||||||
Section(section.rawValue) {
|
sidebar: {
|
||||||
|
self.menuList
|
||||||
ForEach(section.routes) { route in
|
},
|
||||||
|
detail: {
|
||||||
Menu.ItemView(
|
|
||||||
title: route.title,
|
|
||||||
subtitle: route.subtitle,
|
|
||||||
isEnabled: route.isEnabled
|
|
||||||
)
|
|
||||||
.tag(route as Menu.Route?)
|
|
||||||
.disabled(!route.isEnabled)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.navigationTitle("CoreStore Demos")
|
|
||||||
.listStyle(.sidebar)
|
|
||||||
},
|
|
||||||
detail: {
|
|
||||||
|
|
||||||
if let selection = self.selection {
|
|
||||||
|
|
||||||
selection.destination
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
|
|
||||||
Menu.PlaceholderView()
|
Menu.PlaceholderView()
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: Private
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var menuList: some View {
|
||||||
|
List {
|
||||||
|
|
||||||
|
ForEach(Menu.Section.allCases, id: \.self) { section in
|
||||||
|
|
||||||
|
SwiftUI.Section(
|
||||||
|
content: {
|
||||||
|
|
||||||
|
ForEach(section.routes) { route in
|
||||||
|
|
||||||
|
NavigationLink(
|
||||||
|
destination: {
|
||||||
|
route.destination
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
Menu.ItemView(
|
||||||
|
title: route.title,
|
||||||
|
subtitle: route.subtitle,
|
||||||
|
isEnabled: route.isEnabled
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.disabled(!route.isEnabled)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
|
||||||
|
Text(section.rawValue)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
|
.navigationTitle("CoreStore Demos")
|
||||||
|
.listStyle(.sidebar)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
//
|
||||||
|
// WithMainActorImmediate.swift
|
||||||
|
// Demo
|
||||||
|
//
|
||||||
|
// Created by John Estropia on 2026/07/21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - withMainActorImmediate
|
||||||
|
|
||||||
|
func withMainActorImmediate(
|
||||||
|
_ task: @MainActor @Sendable @escaping () -> Void
|
||||||
|
) {
|
||||||
|
|
||||||
|
if #available(iOS 26.0, *) {
|
||||||
|
Task.immediate(operation: task)
|
||||||
|
}
|
||||||
|
else if Thread.isMainThread {
|
||||||
|
|
||||||
|
MainActor.assumeIsolated {
|
||||||
|
|
||||||
|
task()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
Task.init(operation: task)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2201,6 +2201,23 @@ var body: some View {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
The `placeholder:` overload also works with `keyPath:` projections:
|
||||||
|
```swift
|
||||||
|
let person: ObjectPublisher<Person>
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ObjectReader(
|
||||||
|
self.person,
|
||||||
|
keyPath: \.fullName,
|
||||||
|
content: { fullName in
|
||||||
|
Text("Name: \(fullName)")
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
Text("Record not found")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### SwiftUI Property Wrappers
|
### SwiftUI Property Wrappers
|
||||||
|
|
||||||
@@ -2234,7 +2251,8 @@ If a `ListPublisher` instance is not available yet, the fetch can be done inline
|
|||||||
From<Person>()
|
From<Person>()
|
||||||
.sectionBy(\.age)
|
.sectionBy(\.age)
|
||||||
.where(\.isMember == true)
|
.where(\.isMember == true)
|
||||||
.orderBy(.ascending(\.lastName))
|
.orderBy(.ascending(\.lastName)),
|
||||||
|
in: Globals.dataStack
|
||||||
)
|
)
|
||||||
var people: ListSnapshot<Person>
|
var people: ListSnapshot<Person>
|
||||||
|
|
||||||
@@ -2509,4 +2527,3 @@ I'd love to hear about apps using CoreStore. Send me a message and I'll welcome
|
|||||||
|
|
||||||
# License
|
# License
|
||||||
CoreStore is released under an MIT license. See the [LICENSE](https://raw.githubusercontent.com/JohnEstropia/CoreStore/master/LICENSE) file for more information
|
CoreStore is released under an MIT license. See the [LICENSE](https://raw.githubusercontent.com/JohnEstropia/CoreStore/master/LICENSE) file for more information
|
||||||
|
|
||||||
|
|||||||
@@ -80,19 +80,21 @@ public nonisolated final class AsynchronousDataTransaction: BaseDataTransaction
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Returns an editable proxy of a specified `NSManagedObject` or `CoreStoreObject`.
|
Returns an editable proxy of the object with the specified `DynamicObjectID`.
|
||||||
|
|
||||||
- parameter persistentID: the `DynamicObjectID` pertaining ot the `NSManagedObject` or `CoreStoreObject` type to be edited
|
- parameter into: an `Into` clause specifying the entity type
|
||||||
|
- parameter persistentID: the `DynamicObjectID` for the object to be edited
|
||||||
- returns: an editable proxy for the specified `NSManagedObject` or `CoreStoreObject`.
|
- returns: an editable proxy for the specified `NSManagedObject` or `CoreStoreObject`.
|
||||||
*/
|
*/
|
||||||
public override func edit<O: DynamicObject>(
|
public override func edit<O>(
|
||||||
_ persistentID: DynamicObjectID<O>?
|
_ persistentID: DynamicObjectID<O>?
|
||||||
) -> O? {
|
) -> O? {
|
||||||
|
|
||||||
Internals.assert(
|
Internals.assert(
|
||||||
!self.isCommitted,
|
!self.isCommitted,
|
||||||
"Attempted to update an entity for \(Internals.typeName(persistentID)) from an already committed \(Internals.typeName(self))."
|
"Attempted to update an entity of type \(Internals.typeName(persistentID)) from an already committed \(Internals.typeName(self))."
|
||||||
)
|
)
|
||||||
|
|
||||||
return super.edit(persistentID)
|
return super.edit(persistentID)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,6 +116,26 @@ public nonisolated final class AsynchronousDataTransaction: BaseDataTransaction
|
|||||||
return super.edit(object)
|
return super.edit(object)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Returns an editable proxy of the object with the specified `DynamicObjectID`.
|
||||||
|
|
||||||
|
- parameter into: an `Into` clause specifying the entity type
|
||||||
|
- parameter persistentID: the `DynamicObjectID` for the object to be edited
|
||||||
|
- returns: an editable proxy for the specified `NSManagedObject` or `CoreStoreObject`.
|
||||||
|
*/
|
||||||
|
public override func edit<O>(
|
||||||
|
_ into: Into<O>,
|
||||||
|
_ persistentID: DynamicObjectID<O>
|
||||||
|
) -> O? {
|
||||||
|
|
||||||
|
Internals.assert(
|
||||||
|
!self.isCommitted,
|
||||||
|
"Attempted to update an entity of type \(Internals.typeName(into.entityClass)) from an already committed \(Internals.typeName(self))."
|
||||||
|
)
|
||||||
|
|
||||||
|
return super.edit(into, persistentID)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Returns an editable proxy of the object with the specified `NSManagedObjectID`.
|
Returns an editable proxy of the object with the specified `NSManagedObjectID`.
|
||||||
|
|
||||||
@@ -133,6 +155,23 @@ public nonisolated final class AsynchronousDataTransaction: BaseDataTransaction
|
|||||||
|
|
||||||
return super.edit(into, objectID)
|
return super.edit(into, objectID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Deletes the objects with the specified `NSManagedObjectID`s.
|
||||||
|
|
||||||
|
- parameter objectIDs: the `NSManagedObjectID`s of the objects to delete
|
||||||
|
*/
|
||||||
|
public override func delete<O: DynamicObject, S: Sequence>(
|
||||||
|
persistentIDs: S
|
||||||
|
) where S.Iterator.Element == DynamicObjectID<O> {
|
||||||
|
|
||||||
|
Internals.assert(
|
||||||
|
!self.isCommitted,
|
||||||
|
"Attempted to delete an entities from an already committed \(Internals.typeName(self))."
|
||||||
|
)
|
||||||
|
|
||||||
|
super.delete(persistentIDs: persistentIDs)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Deletes the objects with the specified `NSManagedObjectID`s.
|
Deletes the objects with the specified `NSManagedObjectID`s.
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ extension BaseDataTransaction {
|
|||||||
public func importObject<O: ImportableObject>(
|
public func importObject<O: ImportableObject>(
|
||||||
_ into: Into<O>,
|
_ into: Into<O>,
|
||||||
source: O.ImportSource
|
source: O.ImportSource
|
||||||
) throws(any Swift.Error) -> O? {
|
) throws(any Swift::Error) -> O? {
|
||||||
|
|
||||||
Internals.assert(
|
Internals.assert(
|
||||||
self.isRunningInAllowedQueue(),
|
self.isRunningInAllowedQueue(),
|
||||||
@@ -73,7 +73,7 @@ extension BaseDataTransaction {
|
|||||||
public func importObject<O: ImportableObject>(
|
public func importObject<O: ImportableObject>(
|
||||||
_ object: O,
|
_ object: O,
|
||||||
source: O.ImportSource
|
source: O.ImportSource
|
||||||
) throws(any Swift.Error) {
|
) throws(any Swift::Error) {
|
||||||
|
|
||||||
Internals.assert(
|
Internals.assert(
|
||||||
self.isRunningInAllowedQueue(),
|
self.isRunningInAllowedQueue(),
|
||||||
@@ -102,7 +102,7 @@ extension BaseDataTransaction {
|
|||||||
public func importObjects<O: ImportableObject, S: Sequence>(
|
public func importObjects<O: ImportableObject, S: Sequence>(
|
||||||
_ into: Into<O>,
|
_ into: Into<O>,
|
||||||
sourceArray: S
|
sourceArray: S
|
||||||
) throws(any Swift.Error) -> [O] where S.Iterator.Element == O.ImportSource {
|
) throws(any Swift::Error) -> [O] where S.Iterator.Element == O.ImportSource {
|
||||||
|
|
||||||
Internals.assert(
|
Internals.assert(
|
||||||
self.isRunningInAllowedQueue(),
|
self.isRunningInAllowedQueue(),
|
||||||
@@ -139,7 +139,7 @@ extension BaseDataTransaction {
|
|||||||
public func importUniqueObject<O: ImportableUniqueObject>(
|
public func importUniqueObject<O: ImportableUniqueObject>(
|
||||||
_ into: Into<O>,
|
_ into: Into<O>,
|
||||||
source: O.ImportSource
|
source: O.ImportSource
|
||||||
) throws(any Swift.Error) -> O? {
|
) throws(any Swift::Error) -> O? {
|
||||||
|
|
||||||
Internals.assert(
|
Internals.assert(
|
||||||
self.isRunningInAllowedQueue(),
|
self.isRunningInAllowedQueue(),
|
||||||
@@ -194,8 +194,8 @@ extension BaseDataTransaction {
|
|||||||
sourceArray: S,
|
sourceArray: S,
|
||||||
preProcess: @escaping (
|
preProcess: @escaping (
|
||||||
_ mapping: [O.UniqueIDType: O.ImportSource]
|
_ mapping: [O.UniqueIDType: O.ImportSource]
|
||||||
) throws(any Swift.Error) -> [O.UniqueIDType: O.ImportSource] = { $0 }
|
) throws(any Swift::Error) -> [O.UniqueIDType: O.ImportSource] = { $0 }
|
||||||
) throws(any Swift.Error) -> [O] where S.Iterator.Element == O.ImportSource {
|
) throws(any Swift::Error) -> [O] where S.Iterator.Element == O.ImportSource {
|
||||||
|
|
||||||
Internals.assert(
|
Internals.assert(
|
||||||
self.isRunningInAllowedQueue(),
|
self.isRunningInAllowedQueue(),
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ public /*abstract*/ class BaseDataTransaction {
|
|||||||
- parameter persistentID: the `DynamicObjectID` pertaining ot the `NSManagedObject` or `CoreStoreObject` type to be edited
|
- parameter persistentID: the `DynamicObjectID` pertaining ot the `NSManagedObject` or `CoreStoreObject` type to be edited
|
||||||
- returns: an editable proxy for the specified `NSManagedObject` or `CoreStoreObject`.
|
- returns: an editable proxy for the specified `NSManagedObject` or `CoreStoreObject`.
|
||||||
*/
|
*/
|
||||||
public func edit<O: DynamicObject>(
|
public func edit<O>(
|
||||||
_ persistentID: DynamicObjectID<O>?
|
_ persistentID: DynamicObjectID<O>?
|
||||||
) -> O? {
|
) -> O? {
|
||||||
|
|
||||||
@@ -157,6 +157,30 @@ public /*abstract*/ class BaseDataTransaction {
|
|||||||
return self.context.fetchExisting(object)
|
return self.context.fetchExisting(object)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Returns an editable proxy of the object with the specified `DynamicObjectID`.
|
||||||
|
|
||||||
|
- parameter into: an `Into` clause specifying the entity type
|
||||||
|
- parameter persistentID: the `DynamicObjectID` for the object to be edited
|
||||||
|
- returns: an editable proxy for the specified `NSManagedObject` or `CoreStoreObject`.
|
||||||
|
*/
|
||||||
|
public func edit<O>(
|
||||||
|
_ into: Into<O>,
|
||||||
|
_ persistentID: DynamicObjectID<O>
|
||||||
|
) -> O? {
|
||||||
|
|
||||||
|
Internals.assert(
|
||||||
|
self.isRunningInAllowedQueue(),
|
||||||
|
"Attempted to update an entity of type \(Internals.typeName(into.entityClass)) outside its designated queue."
|
||||||
|
)
|
||||||
|
Internals.assert(
|
||||||
|
into.inferStoreIfPossible
|
||||||
|
|| (into.configuration ?? DataStack.defaultConfigurationName) == persistentID.managedObjectID.persistentStore?.configurationName,
|
||||||
|
"Attempted to update an entity of type \(Internals.typeName(into.entityClass)) but the specified persistent store do not match the `NSManagedObjectID`."
|
||||||
|
)
|
||||||
|
return self.fetchExisting(persistentID)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Returns an editable proxy of the object with the specified `NSManagedObjectID`.
|
Returns an editable proxy of the object with the specified `NSManagedObjectID`.
|
||||||
|
|
||||||
@@ -180,6 +204,26 @@ public /*abstract*/ class BaseDataTransaction {
|
|||||||
)
|
)
|
||||||
return self.fetchExisting(objectID)
|
return self.fetchExisting(objectID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Deletes the objects with the specified `DynamicObjectID`s.
|
||||||
|
|
||||||
|
- parameter persistentIDs: the `DynamicObjectID`s of the objects to delete
|
||||||
|
*/
|
||||||
|
public func delete<O: DynamicObject, S: Sequence>(
|
||||||
|
persistentIDs: S
|
||||||
|
) where S.Iterator.Element == DynamicObjectID<O> {
|
||||||
|
|
||||||
|
Internals.assert(
|
||||||
|
self.isRunningInAllowedQueue(),
|
||||||
|
"Attempted to delete an entity outside its designated queue."
|
||||||
|
)
|
||||||
|
let context = self.context
|
||||||
|
persistentIDs.forEach {
|
||||||
|
|
||||||
|
context.fetchExisting($0).map({ context.delete($0.cs_toRaw()) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Deletes the objects with the specified `NSManagedObjectID`s.
|
Deletes the objects with the specified `NSManagedObjectID`s.
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import Foundation
|
|||||||
/**
|
/**
|
||||||
All errors thrown from CoreStore are expressed in `CoreStoreError` enum values.
|
All errors thrown from CoreStore are expressed in `CoreStoreError` enum values.
|
||||||
*/
|
*/
|
||||||
public enum CoreStoreError: Error, CustomNSError, Hashable, Sendable {
|
public enum CoreStoreError: Swift::Error, CustomNSError, Hashable, Sendable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
A failure occured because of an unknown error.
|
A failure occured because of an unknown error.
|
||||||
@@ -67,7 +67,7 @@ public enum CoreStoreError: Error, CustomNSError, Hashable, Sendable {
|
|||||||
/**
|
/**
|
||||||
The transaction was terminated by a user-thrown `Error`.
|
The transaction was terminated by a user-thrown `Error`.
|
||||||
*/
|
*/
|
||||||
case userError(error: Error)
|
case userError(error: Swift::Error)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
The transaction was cancelled by the user.
|
The transaction was cancelled by the user.
|
||||||
@@ -82,7 +82,7 @@ public enum CoreStoreError: Error, CustomNSError, Hashable, Sendable {
|
|||||||
/**
|
/**
|
||||||
Casts any `Error` to a known `CoreStoreError`, or wraps it in `CoreStoreError.internalError(NSError:)`.
|
Casts any `Error` to a known `CoreStoreError`, or wraps it in `CoreStoreError.internalError(NSError:)`.
|
||||||
*/
|
*/
|
||||||
public init(_ error: Error?) {
|
public init(_ error: Swift::Error?) {
|
||||||
|
|
||||||
guard let error = error else {
|
guard let error = error else {
|
||||||
|
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ public final class CustomSchemaMappingProvider: Hashable, SchemaMappingProvider
|
|||||||
public typealias Transformer = @Sendable (
|
public typealias Transformer = @Sendable (
|
||||||
_ sourceObject: UnsafeSourceObject,
|
_ sourceObject: UnsafeSourceObject,
|
||||||
_ createDestinationObject: () -> UnsafeDestinationObject
|
_ createDestinationObject: () -> UnsafeDestinationObject
|
||||||
) throws(any Swift.Error) -> Void
|
) throws(any Swift::Error) -> Void
|
||||||
|
|
||||||
/**
|
/**
|
||||||
The `CustomMapping.inferredTransformation` method can be used directly as the `transformer` if the changes can be inferred (i.e. lightweight).
|
The `CustomMapping.inferredTransformation` method can be used directly as the `transformer` if the changes can be inferred (i.e. lightweight).
|
||||||
@@ -116,7 +116,7 @@ public final class CustomSchemaMappingProvider: Hashable, SchemaMappingProvider
|
|||||||
public static func inferredTransformation(
|
public static func inferredTransformation(
|
||||||
_ sourceObject: UnsafeSourceObject,
|
_ sourceObject: UnsafeSourceObject,
|
||||||
_ createDestinationObject: () -> UnsafeDestinationObject
|
_ createDestinationObject: () -> UnsafeDestinationObject
|
||||||
) throws(any Swift.Error) {
|
) throws(any Swift::Error) {
|
||||||
|
|
||||||
let destinationObject = createDestinationObject()
|
let destinationObject = createDestinationObject()
|
||||||
destinationObject.enumerateAttributes { (attribute, sourceAttribute) in
|
destinationObject.enumerateAttributes { (attribute, sourceAttribute) in
|
||||||
@@ -556,7 +556,7 @@ public final class CustomSchemaMappingProvider: Hashable, SchemaMappingProvider
|
|||||||
forSource sInstance: NSManagedObject,
|
forSource sInstance: NSManagedObject,
|
||||||
in mapping: NSEntityMapping,
|
in mapping: NSEntityMapping,
|
||||||
manager: NSMigrationManager
|
manager: NSMigrationManager
|
||||||
) throws(any Swift.Error) {
|
) throws(any Swift::Error) {
|
||||||
|
|
||||||
let userInfo = mapping.userInfo!
|
let userInfo = mapping.userInfo!
|
||||||
let transformer = userInfo[CustomEntityMigrationPolicy.UserInfoKey.transformer]! as! CustomMapping.Transformer
|
let transformer = userInfo[CustomEntityMigrationPolicy.UserInfoKey.transformer]! as! CustomMapping.Transformer
|
||||||
@@ -588,7 +588,7 @@ public final class CustomSchemaMappingProvider: Hashable, SchemaMappingProvider
|
|||||||
forDestination dInstance: NSManagedObject,
|
forDestination dInstance: NSManagedObject,
|
||||||
in mapping: NSEntityMapping,
|
in mapping: NSEntityMapping,
|
||||||
manager: NSMigrationManager
|
manager: NSMigrationManager
|
||||||
) throws(any Swift.Error) {
|
) throws(any Swift::Error) {
|
||||||
|
|
||||||
try super.createRelationships(forDestination: dInstance, in: mapping, manager: manager)
|
try super.createRelationships(forDestination: dInstance, in: mapping, manager: manager)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ extension DataStack.AsyncNamespace {
|
|||||||
*/
|
*/
|
||||||
public func addStorage<T: StorageInterface>(
|
public func addStorage<T: StorageInterface>(
|
||||||
_ storage: T
|
_ storage: T
|
||||||
) async throws(any Swift.Error) -> T {
|
) async throws(any Swift::Error) -> T {
|
||||||
|
|
||||||
return try await Internals.withCheckedThrowingContinuation { continuation in
|
return try await Internals.withCheckedThrowingContinuation { continuation in
|
||||||
|
|
||||||
@@ -118,7 +118,7 @@ extension DataStack.AsyncNamespace {
|
|||||||
*/
|
*/
|
||||||
public func addStorage<T>(
|
public func addStorage<T>(
|
||||||
_ storage: T
|
_ storage: T
|
||||||
) -> AsyncThrowingStream<MigrationProgress<T>, any Swift.Error> {
|
) -> AsyncThrowingStream<MigrationProgress<T>, any Swift::Error> {
|
||||||
|
|
||||||
return .init(
|
return .init(
|
||||||
bufferingPolicy: .unbounded,
|
bufferingPolicy: .unbounded,
|
||||||
@@ -184,7 +184,7 @@ extension DataStack.AsyncNamespace {
|
|||||||
public func importObject<O: DynamicObject & ImportableObject>(
|
public func importObject<O: DynamicObject & ImportableObject>(
|
||||||
_ into: Into<O>,
|
_ into: Into<O>,
|
||||||
source: O.ImportSource
|
source: O.ImportSource
|
||||||
) async throws(any Swift.Error) -> O? {
|
) async throws(any Swift::Error) -> O? {
|
||||||
|
|
||||||
return try await Internals.withCheckedThrowingContinuation { continuation in
|
return try await Internals.withCheckedThrowingContinuation { continuation in
|
||||||
|
|
||||||
@@ -226,7 +226,7 @@ extension DataStack.AsyncNamespace {
|
|||||||
public func importObject<O: DynamicObject & ImportableObject>(
|
public func importObject<O: DynamicObject & ImportableObject>(
|
||||||
_ object: O,
|
_ object: O,
|
||||||
source: O.ImportSource
|
source: O.ImportSource
|
||||||
) async throws(any Swift.Error) -> O? {
|
) async throws(any Swift::Error) -> O? {
|
||||||
|
|
||||||
nonisolated(unsafe) let object = object
|
nonisolated(unsafe) let object = object
|
||||||
return try await Internals.withCheckedThrowingContinuation { continuation in
|
return try await Internals.withCheckedThrowingContinuation { continuation in
|
||||||
@@ -274,7 +274,7 @@ extension DataStack.AsyncNamespace {
|
|||||||
public func importUniqueObject<O: DynamicObject & ImportableUniqueObject>(
|
public func importUniqueObject<O: DynamicObject & ImportableUniqueObject>(
|
||||||
_ into: Into<O>,
|
_ into: Into<O>,
|
||||||
source: O.ImportSource
|
source: O.ImportSource
|
||||||
) async throws(any Swift.Error) -> O? {
|
) async throws(any Swift::Error) -> O? {
|
||||||
|
|
||||||
return try await Internals.withCheckedThrowingContinuation { continuation in
|
return try await Internals.withCheckedThrowingContinuation { continuation in
|
||||||
|
|
||||||
@@ -324,8 +324,8 @@ extension DataStack.AsyncNamespace {
|
|||||||
sourceArray: S,
|
sourceArray: S,
|
||||||
preProcess: @escaping @Sendable (
|
preProcess: @escaping @Sendable (
|
||||||
_ mapping: [O.UniqueIDType: O.ImportSource]
|
_ mapping: [O.UniqueIDType: O.ImportSource]
|
||||||
) throws(any Swift.Error) -> [O.UniqueIDType: O.ImportSource] = { $0 }
|
) throws(any Swift::Error) -> [O.UniqueIDType: O.ImportSource] = { $0 }
|
||||||
) async throws(any Swift.Error) -> [O]
|
) async throws(any Swift::Error) -> [O]
|
||||||
where S.Iterator.Element == O.ImportSource {
|
where S.Iterator.Element == O.ImportSource {
|
||||||
|
|
||||||
return try await Internals.withCheckedThrowingContinuation { continuation in
|
return try await Internals.withCheckedThrowingContinuation { continuation in
|
||||||
@@ -374,8 +374,8 @@ extension DataStack.AsyncNamespace {
|
|||||||
- throws: A `CoreStoreError` value indicating the failure reason
|
- throws: A `CoreStoreError` value indicating the failure reason
|
||||||
*/
|
*/
|
||||||
public func perform<Output: Sendable>(
|
public func perform<Output: Sendable>(
|
||||||
_ asynchronous: @escaping @Sendable (AsynchronousDataTransaction) throws(any Swift.Error) -> Output
|
_ asynchronous: @escaping @Sendable (AsynchronousDataTransaction) throws(any Swift::Error) -> Output
|
||||||
) async throws(any Swift.Error) -> Output {
|
) async throws(any Swift::Error) -> Output {
|
||||||
|
|
||||||
return try await Internals.withCheckedThrowingContinuation { continuation in
|
return try await Internals.withCheckedThrowingContinuation { continuation in
|
||||||
|
|
||||||
|
|||||||
@@ -326,7 +326,7 @@ extension DataStack.ReactiveNamespace {
|
|||||||
sourceArray: S,
|
sourceArray: S,
|
||||||
preProcess: @escaping @Sendable (
|
preProcess: @escaping @Sendable (
|
||||||
_ mapping: [O.UniqueIDType: O.ImportSource]
|
_ mapping: [O.UniqueIDType: O.ImportSource]
|
||||||
) throws(any Swift.Error) -> [O.UniqueIDType: O.ImportSource] = { $0 }
|
) throws(any Swift::Error) -> [O.UniqueIDType: O.ImportSource] = { $0 }
|
||||||
) -> Future<[O], CoreStoreError> where S.Iterator.Element == O.ImportSource {
|
) -> Future<[O], CoreStoreError> where S.Iterator.Element == O.ImportSource {
|
||||||
|
|
||||||
return .init { (promise) in
|
return .init { (promise) in
|
||||||
@@ -379,7 +379,7 @@ extension DataStack.ReactiveNamespace {
|
|||||||
public func perform<Output: Sendable>(
|
public func perform<Output: Sendable>(
|
||||||
_ asynchronous: @escaping @Sendable (
|
_ asynchronous: @escaping @Sendable (
|
||||||
_ transaction: AsynchronousDataTransaction
|
_ transaction: AsynchronousDataTransaction
|
||||||
) throws(any Swift.Error) -> Output
|
) throws(any Swift::Error) -> Output
|
||||||
) -> Future<Output, CoreStoreError> {
|
) -> Future<Output, CoreStoreError> {
|
||||||
|
|
||||||
return .init { (promise) in
|
return .init { (promise) in
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ extension DataStack {
|
|||||||
public func perform<T: Sendable>(
|
public func perform<T: Sendable>(
|
||||||
asynchronous task: @escaping @Sendable (
|
asynchronous task: @escaping @Sendable (
|
||||||
_ transaction: AsynchronousDataTransaction
|
_ transaction: AsynchronousDataTransaction
|
||||||
) throws(any Swift.Error) -> T,
|
) throws(any Swift::Error) -> T,
|
||||||
sourceIdentifier: (any Sendable)? = nil,
|
sourceIdentifier: (any Sendable)? = nil,
|
||||||
completion: @escaping @MainActor @Sendable (AsynchronousDataTransaction.Result<T>) -> Void
|
completion: @escaping @MainActor @Sendable (AsynchronousDataTransaction.Result<T>) -> Void
|
||||||
) {
|
) {
|
||||||
@@ -65,7 +65,7 @@ extension DataStack {
|
|||||||
public func perform<T>(
|
public func perform<T>(
|
||||||
asynchronous task: @escaping @Sendable (
|
asynchronous task: @escaping @Sendable (
|
||||||
_ transaction: AsynchronousDataTransaction
|
_ transaction: AsynchronousDataTransaction
|
||||||
) throws(any Swift.Error) -> T,
|
) throws(any Swift::Error) -> T,
|
||||||
sourceIdentifier: (any Sendable)? = nil,
|
sourceIdentifier: (any Sendable)? = nil,
|
||||||
success: @escaping @MainActor @Sendable (sending T) -> Void,
|
success: @escaping @MainActor @Sendable (sending T) -> Void,
|
||||||
failure: @escaping @MainActor @Sendable (CoreStoreError) -> Void
|
failure: @escaping @MainActor @Sendable (CoreStoreError) -> Void
|
||||||
@@ -123,7 +123,7 @@ extension DataStack {
|
|||||||
public func perform<T>(
|
public func perform<T>(
|
||||||
synchronous task: (
|
synchronous task: (
|
||||||
_ transaction: SynchronousDataTransaction
|
_ transaction: SynchronousDataTransaction
|
||||||
) throws(any Swift.Error) -> T,
|
) throws(any Swift::Error) -> T,
|
||||||
waitForAllObservers: Bool = true,
|
waitForAllObservers: Bool = true,
|
||||||
sourceIdentifier: (any Sendable)? = nil
|
sourceIdentifier: (any Sendable)? = nil
|
||||||
) throws(CoreStoreError) -> T {
|
) throws(CoreStoreError) -> T {
|
||||||
|
|||||||
@@ -535,7 +535,7 @@ public final class DataStack: Equatable, Sendable {
|
|||||||
_ storage: StorageInterface,
|
_ storage: StorageInterface,
|
||||||
finalURL: URL?,
|
finalURL: URL?,
|
||||||
finalStoreOptions: [AnyHashable: Any]?
|
finalStoreOptions: [AnyHashable: Any]?
|
||||||
) throws(any Swift.Error) -> NSPersistentStore {
|
) throws(any Swift::Error) -> NSPersistentStore {
|
||||||
|
|
||||||
let persistentStore = try self.coordinator.addPersistentStore(
|
let persistentStore = try self.coordinator.addPersistentStore(
|
||||||
ofType: type(of: storage).storeType,
|
ofType: type(of: storage).storeType,
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ extension DiffableDataSource {
|
|||||||
itemForRepresentedObjectAt indexPath: IndexPath
|
itemForRepresentedObjectAt indexPath: IndexPath
|
||||||
) -> NSCollectionViewItem {
|
) -> NSCollectionViewItem {
|
||||||
|
|
||||||
guard let objectID = self.itemID(for: indexPath) else {
|
guard let objectID: NSManagedObjectID = self.itemID(for: indexPath) else {
|
||||||
|
|
||||||
Internals.abort("Object at \(Internals.typeName(IndexPath.self)) \(indexPath) already removed from list")
|
Internals.abort("Object at \(Internals.typeName(IndexPath.self)) \(indexPath) already removed from list")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ extension DispatchQueue {
|
|||||||
|
|
||||||
@nonobjc @inline(__always)
|
@nonobjc @inline(__always)
|
||||||
internal func cs_barrierSync<T>(
|
internal func cs_barrierSync<T>(
|
||||||
_ closure: () throws(any Swift.Error) -> T
|
_ closure: () throws(any Swift::Error) -> T
|
||||||
) rethrows -> T {
|
) rethrows -> T {
|
||||||
|
|
||||||
return try self.sync(flags: .barrier) { try autoreleasepool(invoking: closure) }
|
return try self.sync(flags: .barrier) { try autoreleasepool(invoking: closure) }
|
||||||
|
|||||||
@@ -142,7 +142,9 @@ extension NSManagedObject: DynamicObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@_spi(Internals)
|
@_spi(Internals)
|
||||||
public class func cs_fromRaw(object: NSManagedObject) -> Self {
|
public class func cs_fromRaw(
|
||||||
|
object: NSManagedObject
|
||||||
|
) -> Self {
|
||||||
|
|
||||||
#if swift(>=5.9)
|
#if swift(>=5.9)
|
||||||
return unsafeDowncast(object, to: self)
|
return unsafeDowncast(object, to: self)
|
||||||
@@ -309,7 +311,9 @@ extension CoreStoreObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@_spi(Internals)
|
@_spi(Internals)
|
||||||
public class func cs_fromRaw(object: NSManagedObject) -> Self {
|
public class func cs_fromRaw(
|
||||||
|
object: NSManagedObject
|
||||||
|
) -> Self {
|
||||||
|
|
||||||
if let coreStoreObject = object.coreStoreObject {
|
if let coreStoreObject = object.coreStoreObject {
|
||||||
|
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ public protocol ImportableObject: DynamicObject {
|
|||||||
func didInsert(
|
func didInsert(
|
||||||
from source: ImportSource,
|
from source: ImportSource,
|
||||||
in transaction: BaseDataTransaction
|
in transaction: BaseDataTransaction
|
||||||
) throws(any Swift.Error)
|
) throws(any Swift::Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ public protocol ImportableUniqueObject: ImportableObject, Hashable {
|
|||||||
static func uniqueID(
|
static func uniqueID(
|
||||||
from source: ImportSource,
|
from source: ImportSource,
|
||||||
in transaction: BaseDataTransaction
|
in transaction: BaseDataTransaction
|
||||||
) throws(any Swift.Error) -> UniqueIDType?
|
) throws(any Swift::Error) -> UniqueIDType?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Implements the actual importing of data from `source`. This method is called just after the object is created and assigned its unique ID as returned from `uniqueID(from:in:)`. Implementers should pull values from `source` and assign them to the receiver's attributes. Note that throwing from this method will cause subsequent imports that are part of the same `importUniqueObjects(:sourceArray:)` call to be cancelled. The default implementation simply calls `update(from:in:)`.
|
Implements the actual importing of data from `source`. This method is called just after the object is created and assigned its unique ID as returned from `uniqueID(from:in:)`. Implementers should pull values from `source` and assign them to the receiver's attributes. Note that throwing from this method will cause subsequent imports that are part of the same `importUniqueObjects(:sourceArray:)` call to be cancelled. The default implementation simply calls `update(from:in:)`.
|
||||||
@@ -116,7 +116,7 @@ public protocol ImportableUniqueObject: ImportableObject, Hashable {
|
|||||||
func didInsert(
|
func didInsert(
|
||||||
from source: ImportSource,
|
from source: ImportSource,
|
||||||
in transaction: BaseDataTransaction
|
in transaction: BaseDataTransaction
|
||||||
) throws(any Swift.Error)
|
) throws(any Swift::Error)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Implements the actual importing of data from `source`. This method is called just after the existing object is fetched using its unique ID. Implementers should pull values from `source` and assign them to the receiver's attributes. Note that throwing from this method will cause subsequent imports that are part of the same `importUniqueObjects(:sourceArray:)` call to be cancelled.
|
Implements the actual importing of data from `source`. This method is called just after the existing object is fetched using its unique ID. Implementers should pull values from `source` and assign them to the receiver's attributes. Note that throwing from this method will cause subsequent imports that are part of the same `importUniqueObjects(:sourceArray:)` call to be cancelled.
|
||||||
@@ -127,7 +127,7 @@ public protocol ImportableUniqueObject: ImportableObject, Hashable {
|
|||||||
func update(
|
func update(
|
||||||
from source: ImportSource,
|
from source: ImportSource,
|
||||||
in transaction: BaseDataTransaction
|
in transaction: BaseDataTransaction
|
||||||
) throws(any Swift.Error)
|
) throws(any Swift::Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -180,7 +180,7 @@ extension ImportableUniqueObject {
|
|||||||
public func didInsert(
|
public func didInsert(
|
||||||
from source: Self.ImportSource,
|
from source: Self.ImportSource,
|
||||||
in transaction: BaseDataTransaction
|
in transaction: BaseDataTransaction
|
||||||
) throws(any Swift.Error) {
|
) throws(any Swift::Error) {
|
||||||
|
|
||||||
try self.update(from: source, in: transaction)
|
try self.update(from: source, in: transaction)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ extension Internals {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@nonobjc
|
@nonobjc
|
||||||
internal func performFetchFromSpecifiedStores() throws(any Swift.Error) {
|
internal func performFetchFromSpecifiedStores() throws(any Swift::Error) {
|
||||||
|
|
||||||
try self.reapplyAffectedStores(self.typedFetchRequest, self.managedObjectContext)
|
try self.reapplyAffectedStores(self.typedFetchRequest, self.managedObjectContext)
|
||||||
try self.performFetch()
|
try self.performFetch()
|
||||||
@@ -106,6 +106,6 @@ extension Internals {
|
|||||||
private let reapplyAffectedStores: (
|
private let reapplyAffectedStores: (
|
||||||
_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>,
|
_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>,
|
||||||
_ context: NSManagedObjectContext
|
_ context: NSManagedObjectContext
|
||||||
) throws(any Swift.Error) -> Void
|
) throws(any Swift::Error) -> Void
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ extension Internals {
|
|||||||
borrowing func withLock<Result, E>(
|
borrowing func withLock<Result, E>(
|
||||||
_ body: (inout sending Value) throws(E) -> sending Result
|
_ body: (inout sending Value) throws(E) -> sending Result
|
||||||
) throws(E) -> sending Result
|
) throws(E) -> sending Result
|
||||||
where E: Error, Result: ~Copyable {
|
where E: Swift::Error, Result: ~Copyable {
|
||||||
|
|
||||||
let storage = self.storage
|
let storage = self.storage
|
||||||
storage.lock()
|
storage.lock()
|
||||||
@@ -60,7 +60,7 @@ extension Internals {
|
|||||||
borrowing func withLockUnchecked<Result, E>(
|
borrowing func withLockUnchecked<Result, E>(
|
||||||
_ body: (inout sending Value) throws(E) -> Result
|
_ body: (inout sending Value) throws(E) -> Result
|
||||||
) throws(E) -> sending Result
|
) throws(E) -> sending Result
|
||||||
where E: Error {
|
where E: Swift::Error {
|
||||||
|
|
||||||
let storage = self.storage
|
let storage = self.storage
|
||||||
storage.lock()
|
storage.lock()
|
||||||
|
|||||||
@@ -133,8 +133,8 @@ internal enum Internals {
|
|||||||
|
|
||||||
@inline(__always)
|
@inline(__always)
|
||||||
internal static func autoreleasepool<T>(
|
internal static func autoreleasepool<T>(
|
||||||
_ closure: () throws(any Swift.Error) -> T
|
_ closure: () throws(any Swift::Error) -> T
|
||||||
) throws(any Swift.Error) -> T {
|
) throws(any Swift::Error) -> T {
|
||||||
|
|
||||||
return try ObjectiveC.autoreleasepool(invoking: closure)
|
return try ObjectiveC.autoreleasepool(invoking: closure)
|
||||||
}
|
}
|
||||||
@@ -142,8 +142,8 @@ internal enum Internals {
|
|||||||
@inline(__always)
|
@inline(__always)
|
||||||
internal static func withCheckedThrowingContinuation<T>(
|
internal static func withCheckedThrowingContinuation<T>(
|
||||||
function: String = #function,
|
function: String = #function,
|
||||||
_ body: (CheckedContinuation<T, any Swift.Error>) -> Void
|
_ body: (CheckedContinuation<T, any Swift::Error>) -> Void
|
||||||
) async throws(any Swift.Error) -> sending T {
|
) async throws(any Swift::Error) -> sending T {
|
||||||
|
|
||||||
return try await _Concurrency.withCheckedThrowingContinuation(
|
return try await _Concurrency.withCheckedThrowingContinuation(
|
||||||
function: function,
|
function: function,
|
||||||
|
|||||||
@@ -189,7 +189,7 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
|
|||||||
public func refetch<B: FetchChainableBuilderType>(
|
public func refetch<B: FetchChainableBuilderType>(
|
||||||
_ clauseChain: B,
|
_ clauseChain: B,
|
||||||
sourceIdentifier: (any Sendable)? = nil
|
sourceIdentifier: (any Sendable)? = nil
|
||||||
) throws(any Swift.Error) where B.ObjectType == O {
|
) throws(any Swift::Error) where B.ObjectType == O {
|
||||||
|
|
||||||
try self.refetch(
|
try self.refetch(
|
||||||
from: clauseChain.from,
|
from: clauseChain.from,
|
||||||
@@ -218,7 +218,7 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
|
|||||||
public func refetch<B: SectionMonitorBuilderType>(
|
public func refetch<B: SectionMonitorBuilderType>(
|
||||||
_ clauseChain: B,
|
_ clauseChain: B,
|
||||||
sourceIdentifier: (any Sendable)? = nil
|
sourceIdentifier: (any Sendable)? = nil
|
||||||
) throws(any Swift.Error) where B.ObjectType == O {
|
) throws(any Swift::Error) where B.ObjectType == O {
|
||||||
|
|
||||||
try self.refetch(
|
try self.refetch(
|
||||||
from: clauseChain.from,
|
from: clauseChain.from,
|
||||||
@@ -347,7 +347,7 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
|
|||||||
sectionBy: SectionBy<O>?,
|
sectionBy: SectionBy<O>?,
|
||||||
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void,
|
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void,
|
||||||
sourceIdentifier: (any Sendable)?
|
sourceIdentifier: (any Sendable)?
|
||||||
) throws(any Swift.Error) {
|
) throws(any Swift::Error) {
|
||||||
|
|
||||||
let (newFetchedResultsController, newFetchedResultsControllerDelegate) = Self.recreateFetchedResultsController(
|
let (newFetchedResultsController, newFetchedResultsControllerDelegate) = Self.recreateFetchedResultsController(
|
||||||
context: self.fetchedResultsController.managedObjectContext,
|
context: self.fetchedResultsController.managedObjectContext,
|
||||||
|
|||||||
+35
-14
@@ -35,7 +35,7 @@ import SwiftUI
|
|||||||
A property wrapper type that can read `ListPublisher` changes.
|
A property wrapper type that can read `ListPublisher` changes.
|
||||||
*/
|
*/
|
||||||
@propertyWrapper
|
@propertyWrapper
|
||||||
public struct ListState<O: DynamicObject>: DynamicProperty {
|
public struct ListState<O: DynamicObject>: @MainActor DynamicProperty {
|
||||||
|
|
||||||
// MARK: Public
|
// MARK: Public
|
||||||
|
|
||||||
@@ -70,6 +70,7 @@ public struct ListState<O: DynamicObject>: DynamicProperty {
|
|||||||
_ listPublisher: ListPublisher<O>
|
_ listPublisher: ListPublisher<O>
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
self.sourceListPublisher = listPublisher
|
||||||
self._observer = .init(wrappedValue: .init(listPublisher: listPublisher))
|
self._observer = .init(wrappedValue: .init(listPublisher: listPublisher))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -338,9 +339,11 @@ public struct ListState<O: DynamicObject>: DynamicProperty {
|
|||||||
|
|
||||||
// MARK: DynamicProperty
|
// MARK: DynamicProperty
|
||||||
|
|
||||||
|
@MainActor
|
||||||
public mutating func update() {
|
public mutating func update() {
|
||||||
|
|
||||||
self._observer.update()
|
self._observer.update()
|
||||||
|
self.observer.rebind(to: self.sourceListPublisher)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -349,13 +352,15 @@ public struct ListState<O: DynamicObject>: DynamicProperty {
|
|||||||
@State
|
@State
|
||||||
private var observer: Observer
|
private var observer: Observer
|
||||||
|
|
||||||
|
private let sourceListPublisher: ListPublisher<O>
|
||||||
|
|
||||||
|
|
||||||
// MARK: - Observer
|
// MARK: - Observer
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
private final class Observer: Observation.Observable {
|
private final class Observer: Observation.Observable {
|
||||||
|
|
||||||
let listPublisher: ListPublisher<O>
|
private(set) var listPublisher: ListPublisher<O>
|
||||||
|
|
||||||
nonisolated var items: ListSnapshot<O> {
|
nonisolated var items: ListSnapshot<O> {
|
||||||
|
|
||||||
@@ -377,8 +382,35 @@ public struct ListState<O: DynamicObject>: DynamicProperty {
|
|||||||
|
|
||||||
self.listPublisher = listPublisher
|
self.listPublisher = listPublisher
|
||||||
self.current = .init(listPublisher.snapshot)
|
self.current = .init(listPublisher.snapshot)
|
||||||
|
self.attachObserver()
|
||||||
|
}
|
||||||
|
|
||||||
|
isolated deinit {
|
||||||
|
|
||||||
listPublisher.addObserver(self) { [weak self] (listPublisher) in
|
self.listPublisher.removeObserver(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
func rebind(to listPublisher: ListPublisher<O>) {
|
||||||
|
|
||||||
|
guard self.listPublisher !== listPublisher else {
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.listPublisher.removeObserver(self)
|
||||||
|
self.listPublisher = listPublisher
|
||||||
|
self.items = listPublisher.snapshot
|
||||||
|
self.attachObserver()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: Private
|
||||||
|
|
||||||
|
private let registrar = ObservationRegistrar()
|
||||||
|
private let current: Internals.Mutex<ListSnapshot<O>>
|
||||||
|
|
||||||
|
private func attachObserver() {
|
||||||
|
|
||||||
|
self.listPublisher.addObserver(self) { [weak self] listPublisher in
|
||||||
|
|
||||||
guard let self = self else {
|
guard let self = self else {
|
||||||
|
|
||||||
@@ -387,17 +419,6 @@ public struct ListState<O: DynamicObject>: DynamicProperty {
|
|||||||
self.items = listPublisher.snapshot
|
self.items = listPublisher.snapshot
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isolated deinit {
|
|
||||||
|
|
||||||
self.listPublisher.removeObserver(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// MARK: Private
|
|
||||||
|
|
||||||
private let registrar = ObservationRegistrar()
|
|
||||||
private let current: Internals.Mutex<ListSnapshot<O>>
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ extension NSManagedObject {
|
|||||||
@nonobjc @inline(__always)
|
@nonobjc @inline(__always)
|
||||||
public func getValue<T>(
|
public func getValue<T>(
|
||||||
forKvcKey kvcKey: KeyPathString,
|
forKvcKey kvcKey: KeyPathString,
|
||||||
didGetValue: (Any?) throws(any Swift.Error) -> T
|
didGetValue: (Any?) throws(any Swift::Error) -> T
|
||||||
) rethrows -> T {
|
) rethrows -> T {
|
||||||
|
|
||||||
self.willAccessValue(forKey: kvcKey)
|
self.willAccessValue(forKey: kvcKey)
|
||||||
@@ -128,8 +128,8 @@ extension NSManagedObject {
|
|||||||
@nonobjc @inline(__always)
|
@nonobjc @inline(__always)
|
||||||
public func getValue<T>(
|
public func getValue<T>(
|
||||||
forKvcKey kvcKey: KeyPathString,
|
forKvcKey kvcKey: KeyPathString,
|
||||||
willGetValue: () throws(any Swift.Error) -> Void,
|
willGetValue: () throws(any Swift::Error) -> Void,
|
||||||
didGetValue: (Any?) throws(any Swift.Error) -> T
|
didGetValue: (Any?) throws(any Swift::Error) -> T
|
||||||
) rethrows -> T {
|
) rethrows -> T {
|
||||||
|
|
||||||
self.willAccessValue(forKey: kvcKey)
|
self.willAccessValue(forKey: kvcKey)
|
||||||
@@ -196,7 +196,7 @@ extension NSManagedObject {
|
|||||||
public func setValue<T>(
|
public func setValue<T>(
|
||||||
_ value: T,
|
_ value: T,
|
||||||
forKvcKey KVCKey: KeyPathString,
|
forKvcKey KVCKey: KeyPathString,
|
||||||
willSetValue: (T) throws(any Swift.Error) -> Any?,
|
willSetValue: (T) throws(any Swift::Error) -> Any?,
|
||||||
didSetValue: (Any?) -> Void = { _ in }
|
didSetValue: (Any?) -> Void = { _ in }
|
||||||
) rethrows {
|
) rethrows {
|
||||||
|
|
||||||
|
|||||||
@@ -76,6 +76,14 @@ extension NSManagedObjectContext: FetchableSource, QueryableSource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@nonobjc
|
||||||
|
public func fetchExisting<O: DynamicObject>(
|
||||||
|
_ persistentID: DynamicObjectID<O>
|
||||||
|
) -> O? {
|
||||||
|
|
||||||
|
return self.fetchExisting(persistentID.managedObjectID)
|
||||||
|
}
|
||||||
|
|
||||||
@nonobjc
|
@nonobjc
|
||||||
public func fetchExisting<O: DynamicObject>(
|
public func fetchExisting<O: DynamicObject>(
|
||||||
_ objectID: NSManagedObjectID
|
_ objectID: NSManagedObjectID
|
||||||
@@ -102,10 +110,10 @@ extension NSManagedObjectContext: FetchableSource, QueryableSource {
|
|||||||
|
|
||||||
@nonobjc
|
@nonobjc
|
||||||
public func fetchExisting<O: DynamicObject, S: Sequence>(
|
public func fetchExisting<O: DynamicObject, S: Sequence>(
|
||||||
_ objectIDs: S
|
_ persistentIDs: S
|
||||||
) -> [O] where S.Iterator.Element == DynamicObjectID<O> {
|
) -> [O] where S.Iterator.Element == DynamicObjectID<O> {
|
||||||
|
|
||||||
return objectIDs.compactMap({ self.fetchExisting($0.managedObjectID) })
|
return persistentIDs.compactMap({ self.fetchExisting($0.managedObjectID) })
|
||||||
}
|
}
|
||||||
|
|
||||||
@nonobjc
|
@nonobjc
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ extension NSPersistentStoreCoordinator {
|
|||||||
|
|
||||||
@nonobjc
|
@nonobjc
|
||||||
internal func performSynchronously<T>(
|
internal func performSynchronously<T>(
|
||||||
_ closure: @Sendable () throws(any Swift.Error) -> T
|
_ closure: @Sendable () throws(any Swift::Error) -> T
|
||||||
) throws(CoreStoreError) -> T {
|
) throws(CoreStoreError) -> T {
|
||||||
|
|
||||||
do {
|
do {
|
||||||
|
|||||||
@@ -72,32 +72,36 @@ public final class ObjectMonitor<O: DynamicObject>: Hashable, ObjectRepresentati
|
|||||||
- parameter observer: an `ObjectObserver` to send change notifications to
|
- parameter observer: an `ObjectObserver` to send change notifications to
|
||||||
*/
|
*/
|
||||||
@MainActor
|
@MainActor
|
||||||
public func addObserver<U: ObjectObserver & Sendable>(_ observer: U) where U.ObjectEntityType == O {
|
public func addObserver<U: ObjectObserver & Sendable>(_ observer: U)
|
||||||
|
where U.ObjectEntityType == O {
|
||||||
|
|
||||||
self.unregisterObserver(observer)
|
self.unregisterObserver(observer)
|
||||||
self.registerObserver(
|
self.registerObserver(
|
||||||
observer,
|
observer,
|
||||||
willChangeObject: { (observer, monitor, object) in
|
willChangeObject: { (observer, monitor, object) in
|
||||||
|
|
||||||
|
nonisolated(unsafe) let sending = object
|
||||||
observer.objectMonitor(
|
observer.objectMonitor(
|
||||||
monitor,
|
monitor,
|
||||||
willUpdateObject: object,
|
willUpdateObject: sending,
|
||||||
sourceIdentifier: monitor.context.saveMetadata?.sourceIdentifier
|
sourceIdentifier: monitor.context.saveMetadata?.sourceIdentifier
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
didDeleteObject: { (observer, monitor, object) in
|
didDeleteObject: { (observer, monitor, object) in
|
||||||
|
|
||||||
|
nonisolated(unsafe) let sending = object
|
||||||
observer.objectMonitor(
|
observer.objectMonitor(
|
||||||
monitor,
|
monitor,
|
||||||
didDeleteObject: object,
|
didDeleteObject: sending,
|
||||||
sourceIdentifier: monitor.context.saveMetadata?.sourceIdentifier
|
sourceIdentifier: monitor.context.saveMetadata?.sourceIdentifier
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
didUpdateObject: { (observer, monitor, object, changedPersistentKeys) in
|
didUpdateObject: { (observer, monitor, object, changedPersistentKeys) in
|
||||||
|
|
||||||
|
nonisolated(unsafe) let sending = object
|
||||||
observer.objectMonitor(
|
observer.objectMonitor(
|
||||||
monitor,
|
monitor,
|
||||||
didUpdateObject: object,
|
didUpdateObject: sending,
|
||||||
changedPersistentKeys: changedPersistentKeys,
|
changedPersistentKeys: changedPersistentKeys,
|
||||||
sourceIdentifier: monitor.context.saveMetadata?.sourceIdentifier
|
sourceIdentifier: monitor.context.saveMetadata?.sourceIdentifier
|
||||||
)
|
)
|
||||||
@@ -113,7 +117,8 @@ public final class ObjectMonitor<O: DynamicObject>: Hashable, ObjectRepresentati
|
|||||||
- parameter observer: an `ObjectObserver` to unregister notifications to
|
- parameter observer: an `ObjectObserver` to unregister notifications to
|
||||||
*/
|
*/
|
||||||
@MainActor
|
@MainActor
|
||||||
public func removeObserver<U: ObjectObserver>(_ observer: U) where U.ObjectEntityType == O {
|
public func removeObserver<U: ObjectObserver>(_ observer: U)
|
||||||
|
where U.ObjectEntityType == O {
|
||||||
|
|
||||||
self.unregisterObserver(observer)
|
self.unregisterObserver(observer)
|
||||||
}
|
}
|
||||||
@@ -412,11 +417,13 @@ public final class ObjectMonitor<O: DynamicObject>: Hashable, ObjectRepresentati
|
|||||||
object: self,
|
object: self,
|
||||||
closure: { [weak self] (note) in
|
closure: { [weak self] (note) in
|
||||||
|
|
||||||
guard let self = self,
|
guard
|
||||||
|
let self = self,
|
||||||
let userInfo = note.userInfo,
|
let userInfo = note.userInfo,
|
||||||
let object = userInfo[String(describing: NSManagedObject.self)] as! NSManagedObject? else {
|
let object = userInfo[String(describing: NSManagedObject.self)] as! NSManagedObject?
|
||||||
|
else {
|
||||||
return
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
callback(self, O.cs_fromRaw(object: object))
|
callback(self, O.cs_fromRaw(object: object))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,8 +53,8 @@ public protocol ObjectObserver: AnyObject, Sendable {
|
|||||||
*/
|
*/
|
||||||
func objectMonitor(
|
func objectMonitor(
|
||||||
_ monitor: ObjectMonitor<ObjectEntityType>,
|
_ monitor: ObjectMonitor<ObjectEntityType>,
|
||||||
willUpdateObject object: ObjectEntityType,
|
willUpdateObject object: sending ObjectEntityType,
|
||||||
sourceIdentifier: Any?
|
sourceIdentifier: (any Sendable)?
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -66,7 +66,7 @@ public protocol ObjectObserver: AnyObject, Sendable {
|
|||||||
*/
|
*/
|
||||||
func objectMonitor(
|
func objectMonitor(
|
||||||
_ monitor: ObjectMonitor<ObjectEntityType>,
|
_ monitor: ObjectMonitor<ObjectEntityType>,
|
||||||
willUpdateObject object: ObjectEntityType
|
willUpdateObject object: sending ObjectEntityType
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -80,9 +80,9 @@ public protocol ObjectObserver: AnyObject, Sendable {
|
|||||||
*/
|
*/
|
||||||
func objectMonitor(
|
func objectMonitor(
|
||||||
_ monitor: ObjectMonitor<ObjectEntityType>,
|
_ monitor: ObjectMonitor<ObjectEntityType>,
|
||||||
didUpdateObject object: ObjectEntityType,
|
didUpdateObject object: sending ObjectEntityType,
|
||||||
changedPersistentKeys: Set<KeyPathString>,
|
changedPersistentKeys: Set<KeyPathString>,
|
||||||
sourceIdentifier: Any?
|
sourceIdentifier: (any Sendable)?
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -95,7 +95,7 @@ public protocol ObjectObserver: AnyObject, Sendable {
|
|||||||
*/
|
*/
|
||||||
func objectMonitor(
|
func objectMonitor(
|
||||||
_ monitor: ObjectMonitor<ObjectEntityType>,
|
_ monitor: ObjectMonitor<ObjectEntityType>,
|
||||||
didUpdateObject object: ObjectEntityType,
|
didUpdateObject object: sending ObjectEntityType,
|
||||||
changedPersistentKeys: Set<KeyPathString>
|
changedPersistentKeys: Set<KeyPathString>
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -109,8 +109,8 @@ public protocol ObjectObserver: AnyObject, Sendable {
|
|||||||
*/
|
*/
|
||||||
func objectMonitor(
|
func objectMonitor(
|
||||||
_ monitor: ObjectMonitor<ObjectEntityType>,
|
_ monitor: ObjectMonitor<ObjectEntityType>,
|
||||||
didDeleteObject object: ObjectEntityType,
|
didDeleteObject object: sending ObjectEntityType,
|
||||||
sourceIdentifier: Any?
|
sourceIdentifier: (any Sendable)?
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -122,7 +122,7 @@ public protocol ObjectObserver: AnyObject, Sendable {
|
|||||||
*/
|
*/
|
||||||
func objectMonitor(
|
func objectMonitor(
|
||||||
_ monitor: ObjectMonitor<ObjectEntityType>,
|
_ monitor: ObjectMonitor<ObjectEntityType>,
|
||||||
didDeleteObject object: ObjectEntityType
|
didDeleteObject object: sending ObjectEntityType
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,8 +133,8 @@ extension ObjectObserver {
|
|||||||
|
|
||||||
public func objectMonitor(
|
public func objectMonitor(
|
||||||
_ monitor: ObjectMonitor<ObjectEntityType>,
|
_ monitor: ObjectMonitor<ObjectEntityType>,
|
||||||
willUpdateObject object: ObjectEntityType,
|
willUpdateObject object: sending ObjectEntityType,
|
||||||
sourceIdentifier: Any?
|
sourceIdentifier: (any Sendable)?
|
||||||
) {
|
) {
|
||||||
|
|
||||||
self.objectMonitor(
|
self.objectMonitor(
|
||||||
@@ -145,14 +145,14 @@ extension ObjectObserver {
|
|||||||
|
|
||||||
public func objectMonitor(
|
public func objectMonitor(
|
||||||
_ monitor: ObjectMonitor<ObjectEntityType>,
|
_ monitor: ObjectMonitor<ObjectEntityType>,
|
||||||
willUpdateObject object: ObjectEntityType
|
willUpdateObject object: sending ObjectEntityType
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public func objectMonitor(
|
public func objectMonitor(
|
||||||
_ monitor: ObjectMonitor<ObjectEntityType>,
|
_ monitor: ObjectMonitor<ObjectEntityType>,
|
||||||
didUpdateObject object: ObjectEntityType,
|
didUpdateObject object: sending ObjectEntityType,
|
||||||
changedPersistentKeys: Set<KeyPathString>,
|
changedPersistentKeys: Set<KeyPathString>,
|
||||||
sourceIdentifier: Any?
|
sourceIdentifier: (any Sendable)?
|
||||||
) {
|
) {
|
||||||
|
|
||||||
self.objectMonitor(
|
self.objectMonitor(
|
||||||
@@ -164,14 +164,14 @@ extension ObjectObserver {
|
|||||||
|
|
||||||
public func objectMonitor(
|
public func objectMonitor(
|
||||||
_ monitor: ObjectMonitor<ObjectEntityType>,
|
_ monitor: ObjectMonitor<ObjectEntityType>,
|
||||||
didUpdateObject object: ObjectEntityType,
|
didUpdateObject object: sending ObjectEntityType,
|
||||||
changedPersistentKeys: Set<KeyPathString>
|
changedPersistentKeys: Set<KeyPathString>
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public func objectMonitor(
|
public func objectMonitor(
|
||||||
_ monitor: ObjectMonitor<ObjectEntityType>,
|
_ monitor: ObjectMonitor<ObjectEntityType>,
|
||||||
didDeleteObject object: ObjectEntityType,
|
didDeleteObject object: sending ObjectEntityType,
|
||||||
sourceIdentifier: Any?
|
sourceIdentifier: (any Sendable)?
|
||||||
) {
|
) {
|
||||||
|
|
||||||
self.objectMonitor(
|
self.objectMonitor(
|
||||||
@@ -182,6 +182,6 @@ extension ObjectObserver {
|
|||||||
|
|
||||||
public func objectMonitor(
|
public func objectMonitor(
|
||||||
_ monitor: ObjectMonitor<ObjectEntityType>,
|
_ monitor: ObjectMonitor<ObjectEntityType>,
|
||||||
didDeleteObject object: ObjectEntityType
|
didDeleteObject object: sending ObjectEntityType
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ public struct ObjectReader<Object: DynamicObject, Content: View, Placeholder: Vi
|
|||||||
keyPath: KeyPath<ObjectSnapshot<Object>, Value>,
|
keyPath: KeyPath<ObjectSnapshot<Object>, Value>,
|
||||||
@ViewBuilder content: @escaping (Value) -> Content,
|
@ViewBuilder content: @escaping (Value) -> Content,
|
||||||
@ViewBuilder placeholder: @escaping () -> Placeholder
|
@ViewBuilder placeholder: @escaping () -> Placeholder
|
||||||
) where Placeholder == EmptyView {
|
) {
|
||||||
|
|
||||||
self._object = .init(objectPublisher)
|
self._object = .init(objectPublisher)
|
||||||
self.content = {
|
self.content = {
|
||||||
|
|||||||
+40
-19
@@ -35,7 +35,7 @@ import SwiftUI
|
|||||||
A property wrapper type that can read `ObjectPublisher` changes.
|
A property wrapper type that can read `ObjectPublisher` changes.
|
||||||
*/
|
*/
|
||||||
@propertyWrapper
|
@propertyWrapper
|
||||||
public struct ObjectState<O: DynamicObject>: DynamicProperty {
|
public struct ObjectState<O: DynamicObject>: @MainActor DynamicProperty {
|
||||||
|
|
||||||
// MARK: Public
|
// MARK: Public
|
||||||
|
|
||||||
@@ -65,6 +65,7 @@ public struct ObjectState<O: DynamicObject>: DynamicProperty {
|
|||||||
@MainActor
|
@MainActor
|
||||||
public init(_ objectPublisher: ObjectPublisher<O>?) {
|
public init(_ objectPublisher: ObjectPublisher<O>?) {
|
||||||
|
|
||||||
|
self.sourceObjectPublisher = objectPublisher
|
||||||
self._observer = .init(wrappedValue: .init(objectPublisher: objectPublisher))
|
self._observer = .init(wrappedValue: .init(objectPublisher: objectPublisher))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,9 +87,11 @@ public struct ObjectState<O: DynamicObject>: DynamicProperty {
|
|||||||
|
|
||||||
// MARK: DynamicProperty
|
// MARK: DynamicProperty
|
||||||
|
|
||||||
|
@MainActor
|
||||||
public mutating func update() {
|
public mutating func update() {
|
||||||
|
|
||||||
self._observer.update()
|
self._observer.update()
|
||||||
|
self.observer.rebind(to: self.sourceObjectPublisher)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -97,13 +100,15 @@ public struct ObjectState<O: DynamicObject>: DynamicProperty {
|
|||||||
@State
|
@State
|
||||||
private var observer: Observer
|
private var observer: Observer
|
||||||
|
|
||||||
|
private let sourceObjectPublisher: ObjectPublisher<O>?
|
||||||
|
|
||||||
|
|
||||||
// MARK: - Observer
|
// MARK: - Observer
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
private final class Observer: Observation.Observable {
|
private final class Observer: Observation.Observable {
|
||||||
|
|
||||||
let objectPublisher: ObjectPublisher<O>?
|
private(set) var objectPublisher: ObjectPublisher<O>?
|
||||||
|
|
||||||
nonisolated var item: ObjectSnapshot<O>? {
|
nonisolated var item: ObjectSnapshot<O>? {
|
||||||
|
|
||||||
@@ -122,21 +127,28 @@ public struct ObjectState<O: DynamicObject>: DynamicProperty {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init(objectPublisher: ObjectPublisher<O>?) {
|
init(objectPublisher: ObjectPublisher<O>?) {
|
||||||
|
|
||||||
guard
|
self.objectPublisher = nil
|
||||||
let dataStack = objectPublisher?.cs_dataStack(),
|
self.current = .init(nil)
|
||||||
let objectPublisher = objectPublisher?.asPublisher(in: dataStack)
|
self.rebind(to: objectPublisher)
|
||||||
else {
|
}
|
||||||
|
|
||||||
self.objectPublisher = nil
|
isolated deinit {
|
||||||
self.current = .init(nil)
|
|
||||||
|
self.objectPublisher?.removeObserver(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
func rebind(to objectPublisher: ObjectPublisher<O>?) {
|
||||||
|
|
||||||
|
let objectPublisher = Self.canonicalPublisher(for: objectPublisher)
|
||||||
|
guard self.objectPublisher != objectPublisher else {
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
self.objectPublisher?.removeObserver(self)
|
||||||
self.objectPublisher = objectPublisher
|
self.objectPublisher = objectPublisher
|
||||||
self.current = .init(objectPublisher.snapshot)
|
self.item = objectPublisher?.snapshot
|
||||||
|
objectPublisher?.addObserver(self) { [weak self] objectPublisher in
|
||||||
objectPublisher.addObserver(self) { [weak self] (objectPublisher) in
|
|
||||||
|
|
||||||
guard let self = self else {
|
guard let self = self else {
|
||||||
|
|
||||||
@@ -146,16 +158,25 @@ public struct ObjectState<O: DynamicObject>: DynamicProperty {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isolated deinit {
|
|
||||||
|
|
||||||
self.objectPublisher?.removeObserver(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// MARK: Private
|
// MARK: Private
|
||||||
|
|
||||||
private let registrar = ObservationRegistrar()
|
private let registrar = ObservationRegistrar()
|
||||||
private let current: Internals.Mutex<ObjectSnapshot<O>?>
|
private let current: Internals.Mutex<ObjectSnapshot<O>?>
|
||||||
|
|
||||||
|
private static func canonicalPublisher(
|
||||||
|
for objectPublisher: ObjectPublisher<O>?
|
||||||
|
) -> ObjectPublisher<O>? {
|
||||||
|
|
||||||
|
guard
|
||||||
|
let objectPublisher = objectPublisher,
|
||||||
|
let dataStack = objectPublisher.cs_dataStack()
|
||||||
|
else {
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return objectPublisher.asPublisher(in: dataStack)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -236,7 +236,7 @@ public final class SQLiteStore: LocalStorage {
|
|||||||
@_spi(Internals)
|
@_spi(Internals)
|
||||||
public func cs_finalizeStorageAndWait(
|
public func cs_finalizeStorageAndWait(
|
||||||
soureModelHint: NSManagedObjectModel
|
soureModelHint: NSManagedObjectModel
|
||||||
) throws(any Swift.Error) {
|
) throws(any Swift::Error) {
|
||||||
|
|
||||||
_ = try withExtendedLifetime(NSPersistentStoreCoordinator(managedObjectModel: soureModelHint)) { (coordinator: NSPersistentStoreCoordinator) in
|
_ = try withExtendedLifetime(NSPersistentStoreCoordinator(managedObjectModel: soureModelHint)) { (coordinator: NSPersistentStoreCoordinator) in
|
||||||
|
|
||||||
@@ -259,12 +259,12 @@ public final class SQLiteStore: LocalStorage {
|
|||||||
public func cs_eraseStorageAndWait(
|
public func cs_eraseStorageAndWait(
|
||||||
metadata: [String: Any],
|
metadata: [String: Any],
|
||||||
soureModelHint: NSManagedObjectModel?
|
soureModelHint: NSManagedObjectModel?
|
||||||
) throws(any Swift.Error) {
|
) throws(any Swift::Error) {
|
||||||
|
|
||||||
func deleteFiles(
|
func deleteFiles(
|
||||||
storeURL: URL,
|
storeURL: URL,
|
||||||
extraFiles: [String] = []
|
extraFiles: [String] = []
|
||||||
) throws(any Swift.Error) {
|
) throws(any Swift::Error) {
|
||||||
|
|
||||||
let fileManager = FileManager.default
|
let fileManager = FileManager.default
|
||||||
let extraFiles: [String] = [
|
let extraFiles: [String] = [
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ public protocol LocalStorage: StorageInterface {
|
|||||||
@_spi(Internals)
|
@_spi(Internals)
|
||||||
func cs_finalizeStorageAndWait(
|
func cs_finalizeStorageAndWait(
|
||||||
soureModelHint: NSManagedObjectModel
|
soureModelHint: NSManagedObjectModel
|
||||||
) throws(any Swift.Error)
|
) throws(any Swift::Error)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Called by the `DataStack` to perform actual deletion of the store file from disk. **Do not call directly!** The `sourceModel` argument is a hint for the existing store's model version. Implementers can use the `sourceModel` to perform necessary store operations. (SQLite stores for example, can convert WAL journaling mode to DELETE before deleting)
|
Called by the `DataStack` to perform actual deletion of the store file from disk. **Do not call directly!** The `sourceModel` argument is a hint for the existing store's model version. Implementers can use the `sourceModel` to perform necessary store operations. (SQLite stores for example, can convert WAL journaling mode to DELETE before deleting)
|
||||||
@@ -160,7 +160,7 @@ public protocol LocalStorage: StorageInterface {
|
|||||||
func cs_eraseStorageAndWait(
|
func cs_eraseStorageAndWait(
|
||||||
metadata: [String: Any],
|
metadata: [String: Any],
|
||||||
soureModelHint: NSManagedObjectModel?
|
soureModelHint: NSManagedObjectModel?
|
||||||
) throws(any Swift.Error)
|
) throws(any Swift::Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
extension LocalStorage {
|
extension LocalStorage {
|
||||||
|
|||||||
@@ -103,6 +103,26 @@ public nonisolated final class SynchronousDataTransaction: BaseDataTransaction {
|
|||||||
return super.edit(object)
|
return super.edit(object)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Returns an editable proxy of the object with the specified `DynamicObjectID`.
|
||||||
|
|
||||||
|
- parameter into: an `Into` clause specifying the entity type
|
||||||
|
- parameter persistentID: the `DynamicObjectID` for the object to be edited
|
||||||
|
- returns: an editable proxy for the specified `NSManagedObject` or `CoreStoreObject`.
|
||||||
|
*/
|
||||||
|
public override func edit<O>(
|
||||||
|
_ into: Into<O>,
|
||||||
|
_ persistentID: DynamicObjectID<O>
|
||||||
|
) -> O? {
|
||||||
|
|
||||||
|
Internals.assert(
|
||||||
|
!self.isCommitted,
|
||||||
|
"Attempted to update an entity of type \(Internals.typeName(into.entityClass)) from an already committed \(Internals.typeName(self))."
|
||||||
|
)
|
||||||
|
|
||||||
|
return super.edit(into, persistentID)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Returns an editable proxy of the object with the specified `NSManagedObjectID`.
|
Returns an editable proxy of the object with the specified `NSManagedObjectID`.
|
||||||
|
|
||||||
@@ -122,6 +142,23 @@ public nonisolated final class SynchronousDataTransaction: BaseDataTransaction {
|
|||||||
|
|
||||||
return super.edit(into, objectID)
|
return super.edit(into, objectID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Deletes the objects with the specified `NSManagedObjectID`s.
|
||||||
|
|
||||||
|
- parameter objectIDs: the `NSManagedObjectID`s of the objects to delete
|
||||||
|
*/
|
||||||
|
public override func delete<O: DynamicObject, S: Sequence>(
|
||||||
|
persistentIDs: S
|
||||||
|
) where S.Iterator.Element == DynamicObjectID<O> {
|
||||||
|
|
||||||
|
Internals.assert(
|
||||||
|
!self.isCommitted,
|
||||||
|
"Attempted to delete an entities from an already committed \(Internals.typeName(self))."
|
||||||
|
)
|
||||||
|
|
||||||
|
super.delete(persistentIDs: persistentIDs)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Deletes the objects with the specified `NSManagedObjectID`s.
|
Deletes the objects with the specified `NSManagedObjectID`s.
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ public final class UnsafeDataTransaction: BaseDataTransaction, @unchecked Sendab
|
|||||||
- throws: an error thrown from `closure`, or an error thrown by Core Data (usually validation errors or conflict errors)
|
- throws: an error thrown from `closure`, or an error thrown by Core Data (usually validation errors or conflict errors)
|
||||||
*/
|
*/
|
||||||
public func flush(
|
public func flush(
|
||||||
closure: () throws(any Swift.Error) -> Void
|
closure: () throws(any Swift::Error) -> Void
|
||||||
) rethrows {
|
) rethrows {
|
||||||
|
|
||||||
try closure()
|
try closure()
|
||||||
|
|||||||
Reference in New Issue
Block a user