mirror of
https://github.com/JohnEstropia/CoreStore.git
synced 2026-07-17 02:01:23 +02:00
Initial prototype for Swift 6 mode
This commit is contained in:
@@ -3016,8 +3016,8 @@
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
INFOPLIST_FILE = Sources/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.5;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
OTHER_SWIFT_FLAGS = "-D DEBUG";
|
||||
@@ -3027,12 +3027,12 @@
|
||||
SWIFT_COMPILATION_MODE = singlefile;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = Off;
|
||||
SWIFT_VERSION = 5.0;
|
||||
SWIFT_VERSION = 6.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TVOS_DEPLOYMENT_TARGET = 16.6;
|
||||
TVOS_DEPLOYMENT_TARGET = 17.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
WATCHOS_DEPLOYMENT_TARGET = 9.6;
|
||||
WATCHOS_DEPLOYMENT_TARGET = 10.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -3083,8 +3083,8 @@
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
INFOPLIST_FILE = Sources/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.5;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.johnestropia.CoreStore;
|
||||
PRODUCT_NAME = CoreStore;
|
||||
@@ -3092,13 +3092,13 @@
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = Off;
|
||||
SWIFT_VERSION = 5.0;
|
||||
SWIFT_VERSION = 6.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TVOS_DEPLOYMENT_TARGET = 16.6;
|
||||
TVOS_DEPLOYMENT_TARGET = 17.0;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
WATCHOS_DEPLOYMENT_TARGET = 9.6;
|
||||
WATCHOS_DEPLOYMENT_TARGET = 10.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
@@ -3366,7 +3366,7 @@
|
||||
"@executable_path/../Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.5;
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||
MARKETING_VERSION = 9.3.0;
|
||||
OTHER_LDFLAGS = (
|
||||
"-weak_framework",
|
||||
@@ -3401,7 +3401,7 @@
|
||||
"@executable_path/../Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.5;
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||
MARKETING_VERSION = 9.3.0;
|
||||
OTHER_LDFLAGS = (
|
||||
"-weak_framework",
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
//
|
||||
|
||||
import XCTest
|
||||
import os
|
||||
|
||||
@testable
|
||||
import CoreStore
|
||||
@@ -47,7 +48,11 @@ class BaseTestCase: XCTestCase {
|
||||
// MARK: Internal
|
||||
|
||||
@nonobjc
|
||||
func prepareStack(configurations: [ModelConfiguration] = [nil], _ closure: (_ dataStack: DataStack) throws -> Void) {
|
||||
@MainActor
|
||||
func prepareStack(
|
||||
configurations: [ModelConfiguration] = [nil],
|
||||
_ closure: (_ dataStack: DataStack) throws -> Void
|
||||
) {
|
||||
|
||||
let stack = DataStack(
|
||||
xcodeModelName: "Model",
|
||||
@@ -79,6 +84,7 @@ class BaseTestCase: XCTestCase {
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
@MainActor
|
||||
func expectLogger<T>(_ expectations: [TestLogger.Expectation], closure: () throws -> T) rethrows -> T {
|
||||
|
||||
CoreStoreDefaults.logger = TestLogger(self.prepareLoggerExpectations(expectations))
|
||||
@@ -97,6 +103,7 @@ class BaseTestCase: XCTestCase {
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
@MainActor
|
||||
func expectError<T>(code: CoreStoreErrorCode, closure: () throws -> T) {
|
||||
|
||||
CoreStoreDefaults.logger = TestLogger(self.prepareLoggerExpectations([.logError]))
|
||||
@@ -135,12 +142,14 @@ class BaseTestCase: XCTestCase {
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
@MainActor
|
||||
func checkExpectationsImmediately() {
|
||||
|
||||
self.waitForExpectations(timeout: 0, handler: { _ in })
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
@MainActor
|
||||
func waitAndCheckExpectations() {
|
||||
|
||||
self.waitForExpectations(timeout: 10, handler: {_ in })
|
||||
@@ -174,7 +183,7 @@ class BaseTestCase: XCTestCase {
|
||||
|
||||
// MARK: - TestLogger
|
||||
|
||||
class TestLogger: CoreStoreLogger {
|
||||
final class TestLogger: CoreStoreLogger {
|
||||
|
||||
enum Expectation {
|
||||
|
||||
@@ -187,13 +196,13 @@ class TestLogger: CoreStoreLogger {
|
||||
|
||||
init(_ expectations: [Expectation: XCTestExpectation]) {
|
||||
|
||||
self.expectations = expectations
|
||||
self.expectations = .init(initialState: expectations)
|
||||
}
|
||||
|
||||
|
||||
// MARK: CoreStoreLogger
|
||||
|
||||
var enableObjectConcurrencyDebugging: Bool = true
|
||||
let enableObjectConcurrencyDebugging: Bool = true
|
||||
|
||||
func log(level: LogLevel, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
||||
|
||||
@@ -227,18 +236,21 @@ class TestLogger: CoreStoreLogger {
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private var expectations: [Expectation: XCTestExpectation]
|
||||
private let expectations: OSAllocatedUnfairLock<[Expectation: XCTestExpectation]>
|
||||
|
||||
private func fulfill(_ expectation: Expectation) {
|
||||
|
||||
if let instance = self.expectations[expectation] {
|
||||
self.expectations.withLock {
|
||||
|
||||
if let instance = $0[expectation] {
|
||||
|
||||
instance.fulfill()
|
||||
self.expectations[expectation] = nil
|
||||
$0[expectation] = nil
|
||||
}
|
||||
else {
|
||||
|
||||
XCTFail("Unexpected Logger Action: \(expectation)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ import CoreStore
|
||||
class ConvenienceTests: BaseTestCase {
|
||||
|
||||
@objc
|
||||
@MainActor
|
||||
dynamic func test_ThatDataStacks_CanCreateFetchedResultsControllers() {
|
||||
|
||||
self.prepareStack { (stack) in
|
||||
@@ -62,6 +63,7 @@ class ConvenienceTests: BaseTestCase {
|
||||
}
|
||||
|
||||
@objc
|
||||
@MainActor
|
||||
dynamic func test_ThatUnsafeDataTransactions_CanCreateFetchedResultsControllers() {
|
||||
|
||||
self.prepareStack { (stack) in
|
||||
|
||||
@@ -162,6 +162,18 @@ class Person: CoreStoreObject {
|
||||
@Field.Relationship("_spouseInverse", inverse: \.$spouse)
|
||||
private var spouseInverse: Person?
|
||||
|
||||
@Field.Coded("ownerInfosJson", coder: FieldCoders.Json.self)
|
||||
var ownerInfosJson: [OwnerInfo] = []
|
||||
|
||||
@Field.Coded("ownerInfosPlist", coder: FieldCoders.Plist.self)
|
||||
var ownerInfosPlist: [OwnerInfo] = []
|
||||
|
||||
struct OwnerInfo: Codable, Equatable {
|
||||
var id: UUID
|
||||
var displayName: String?
|
||||
var otherIDs: Set<UUID>
|
||||
}
|
||||
|
||||
private static func getDisplayName(_ object: ObjectProxy<Person>, _ field: ObjectProxy<Person>.FieldProxy<String?>) -> String? {
|
||||
|
||||
if let value = field.primitiveValue {
|
||||
@@ -203,7 +215,7 @@ class DynamicModelTests: BaseTestDataTestCase {
|
||||
versionLock: [
|
||||
"Animal": [0x1b59d511019695cf, 0xdeb97e86c5eff179, 0x1cfd80745646cb3, 0x4ff99416175b5b9a],
|
||||
"Dog": [0xad6de93adc5565d, 0x7897e51253eba5a3, 0xd12b9ce0b13600f3, 0x5a4827cd794cd15e],
|
||||
"Person": [0xf3e6ba6016bbedc6, 0x50dedf64f0eba490, 0xa32088a0ee83468d, 0xb72d1d0b37bd0992]
|
||||
"Person": [0xbaec6549a025b59f, 0x3415c71e9f46fcf3, 0xb86b33433cb335eb, 0xfe441fde120dab67]
|
||||
]
|
||||
)
|
||||
)
|
||||
@@ -223,6 +235,14 @@ class DynamicModelTests: BaseTestDataTestCase {
|
||||
let willSetPriorObserverDone = self.expectation(description: "willSet-observe-prior-done")
|
||||
let willSetNotPriorObserverDone = self.expectation(description: "willSet-observe-notPrior-done")
|
||||
let didSetObserverDone = self.expectation(description: "didSet-observe-done")
|
||||
|
||||
let ownerInfos: [Person.OwnerInfo] = (1...3).map {
|
||||
.init(
|
||||
id: .init(),
|
||||
displayName: "ownerInfo_\($0)",
|
||||
otherIDs: Set((1...3).map({ _ in .init() }))
|
||||
)
|
||||
}
|
||||
stack.perform(
|
||||
asynchronous: { (transaction) in
|
||||
|
||||
@@ -373,11 +393,19 @@ class DynamicModelTests: BaseTestDataTestCase {
|
||||
person.job = .engineer
|
||||
XCTAssertEqual(person.job, .engineer)
|
||||
|
||||
person.ownerInfosJson = ownerInfos
|
||||
XCTAssertEqual(person.ownerInfosJson, ownerInfos)
|
||||
|
||||
person.ownerInfosPlist = ownerInfos
|
||||
XCTAssertEqual(person.ownerInfosPlist, ownerInfos)
|
||||
|
||||
let personSnapshot2 = person.asSnapshot(in: transaction)!
|
||||
XCTAssertEqual(person.name, personSnapshot2.$name)
|
||||
XCTAssertEqual(person.title, personSnapshot2.$title)
|
||||
XCTAssertEqual(person.displayName, personSnapshot2.$displayName)
|
||||
XCTAssertEqual(person.job, personSnapshot2.$job)
|
||||
XCTAssertEqual(person.ownerInfosJson, personSnapshot2.$ownerInfosJson)
|
||||
XCTAssertEqual(person.ownerInfosPlist, personSnapshot2.$ownerInfosPlist)
|
||||
|
||||
var personSnapshot3 = personSnapshot2
|
||||
personSnapshot3.$name = "James"
|
||||
@@ -390,6 +418,8 @@ class DynamicModelTests: BaseTestDataTestCase {
|
||||
XCTAssertEqual(personSnapshot3.$name, "James")
|
||||
XCTAssertEqual(personSnapshot3.$displayName, "Sir John")
|
||||
XCTAssertEqual(personSnapshot3.$job, .engineer)
|
||||
XCTAssertEqual(personSnapshot3.$ownerInfosJson, ownerInfos)
|
||||
XCTAssertEqual(personSnapshot3.$ownerInfosPlist, ownerInfos)
|
||||
|
||||
|
||||
|
||||
@@ -409,6 +439,8 @@ class DynamicModelTests: BaseTestDataTestCase {
|
||||
XCTAssertEqual(personPublisher.$name, "John")
|
||||
XCTAssertEqual(personPublisher.$displayName, "Sir John")
|
||||
XCTAssertEqual(personPublisher.$job, .engineer)
|
||||
XCTAssertEqual(personPublisher.$ownerInfosJson, ownerInfos)
|
||||
XCTAssertEqual(personPublisher.$ownerInfosPlist, ownerInfos)
|
||||
|
||||
updateDone.fulfill()
|
||||
},
|
||||
@@ -444,6 +476,8 @@ class DynamicModelTests: BaseTestDataTestCase {
|
||||
XCTAssertEqual(person!.customField.string, "customString")
|
||||
XCTAssertEqual(person!.job, .engineer)
|
||||
XCTAssertEqual(person!.pets.first, dog)
|
||||
XCTAssertEqual(person!.ownerInfosJson, ownerInfos)
|
||||
XCTAssertEqual(person!.ownerInfosPlist, ownerInfos)
|
||||
|
||||
let p3 = Where<Dog>({ $0.$age == 10 })
|
||||
XCTAssertEqual(p3.predicate, NSPredicate(format: "%K == %d", "age", 10))
|
||||
|
||||
@@ -63,6 +63,7 @@ final class GroupByTests: BaseTestCase {
|
||||
}
|
||||
|
||||
@objc
|
||||
@MainActor
|
||||
dynamic func test_ThatGroupByClauses_ApplyToFetchRequestsCorrectly() {
|
||||
|
||||
self.prepareStack { (dataStack) in
|
||||
|
||||
@@ -36,6 +36,7 @@ import CoreStore
|
||||
class ListPublisherTests: BaseTestDataTestCase {
|
||||
|
||||
@objc
|
||||
@MainActor
|
||||
dynamic func test_ThatListPublishers_CanReceiveInsertNotifications() {
|
||||
|
||||
self.prepareStack { (stack) in
|
||||
@@ -62,7 +63,7 @@ class ListPublisherTests: BaseTestDataTestCase {
|
||||
|
||||
let saveExpectation = self.expectation(description: "save")
|
||||
stack.perform(
|
||||
asynchronous: { (transaction) -> Bool in
|
||||
asynchronous: { [dateFormatter] (transaction) -> Bool in
|
||||
|
||||
let object = transaction.create(Into<TestEntity1>())
|
||||
object.testBoolean = NSNumber(value: true)
|
||||
@@ -70,7 +71,7 @@ class ListPublisherTests: BaseTestDataTestCase {
|
||||
object.testDecimal = NSDecimalNumber(string: "1")
|
||||
object.testString = "nil:TestEntity1:1"
|
||||
object.testData = ("nil:TestEntity1:1" as NSString).data(using: String.Encoding.utf8.rawValue)!
|
||||
object.testDate = self.dateFormatter.date(from: "2000-01-01T00:00:00Z")!
|
||||
object.testDate = dateFormatter.date(from: "2000-01-01T00:00:00Z")!
|
||||
|
||||
return transaction.hasChanges
|
||||
},
|
||||
@@ -92,6 +93,7 @@ class ListPublisherTests: BaseTestDataTestCase {
|
||||
}
|
||||
|
||||
@objc
|
||||
@MainActor
|
||||
dynamic func test_ThatListPublishers_CanReceiveUpdateNotifications() {
|
||||
|
||||
self.prepareStack { (stack) in
|
||||
@@ -126,7 +128,7 @@ class ListPublisherTests: BaseTestDataTestCase {
|
||||
|
||||
let saveExpectation = self.expectation(description: "save")
|
||||
stack.perform(
|
||||
asynchronous: { (transaction) -> Bool in
|
||||
asynchronous: { [dateFormatter] (transaction) -> Bool in
|
||||
|
||||
if let object = try transaction.fetchOne(
|
||||
From<TestEntity1>(),
|
||||
@@ -136,7 +138,7 @@ class ListPublisherTests: BaseTestDataTestCase {
|
||||
object.testDecimal = NSDecimalNumber(string: "11")
|
||||
object.testString = "nil:TestEntity1:11"
|
||||
object.testData = ("nil:TestEntity1:11" as NSString).data(using: String.Encoding.utf8.rawValue)!
|
||||
object.testDate = self.dateFormatter.date(from: "2000-01-11T00:00:00Z")!
|
||||
object.testDate = dateFormatter.date(from: "2000-01-11T00:00:00Z")!
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -150,7 +152,7 @@ class ListPublisherTests: BaseTestDataTestCase {
|
||||
object.testDecimal = NSDecimalNumber(string: "22")
|
||||
object.testString = "nil:TestEntity1:22"
|
||||
object.testData = ("nil:TestEntity1:22" as NSString).data(using: String.Encoding.utf8.rawValue)!
|
||||
object.testDate = self.dateFormatter.date(from: "2000-01-22T00:00:00Z")!
|
||||
object.testDate = dateFormatter.date(from: "2000-01-22T00:00:00Z")!
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -176,6 +178,7 @@ class ListPublisherTests: BaseTestDataTestCase {
|
||||
}
|
||||
|
||||
@objc
|
||||
@MainActor
|
||||
dynamic func test_ThatListPublishers_CanReceiveMoveNotifications() {
|
||||
|
||||
self.prepareStack { (stack) in
|
||||
@@ -242,6 +245,7 @@ class ListPublisherTests: BaseTestDataTestCase {
|
||||
}
|
||||
|
||||
@objc
|
||||
@MainActor
|
||||
dynamic func test_ThatListPublishers_CanReceiveDeleteNotifications() {
|
||||
|
||||
self.prepareStack { (stack) in
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
//
|
||||
|
||||
import XCTest
|
||||
import os
|
||||
|
||||
@testable
|
||||
import CoreStore
|
||||
@@ -34,6 +35,7 @@ import CoreStore
|
||||
class ObjectObserverTests: BaseTestDataTestCase {
|
||||
|
||||
@objc
|
||||
@MainActor
|
||||
dynamic func test_ThatObjectObservers_CanReceiveUpdateNotifications() {
|
||||
|
||||
self.prepareStack { (stack) in
|
||||
@@ -54,13 +56,16 @@ class ObjectObserverTests: BaseTestDataTestCase {
|
||||
XCTAssertEqual(monitor.object, object)
|
||||
XCTAssertFalse(monitor.isObjectDeleted)
|
||||
|
||||
var events = 0
|
||||
let events: OSAllocatedUnfairLock<Int> = .init(initialState: 0)
|
||||
|
||||
_ = self.expectation(
|
||||
forNotification: NSNotification.Name(rawValue: "objectMonitor:willUpdateObject:"),
|
||||
object: observer,
|
||||
handler: { (note) -> Bool in
|
||||
|
||||
nonisolated(unsafe) let note = note
|
||||
return events.withLock { events in
|
||||
|
||||
XCTAssertEqual(events, 0)
|
||||
XCTAssertEqual(
|
||||
((note.userInfo as NSDictionary?) ?? [:]),
|
||||
@@ -72,12 +77,16 @@ class ObjectObserverTests: BaseTestDataTestCase {
|
||||
}
|
||||
return events == 0
|
||||
}
|
||||
}
|
||||
)
|
||||
_ = self.expectation(
|
||||
forNotification: NSNotification.Name(rawValue: "objectMonitor:didUpdateObject:changedPersistentKeys:"),
|
||||
object: observer,
|
||||
handler: { (note) -> Bool in
|
||||
|
||||
nonisolated(unsafe) let note = note
|
||||
return events.withLock { events in
|
||||
|
||||
XCTAssertEqual(events, 1)
|
||||
XCTAssertEqual(
|
||||
((note.userInfo as NSDictionary?) ?? [:]),
|
||||
@@ -101,6 +110,7 @@ class ObjectObserverTests: BaseTestDataTestCase {
|
||||
}
|
||||
return events == 1
|
||||
}
|
||||
}
|
||||
)
|
||||
let saveExpectation = self.expectation(description: "save")
|
||||
stack.perform(
|
||||
@@ -131,6 +141,7 @@ class ObjectObserverTests: BaseTestDataTestCase {
|
||||
}
|
||||
|
||||
@objc
|
||||
@MainActor
|
||||
dynamic func test_ThatObjectObservers_CanReceiveDeleteNotifications() {
|
||||
|
||||
self.prepareStack { (stack) in
|
||||
@@ -151,13 +162,16 @@ class ObjectObserverTests: BaseTestDataTestCase {
|
||||
XCTAssertEqual(monitor.object, object)
|
||||
XCTAssertFalse(monitor.isObjectDeleted)
|
||||
|
||||
var events = 0
|
||||
let events: OSAllocatedUnfairLock<Int> = .init(initialState: 0)
|
||||
|
||||
_ = self.expectation(
|
||||
forNotification: NSNotification.Name(rawValue: "objectMonitor:didDeleteObject:"),
|
||||
object: observer,
|
||||
handler: { (note) -> Bool in
|
||||
|
||||
nonisolated(unsafe) let note = note
|
||||
return events.withLock { events in
|
||||
|
||||
XCTAssertEqual(events, 0)
|
||||
XCTAssertEqual(
|
||||
((note.userInfo as NSDictionary?) ?? [:]),
|
||||
@@ -169,6 +183,7 @@ class ObjectObserverTests: BaseTestDataTestCase {
|
||||
}
|
||||
return events == 0
|
||||
}
|
||||
}
|
||||
)
|
||||
let saveExpectation = self.expectation(description: "save")
|
||||
stack.perform(
|
||||
@@ -202,7 +217,7 @@ class ObjectObserverTests: BaseTestDataTestCase {
|
||||
|
||||
// MARK: TestObjectObserver
|
||||
|
||||
class TestObjectObserver: ObjectObserver {
|
||||
final class TestObjectObserver: ObjectObserver {
|
||||
|
||||
typealias ObjectEntityType = TestEntity1
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ import CoreStore
|
||||
class ObjectPublisherTests: BaseTestDataTestCase {
|
||||
|
||||
@objc
|
||||
@MainActor
|
||||
dynamic func test_ThatObjectPublishers_CanReceiveUpdateNotifications() {
|
||||
|
||||
self.prepareStack { (stack) in
|
||||
@@ -93,6 +94,7 @@ class ObjectPublisherTests: BaseTestDataTestCase {
|
||||
}
|
||||
|
||||
@objc
|
||||
@MainActor
|
||||
dynamic func test_ThatObjectPublishers_CanReceiveDeleteNotifications() {
|
||||
|
||||
self.prepareStack { (stack) in
|
||||
|
||||
@@ -35,6 +35,7 @@ import CoreStore
|
||||
class SetupTests: BaseTestDataTestCase {
|
||||
|
||||
@objc
|
||||
@MainActor
|
||||
dynamic func test_ThatDataStacks_ConfigureCorrectly() {
|
||||
|
||||
do {
|
||||
|
||||
@@ -74,7 +74,7 @@ extension Modern {
|
||||
|
||||
// MARK: - TransactionSource
|
||||
|
||||
enum TransactionSource {
|
||||
enum TransactionSource: Sendable {
|
||||
|
||||
case add
|
||||
case delete
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@ import PackageDescription
|
||||
let package = Package(
|
||||
name: "CoreStore",
|
||||
platforms: [
|
||||
.macOS(.v13), .iOS(.v16), .tvOS(.v16), .watchOS(.v9)
|
||||
.macOS(.v14), .iOS(.v17), .tvOS(.v17), .watchOS(.v10)
|
||||
],
|
||||
products: [
|
||||
.library(name: "CoreStore", targets: ["CoreStore"])
|
||||
|
||||
@@ -175,7 +175,7 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
|
||||
internal init(
|
||||
mainContext: NSManagedObjectContext,
|
||||
queue: DispatchQueue,
|
||||
sourceIdentifier: Any?
|
||||
sourceIdentifier: (any Sendable)?
|
||||
) {
|
||||
|
||||
super.init(
|
||||
@@ -188,7 +188,7 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
|
||||
}
|
||||
|
||||
internal func autoCommit(
|
||||
_ completion: @escaping (
|
||||
_ completion: @escaping @MainActor (
|
||||
_ hasChanges: Bool,
|
||||
_ error: CoreStoreError?
|
||||
) -> Void
|
||||
@@ -197,12 +197,14 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
|
||||
self.isCommitted = true
|
||||
let group = DispatchGroup()
|
||||
group.enter()
|
||||
|
||||
nonisolated(unsafe) let transaction = self
|
||||
self.context.saveAsynchronously(
|
||||
sourceIdentifier: self.sourceIdentifier,
|
||||
completion: { (hasChanges, error) -> Void in
|
||||
|
||||
completion(hasChanges, error)
|
||||
self.result = (hasChanges, error)
|
||||
transaction.result = (hasChanges, error)
|
||||
group.leave()
|
||||
}
|
||||
)
|
||||
|
||||
@@ -441,7 +441,7 @@ public /*abstract*/ class BaseDataTransaction {
|
||||
/**
|
||||
An arbitrary value that identifies the source of this transaction. Callers of the transaction can provide this value through the `DataStack.perform(...)` methods.
|
||||
*/
|
||||
public let sourceIdentifier: Any?
|
||||
public let sourceIdentifier: (any Sendable)?
|
||||
|
||||
/**
|
||||
Allow external libraries to store custom data in the transaction. App code should rarely have a need for this.
|
||||
@@ -471,7 +471,7 @@ public /*abstract*/ class BaseDataTransaction {
|
||||
queue: DispatchQueue,
|
||||
supportsUndo: Bool,
|
||||
bypassesQueueing: Bool,
|
||||
sourceIdentifier: Any?
|
||||
sourceIdentifier: (any Sendable)?
|
||||
) {
|
||||
|
||||
let context = mainContext.temporaryContextInTransactionWithConcurrencyType(
|
||||
@@ -493,9 +493,12 @@ public /*abstract*/ class BaseDataTransaction {
|
||||
}
|
||||
else if context.undoManager == nil {
|
||||
|
||||
Internals.mainActorImmediate {
|
||||
|
||||
context.undoManager = UndoManager()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal func isRunningInAllowedQueue() -> Bool {
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os
|
||||
|
||||
|
||||
// MARK: - CoreStoreDefaults
|
||||
@@ -36,7 +37,29 @@ public enum CoreStoreDefaults {
|
||||
/**
|
||||
The `CoreStoreLogger` instance to be used. The default logger is an instance of a `DefaultLogger`.
|
||||
*/
|
||||
public static var logger: CoreStoreLogger = DefaultLogger()
|
||||
public static var logger: CoreStoreLogger {
|
||||
|
||||
get {
|
||||
|
||||
return self.loggerInstance.withLock {
|
||||
|
||||
if let logger = $0 {
|
||||
|
||||
return logger
|
||||
}
|
||||
let logger = DefaultLogger()
|
||||
$0 = logger
|
||||
return logger
|
||||
}
|
||||
}
|
||||
set {
|
||||
|
||||
self.loggerInstance.withLock {
|
||||
|
||||
$0 = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
The default `DataStack` instance to be used. If `defaultStack` is not set during the first time accessed, a default-configured `DataStack` will be created.
|
||||
@@ -48,20 +71,22 @@ public enum CoreStoreDefaults {
|
||||
|
||||
get {
|
||||
|
||||
self.defaultStackBarrierQueue.sync(flags: .barrier) {
|
||||
return self.defaultStackInstance.withLock {
|
||||
|
||||
if self.defaultStackInstance == nil {
|
||||
if let dataStack = $0 {
|
||||
|
||||
self.defaultStackInstance = DataStack()
|
||||
return dataStack
|
||||
}
|
||||
let dataStack = DataStack()
|
||||
$0 = dataStack
|
||||
return dataStack
|
||||
}
|
||||
return self.defaultStackInstance!
|
||||
}
|
||||
set {
|
||||
|
||||
self.defaultStackBarrierQueue.async(flags: .barrier) {
|
||||
self.defaultStackInstance.withLock {
|
||||
|
||||
self.defaultStackInstance = newValue
|
||||
$0 = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,7 +94,6 @@ public enum CoreStoreDefaults {
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private static let defaultStackBarrierQueue = DispatchQueue.concurrent("com.coreStore.defaultStackBarrierQueue", qos: .userInteractive)
|
||||
|
||||
private static var defaultStackInstance: DataStack?
|
||||
private static let defaultStackInstance: OSAllocatedUnfairLock<DataStack?> = .init(initialState: nil)
|
||||
private static let loggerInstance: OSAllocatedUnfairLock<(any CoreStoreLogger)?> = .init(initialState: nil)
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
@preconcurrency import CoreData
|
||||
|
||||
|
||||
// MARK: - CoreStoreError
|
||||
@@ -32,7 +32,7 @@ import CoreData
|
||||
/**
|
||||
All errors thrown from CoreStore are expressed in `CoreStoreError` enum values.
|
||||
*/
|
||||
public enum CoreStoreError: Error, CustomNSError, Hashable {
|
||||
public enum CoreStoreError: Error, CustomNSError, Hashable, @unchecked Sendable {
|
||||
|
||||
/**
|
||||
A failure occured because of an unknown error.
|
||||
|
||||
@@ -45,7 +45,7 @@ public enum LogLevel {
|
||||
/**
|
||||
Custom loggers should implement the `CoreStoreLogger` protocol and pass its instance to `CoreStoreDefaults.logger`. Calls to `log(...)`, `assert(...)`, and `abort(...)` are not tied to a specific queue/thread, so it is the implementer's job to handle thread-safety.
|
||||
*/
|
||||
public protocol CoreStoreLogger {
|
||||
public protocol CoreStoreLogger: Sendable {
|
||||
|
||||
/**
|
||||
Handles log messages sent by the `CoreStore` framework.
|
||||
|
||||
@@ -29,7 +29,8 @@ import Foundation
|
||||
|
||||
// MARK: - CoreStoreManagedObject
|
||||
|
||||
@objc internal class CoreStoreManagedObject: NSManagedObject {
|
||||
@objc
|
||||
internal class CoreStoreManagedObject: NSManagedObject {
|
||||
|
||||
internal typealias CustomGetter = @convention(block) (_ rawObject: Any) -> Any?
|
||||
internal typealias CustomSetter = @convention(block) (_ rawObject: Any, _ newValue: Any?) -> Void
|
||||
@@ -42,12 +43,3 @@ import Foundation
|
||||
return "_\(NSStringFromClass(CoreStoreManagedObject.self))__\(modelVersion)__\(NSStringFromClass(entity.type))__\(entity.entityName)"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private enum Static {
|
||||
|
||||
static let queue = DispatchQueue.concurrent("com.coreStore.coreStoreManagerObjectBarrierQueue", qos: .userInteractive)
|
||||
static var cache: [ObjectIdentifier: [KeyPathString: Set<KeyPathString>]] = [:]
|
||||
}
|
||||
|
||||
@@ -439,7 +439,7 @@ fileprivate final class _CoreStoreObjectKeyValueObservation: NSObject, CoreStore
|
||||
|
||||
// workaround for <rdar://problem/31640524> Erroneous (?) error when using bridging in the Foundation overlay
|
||||
@nonobjc
|
||||
static var swizzler: Any? = Internals.with {
|
||||
static let swizzler: Void? = Internals.with {
|
||||
|
||||
let bridgeClass: AnyClass = _CoreStoreObjectKeyValueObservation.self
|
||||
let rootObserveImpl = class_getInstanceMethod(
|
||||
|
||||
@@ -266,6 +266,6 @@ fileprivate enum Static {
|
||||
|
||||
// MARK: FilePrivate
|
||||
|
||||
fileprivate static var metaCache: [ObjectIdentifier: Any] = [:]
|
||||
fileprivate static var propertiesCache: [ObjectIdentifier: [PropertyProtocol]] = [:]
|
||||
fileprivate static nonisolated(unsafe) var metaCache: [ObjectIdentifier: Any] = [:]
|
||||
fileprivate static nonisolated(unsafe) var propertiesCache: [ObjectIdentifier: [PropertyProtocol]] = [:]
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ extension DataStack {
|
||||
/**
|
||||
Swift concurrency for the `DataStack` are exposed through this namespace. Extend this type if you need to add other `async` utilities for `DataStack`.
|
||||
*/
|
||||
public struct AsyncNamespace {
|
||||
public struct AsyncNamespace: Sendable {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
@@ -124,7 +124,7 @@ extension DataStack.AsyncNamespace {
|
||||
bufferingPolicy: .unbounded,
|
||||
{ continuation in
|
||||
|
||||
var progress: Progress? = nil
|
||||
nonisolated(unsafe) var progress: Progress? = nil
|
||||
progress = self.base.addStorage(
|
||||
storage,
|
||||
completion: { result in
|
||||
@@ -151,6 +151,8 @@ extension DataStack.AsyncNamespace {
|
||||
)
|
||||
if let progress = progress {
|
||||
|
||||
Internals.mainActorImmediate { @MainActor in
|
||||
|
||||
progress.setProgressHandler { progress in
|
||||
|
||||
continuation.yield(
|
||||
@@ -162,6 +164,7 @@ extension DataStack.AsyncNamespace {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -185,7 +188,7 @@ extension DataStack.AsyncNamespace {
|
||||
|
||||
return try await Internals.withCheckedThrowingContinuation { continuation in
|
||||
|
||||
self.base.perform(
|
||||
return self.base.perform(
|
||||
asynchronous: { (transaction) -> O? in
|
||||
|
||||
return try transaction.importObject(
|
||||
@@ -196,10 +199,13 @@ extension DataStack.AsyncNamespace {
|
||||
success: {
|
||||
|
||||
continuation.resume(
|
||||
with: .success($0.flatMap(self.base.fetchExisting))
|
||||
returning: $0.flatMap(self.base.fetchExisting)
|
||||
)
|
||||
},
|
||||
failure: continuation.resume(throwing:)
|
||||
failure: {
|
||||
|
||||
continuation.resume(throwing: $0)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -222,6 +228,7 @@ extension DataStack.AsyncNamespace {
|
||||
source: O.ImportSource
|
||||
) async throws(any Swift.Error) -> O? {
|
||||
|
||||
nonisolated(unsafe) let object = object
|
||||
return try await Internals.withCheckedThrowingContinuation { continuation in
|
||||
|
||||
self.base.perform(
|
||||
@@ -240,10 +247,13 @@ extension DataStack.AsyncNamespace {
|
||||
success: {
|
||||
|
||||
continuation.resume(
|
||||
with: .success($0.flatMap(self.base.fetchExisting))
|
||||
returning: $0.flatMap(self.base.fetchExisting)
|
||||
)
|
||||
},
|
||||
failure: continuation.resume(throwing:)
|
||||
failure: {
|
||||
|
||||
continuation.resume(throwing: $0)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -279,10 +289,13 @@ extension DataStack.AsyncNamespace {
|
||||
success: {
|
||||
|
||||
continuation.resume(
|
||||
with: .success($0.flatMap(self.base.fetchExisting))
|
||||
returning: $0.flatMap(self.base.fetchExisting)
|
||||
)
|
||||
},
|
||||
failure: continuation.resume(throwing:)
|
||||
failure: {
|
||||
|
||||
continuation.resume(throwing: $0)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -306,7 +319,7 @@ extension DataStack.AsyncNamespace {
|
||||
- returns: The imported objects correctly associated for the `DataStack`.
|
||||
- throws: A `CoreStoreError` value indicating the failure reason
|
||||
*/
|
||||
public func importUniqueObjects<O: DynamicObject & ImportableUniqueObject, S: Sequence>(
|
||||
public func importUniqueObjects<O: DynamicObject & ImportableUniqueObject, S: Sequence & Sendable>(
|
||||
_ into: Into<O>,
|
||||
sourceArray: S,
|
||||
preProcess: @escaping @Sendable (
|
||||
@@ -329,10 +342,13 @@ extension DataStack.AsyncNamespace {
|
||||
success: {
|
||||
|
||||
continuation.resume(
|
||||
with: .success(self.base.fetchExisting($0))
|
||||
returning: self.base.fetchExisting($0)
|
||||
)
|
||||
},
|
||||
failure: continuation.resume(throwing:)
|
||||
failure: {
|
||||
|
||||
continuation.resume(throwing: $0)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -357,7 +373,7 @@ extension DataStack.AsyncNamespace {
|
||||
- returns: The value returned from the `task` closure.
|
||||
- throws: A `CoreStoreError` value indicating the failure reason
|
||||
*/
|
||||
public func perform<Output>(
|
||||
public func perform<Output: Sendable>(
|
||||
_ asynchronous: @escaping @Sendable (AsynchronousDataTransaction) throws(any Swift.Error) -> Output
|
||||
) async throws(any Swift.Error) -> Output {
|
||||
|
||||
|
||||
@@ -24,7 +24,8 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
@preconcurrency import CoreData
|
||||
import os
|
||||
|
||||
|
||||
// MARK: - DataStack
|
||||
@@ -49,7 +50,7 @@ extension DataStack {
|
||||
*/
|
||||
public func addStorage<T>(
|
||||
_ storage: T,
|
||||
completion: @escaping (SetupResult<T>) -> Void
|
||||
completion: @escaping @MainActor (SetupResult<T>) -> Void
|
||||
) {
|
||||
|
||||
self.coordinator.performAsynchronously {
|
||||
@@ -110,7 +111,7 @@ extension DataStack {
|
||||
*/
|
||||
public func addStorage<T: LocalStorage>(
|
||||
_ storage: T,
|
||||
completion: @escaping (SetupResult<T>) -> Void
|
||||
completion: @escaping @MainActor (SetupResult<T>) -> Void
|
||||
) -> Progress? {
|
||||
|
||||
let fileURL = storage.fileURL
|
||||
@@ -162,7 +163,7 @@ extension DataStack {
|
||||
attributes: nil
|
||||
)
|
||||
|
||||
let metadata = try NSPersistentStoreCoordinator.metadataForPersistentStore(
|
||||
nonisolated(unsafe) let metadata = try NSPersistentStoreCoordinator.metadataForPersistentStore(
|
||||
ofType: type(of: storage).storeType,
|
||||
at: fileURL as URL,
|
||||
options: storage.storeOptions
|
||||
@@ -192,12 +193,18 @@ extension DataStack {
|
||||
}
|
||||
catch {
|
||||
|
||||
DispatchQueue.main.async {
|
||||
|
||||
completion(.failure(CoreStoreError(error)))
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
|
||||
completion(.failure(CoreStoreError(error)))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -212,9 +219,12 @@ extension DataStack {
|
||||
}
|
||||
catch {
|
||||
|
||||
DispatchQueue.main.async {
|
||||
|
||||
completion(.failure(CoreStoreError(error)))
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
catch let error as NSError
|
||||
@@ -264,7 +274,7 @@ extension DataStack {
|
||||
*/
|
||||
public func upgradeStorageIfNeeded<T: LocalStorage>(
|
||||
_ storage: T,
|
||||
completion: @escaping (MigrationResult) -> Void
|
||||
completion: @escaping @MainActor (MigrationResult) -> Void
|
||||
) throws(CoreStoreError) -> Progress? {
|
||||
|
||||
return try self.coordinator.performSynchronously {
|
||||
@@ -376,7 +386,7 @@ extension DataStack {
|
||||
private func upgradeStorageIfNeeded<T: LocalStorage>(
|
||||
_ storage: T,
|
||||
metadata: [String: Any],
|
||||
completion: @escaping (MigrationResult) -> Void
|
||||
completion: @escaping @MainActor (MigrationResult) -> Void
|
||||
) -> Progress? {
|
||||
|
||||
guard let migrationSteps = self.computeMigrationFromStorage(storage, metadata: metadata) else {
|
||||
@@ -423,9 +433,13 @@ extension DataStack {
|
||||
}
|
||||
|
||||
let migrationTypes = migrationSteps.map { $0.migrationType }
|
||||
var migrationResult: MigrationResult?
|
||||
let migrationState: OSAllocatedUnfairLock<(migrationResult: MigrationResult?, cancelled: Bool)> = .init(
|
||||
initialState: (
|
||||
migrationResult: nil,
|
||||
cancelled: false
|
||||
)
|
||||
)
|
||||
var operations = [Operation]()
|
||||
var cancelled = false
|
||||
|
||||
let progress = Progress(parent: nil, userInfo: nil)
|
||||
progress.totalUnitCount = numberOfMigrations
|
||||
@@ -440,12 +454,15 @@ extension DataStack {
|
||||
operations.append(
|
||||
BlockOperation { [weak self] in
|
||||
|
||||
guard let self = self, !cancelled else {
|
||||
guard
|
||||
let self = self,
|
||||
!migrationState.withLock({ $0.cancelled })
|
||||
else {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
autoreleasepool {
|
||||
Internals.autoreleasepool {
|
||||
|
||||
do {
|
||||
|
||||
@@ -465,8 +482,14 @@ extension DataStack {
|
||||
migrationError,
|
||||
"Failed to migrate version model \"\(migrationType.sourceVersion)\" to version \"\(migrationType.destinationVersion)\"."
|
||||
)
|
||||
migrationResult = .failure(migrationError)
|
||||
cancelled = true
|
||||
migrationState.withLock { state in
|
||||
|
||||
if !state.cancelled {
|
||||
|
||||
state.migrationResult = .failure(migrationError)
|
||||
state.cancelled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -488,7 +511,10 @@ extension DataStack {
|
||||
DispatchQueue.main.async {
|
||||
|
||||
progress.setProgressHandler(nil)
|
||||
completion(migrationResult ?? .success(migrationTypes))
|
||||
completion(
|
||||
migrationState.withLock { $0.migrationResult }
|
||||
?? .success(migrationTypes)
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -594,23 +620,29 @@ extension DataStack {
|
||||
let estimatedTime: TimeInterval = 60 * 3 // 3 mins
|
||||
let interval: TimeInterval = 1
|
||||
let fakeTotalUnitCount: Float = 0.9 * Float(progress.totalUnitCount)
|
||||
var fakeProgress: Float = 0
|
||||
let fakeProgress: OSAllocatedUnfairLock<Float> = .init(initialState: 0)
|
||||
|
||||
var recursiveCheck: () -> Void = {}
|
||||
recursiveCheck = { [weak timerQueue] in
|
||||
@Sendable
|
||||
func recursiveCheck() {
|
||||
|
||||
guard let timerQueue = timerQueue, fakeProgress < 1 else {
|
||||
let enqueueNext = fakeProgress.withLock {
|
||||
|
||||
return
|
||||
guard $0 < 1.0 else {
|
||||
|
||||
return false
|
||||
}
|
||||
progress.completedUnitCount = Int64(fakeTotalUnitCount * fakeProgress)
|
||||
fakeProgress += Float(interval / estimatedTime)
|
||||
progress.completedUnitCount = Int64(fakeTotalUnitCount * $0)
|
||||
$0 += Float(interval / estimatedTime)
|
||||
return true
|
||||
}
|
||||
if enqueueNext {
|
||||
|
||||
timerQueue.asyncAfter(
|
||||
deadline: .now() + interval,
|
||||
execute: recursiveCheck
|
||||
)
|
||||
}
|
||||
}
|
||||
timerQueue.async(execute: recursiveCheck)
|
||||
|
||||
_ = try withExtendedLifetime(NSPersistentStoreCoordinator(managedObjectModel: destinationModel)) { (coordinator: NSPersistentStoreCoordinator) in
|
||||
@@ -626,7 +658,7 @@ extension DataStack {
|
||||
}
|
||||
timerQueue.sync {
|
||||
|
||||
fakeProgress = 1
|
||||
fakeProgress.withLock({ $0 = 1.0 })
|
||||
}
|
||||
_ = try? storage.cs_finalizeStorageAndWait(soureModelHint: destinationModel)
|
||||
progress.completedUnitCount = progress.totalUnitCount
|
||||
|
||||
@@ -125,7 +125,7 @@ extension DataStack {
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
*/
|
||||
public func monitorList<O>(
|
||||
createAsynchronously: @escaping (ListMonitor<O>) -> Void,
|
||||
createAsynchronously: @escaping @Sendable (ListMonitor<O>) -> Void,
|
||||
_ from: From<O>,
|
||||
_ fetchClauses: FetchClause...
|
||||
) {
|
||||
@@ -145,7 +145,7 @@ extension DataStack {
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
*/
|
||||
public func monitorList<O>(
|
||||
createAsynchronously: @escaping (ListMonitor<O>) -> Void,
|
||||
createAsynchronously: @escaping @Sendable (ListMonitor<O>) -> Void,
|
||||
_ from: From<O>,
|
||||
_ fetchClauses: [FetchClause]
|
||||
) {
|
||||
@@ -188,7 +188,7 @@ extension DataStack {
|
||||
- parameter clauseChain: a `FetchChainableBuilderType` built from a chain of clauses
|
||||
*/
|
||||
public func monitorList<B: FetchChainableBuilderType>(
|
||||
createAsynchronously: @escaping (ListMonitor<B.ObjectType>) -> Void,
|
||||
createAsynchronously: @escaping @Sendable (ListMonitor<B.ObjectType>) -> Void,
|
||||
_ clauseChain: B
|
||||
) {
|
||||
|
||||
@@ -288,7 +288,7 @@ extension DataStack {
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
*/
|
||||
public func monitorSectionedList<O>(
|
||||
createAsynchronously: @escaping (ListMonitor<O>) -> Void,
|
||||
createAsynchronously: @escaping @Sendable (ListMonitor<O>) -> Void,
|
||||
_ from: From<O>,
|
||||
_ sectionBy: SectionBy<O>,
|
||||
_ fetchClauses: FetchClause...
|
||||
@@ -311,7 +311,7 @@ extension DataStack {
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
*/
|
||||
public func monitorSectionedList<O>(
|
||||
createAsynchronously: @escaping (ListMonitor<O>) -> Void,
|
||||
createAsynchronously: @escaping @Sendable (ListMonitor<O>) -> Void,
|
||||
_ from: From<O>,
|
||||
_ sectionBy: SectionBy<O>,
|
||||
_ fetchClauses: [FetchClause]
|
||||
@@ -356,7 +356,7 @@ extension DataStack {
|
||||
- parameter clauseChain: a `SectionMonitorBuilderType` built from a chain of clauses
|
||||
*/
|
||||
public func monitorSectionedList<B: SectionMonitorBuilderType>(
|
||||
createAsynchronously: @escaping (ListMonitor<B.ObjectType>) -> Void,
|
||||
createAsynchronously: @escaping @Sendable (ListMonitor<B.ObjectType>) -> Void,
|
||||
_ clauseChain: B
|
||||
) {
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ extension DataStack {
|
||||
/**
|
||||
Combine utilities for the `DataStack` are exposed through this namespace. Extend this type if you need to add other Combine Publisher utilities for `DataStack`.
|
||||
*/
|
||||
public struct ReactiveNamespace {
|
||||
public struct ReactiveNamespace: Sendable {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
@@ -99,6 +99,7 @@ extension DataStack.ReactiveNamespace {
|
||||
|
||||
return .init { (promise) in
|
||||
|
||||
nonisolated(unsafe) let promise = promise
|
||||
self.base.addStorage(
|
||||
storage,
|
||||
completion: { (result) in
|
||||
@@ -179,6 +180,7 @@ extension DataStack.ReactiveNamespace {
|
||||
|
||||
return .init { (promise) in
|
||||
|
||||
nonisolated(unsafe) let promise = promise
|
||||
self.base.perform(
|
||||
asynchronous: { (transaction) -> O? in
|
||||
|
||||
@@ -222,8 +224,10 @@ extension DataStack.ReactiveNamespace {
|
||||
source: O.ImportSource
|
||||
) -> Future<O?, CoreStoreError> {
|
||||
|
||||
nonisolated(unsafe) let object = object
|
||||
return .init { (promise) in
|
||||
|
||||
nonisolated(unsafe) let promise = promise
|
||||
self.base.perform(
|
||||
asynchronous: { (transaction) -> O? in
|
||||
|
||||
@@ -273,6 +277,7 @@ extension DataStack.ReactiveNamespace {
|
||||
|
||||
return .init { (promise) in
|
||||
|
||||
nonisolated(unsafe) let promise = promise
|
||||
self.base.perform(
|
||||
asynchronous: { (transaction) -> O? in
|
||||
|
||||
@@ -316,16 +321,17 @@ extension DataStack.ReactiveNamespace {
|
||||
- parameter preProcess: a closure that lets the caller tweak the internal `UniqueIDType`-to-`ImportSource` mapping to be used for importing. Callers can remove from/add to/update `mapping` and return the updated array from the closure.
|
||||
- returns: A `Future` for the imported objects. The event values will be the object instances correctly associated for the `DataStack`.
|
||||
*/
|
||||
public func importUniqueObjects<O: DynamicObject & ImportableUniqueObject, S: Sequence>(
|
||||
public func importUniqueObjects<O: DynamicObject & ImportableUniqueObject, S: Sequence & Sendable>(
|
||||
_ into: Into<O>,
|
||||
sourceArray: S,
|
||||
preProcess: @escaping (
|
||||
preProcess: @escaping @Sendable (
|
||||
_ mapping: [O.UniqueIDType: O.ImportSource]
|
||||
) throws(any Swift.Error) -> [O.UniqueIDType: O.ImportSource] = { $0 }
|
||||
) -> Future<[O], CoreStoreError> where S.Iterator.Element == O.ImportSource {
|
||||
|
||||
return .init { (promise) in
|
||||
|
||||
nonisolated(unsafe) let promise = promise
|
||||
self.base.perform(
|
||||
asynchronous: { (transaction) -> [O] in
|
||||
|
||||
@@ -370,14 +376,15 @@ extension DataStack.ReactiveNamespace {
|
||||
- parameter task: the asynchronous closure where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`.
|
||||
- returns: A `Future` whose event value be the value returned from the `task` closure.
|
||||
*/
|
||||
public func perform<Output>(
|
||||
_ asynchronous: @escaping (
|
||||
public func perform<Output: Sendable>(
|
||||
_ asynchronous: @escaping @Sendable (
|
||||
_ transaction: AsynchronousDataTransaction
|
||||
) throws(any Swift.Error) -> Output
|
||||
) -> Future<Output, CoreStoreError> {
|
||||
|
||||
return .init { (promise) in
|
||||
|
||||
nonisolated(unsafe) let promise = promise
|
||||
self.base.perform(
|
||||
asynchronous: asynchronous,
|
||||
success: { promise(.success($0)) },
|
||||
|
||||
@@ -38,12 +38,12 @@ extension DataStack {
|
||||
- parameter sourceIdentifier: an optional value that identifies the source of this transaction. This identifier will be passed to the change notifications and callers can use it for custom handling that depends on the source.
|
||||
- parameter completion: the closure executed after the save completes. The `Result` argument of the closure will either wrap the return value of `task`, or any uncaught errors thrown from within `task`. Cancelled `task`s will be indicated by `.failure(error: CoreStoreError.userCancelled)`. Custom errors thrown by the user will be wrapped in `CoreStoreError.userError(error: Error)`.
|
||||
*/
|
||||
public func perform<T>(
|
||||
asynchronous task: @escaping (
|
||||
public func perform<T: Sendable>(
|
||||
asynchronous task: @escaping @Sendable (
|
||||
_ transaction: AsynchronousDataTransaction
|
||||
) throws(any Swift.Error) -> T,
|
||||
sourceIdentifier: Any? = nil,
|
||||
completion: @escaping (AsynchronousDataTransaction.Result<T>) -> Void
|
||||
sourceIdentifier: (any Sendable)? = nil,
|
||||
completion: @escaping @Sendable (AsynchronousDataTransaction.Result<T>) -> Void
|
||||
) {
|
||||
|
||||
self.perform(
|
||||
@@ -63,22 +63,22 @@ extension DataStack {
|
||||
- parameter failure: the closure executed if the save fails or if any errors are thrown within `task`. Cancelled `task`s will be indicated by `CoreStoreError.userCancelled`. Custom errors thrown by the user will be wrapped in `CoreStoreError.userError(error: Error)`.
|
||||
*/
|
||||
public func perform<T>(
|
||||
asynchronous task: @escaping (
|
||||
asynchronous task: @escaping @Sendable (
|
||||
_ transaction: AsynchronousDataTransaction
|
||||
) throws(any Swift.Error) -> T,
|
||||
sourceIdentifier: Any? = nil,
|
||||
success: @escaping (T) -> Void,
|
||||
failure: @escaping (CoreStoreError) -> Void
|
||||
sourceIdentifier: (any Sendable)? = nil,
|
||||
success: @escaping @Sendable (sending T) -> Void,
|
||||
failure: @escaping @Sendable (CoreStoreError) -> Void
|
||||
) {
|
||||
|
||||
let transaction = AsynchronousDataTransaction(
|
||||
nonisolated(unsafe) let transaction = AsynchronousDataTransaction(
|
||||
mainContext: self.rootSavingContext,
|
||||
queue: self.childTransactionQueue,
|
||||
sourceIdentifier: sourceIdentifier
|
||||
)
|
||||
transaction.transactionQueue.cs_async {
|
||||
|
||||
let userInfo: T
|
||||
nonisolated(unsafe) let userInfo: T
|
||||
do {
|
||||
|
||||
userInfo = try task(transaction)
|
||||
@@ -125,7 +125,7 @@ extension DataStack {
|
||||
_ transaction: SynchronousDataTransaction
|
||||
) throws(any Swift.Error) -> T,
|
||||
waitForAllObservers: Bool = true,
|
||||
sourceIdentifier: Any? = nil
|
||||
sourceIdentifier: (any Sendable)? = nil
|
||||
) throws(CoreStoreError) -> T {
|
||||
|
||||
let transaction = SynchronousDataTransaction(
|
||||
@@ -172,7 +172,7 @@ extension DataStack {
|
||||
*/
|
||||
public func beginUnsafe(
|
||||
supportsUndo: Bool = false,
|
||||
sourceIdentifier: Any? = nil
|
||||
sourceIdentifier: (any Sendable)? = nil
|
||||
) -> UnsafeDataTransaction {
|
||||
|
||||
return UnsafeDataTransaction(
|
||||
|
||||
@@ -90,7 +90,8 @@ extension DataStack {
|
||||
|
||||
// MARK: - AddStorageSubscription
|
||||
|
||||
fileprivate final class AddStorageSubscription<S: Subscriber>: Subscription where S.Input == Output, S.Failure == CoreStoreError {
|
||||
fileprivate final class AddStorageSubscription<S: Subscriber>: Subscription, @unchecked Sendable
|
||||
where S.Input == Output, S.Failure == CoreStoreError {
|
||||
|
||||
// MARK: FilePrivate
|
||||
|
||||
@@ -114,7 +115,7 @@ extension DataStack {
|
||||
|
||||
return
|
||||
}
|
||||
var progress: Progress? = nil
|
||||
nonisolated(unsafe) var progress: Progress? = nil
|
||||
progress = self.dataStack.addStorage(
|
||||
self.storage,
|
||||
completion: { [weak self] result in
|
||||
@@ -150,7 +151,9 @@ extension DataStack {
|
||||
)
|
||||
if let progress = progress {
|
||||
|
||||
progress.setProgressHandler { [weak self] progress in
|
||||
Internals.mainActorImmediate { @MainActor [weak self] in
|
||||
|
||||
progress.setProgressHandler { progress in
|
||||
|
||||
guard
|
||||
let self = self,
|
||||
@@ -168,6 +171,7 @@ extension DataStack {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Cancellable
|
||||
|
||||
@@ -32,7 +32,7 @@ import CoreData
|
||||
/**
|
||||
The `DataStack` encapsulates the data model for the Core Data stack. Each `DataStack` can have multiple data stores, usually specified as a "Configuration" in the model editor. Behind the scenes, the DataStack manages its own `NSPersistentStoreCoordinator`, a root `NSManagedObjectContext` for disk saves, and a shared `NSManagedObjectContext` designed as a read-only model interface for `NSManagedObjects`.
|
||||
*/
|
||||
public final class DataStack: Equatable {
|
||||
public final class DataStack: Equatable, @unchecked Sendable {
|
||||
|
||||
/**
|
||||
The resolved application name, used by the `DataStack` as the default Xcode model name (.xcdatamodel filename) if not explicitly provided.
|
||||
@@ -398,7 +398,7 @@ public final class DataStack: Equatable {
|
||||
- parameter completion: the closure to execute after all persistent stores are removed
|
||||
*/
|
||||
public func unsafeRemoveAllPersistentStores(
|
||||
completion: @escaping () -> Void = {}
|
||||
completion: @escaping @MainActor () -> Void = {}
|
||||
) {
|
||||
|
||||
let coordinator = self.coordinator
|
||||
@@ -459,7 +459,7 @@ public final class DataStack: Equatable {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal static var defaultConfigurationName = "PF_DEFAULT_CONFIGURATION_NAME"
|
||||
internal static let defaultConfigurationName = "PF_DEFAULT_CONFIGURATION_NAME"
|
||||
|
||||
internal let coordinator: NSPersistentStoreCoordinator
|
||||
internal let rootSavingContext: NSManagedObjectContext
|
||||
|
||||
@@ -95,7 +95,10 @@ extension DiffableDataSource {
|
||||
- parameter dataStack: the `DataStack` instance that the dataSource will fetch objects from
|
||||
- parameter cellProvider: a closure that configures and returns the `UITableViewCell` for the object
|
||||
*/
|
||||
public init(target: T, dataStack: DataStack) {
|
||||
public init(
|
||||
target: T,
|
||||
dataStack: DataStack
|
||||
) {
|
||||
|
||||
self.target = target
|
||||
self.dataStack = dataStack
|
||||
@@ -106,7 +109,10 @@ extension DiffableDataSource {
|
||||
Clears the target.
|
||||
- parameter animatingDifferences: if `true`, animations may be applied accordingly. Defaults to `true`.
|
||||
*/
|
||||
open func purge(animatingDifferences: Bool = true, completion: @escaping () -> Void = {}) {
|
||||
open func purge(
|
||||
animatingDifferences: Bool = true,
|
||||
completion: @escaping @Sendable () -> Void = {}
|
||||
) {
|
||||
|
||||
self.dispatcher.purge(
|
||||
target: self.target,
|
||||
@@ -136,7 +142,11 @@ extension DiffableDataSource {
|
||||
- parameter snapshot: the `ListSnapshot` used to reload the target with. This is typically from the `snapshot` property of a `ListPublisher`.
|
||||
- parameter animatingDifferences: if `true`, animations may be applied accordingly. Defaults to `true`.
|
||||
*/
|
||||
open func apply(_ snapshot: ListSnapshot<O>, animatingDifferences: Bool = true, completion: @escaping () -> Void = {}) {
|
||||
open func apply(
|
||||
_ snapshot: ListSnapshot<O>,
|
||||
animatingDifferences: Bool = true,
|
||||
completion: @escaping @Sendable () -> Void = {}
|
||||
) {
|
||||
|
||||
let diffableSnapshot = snapshot.diffableSnapshot
|
||||
self.dispatcher.apply(
|
||||
|
||||
@@ -83,6 +83,7 @@ extension DiffableDataSource {
|
||||
- parameter itemProvider: a closure that configures and returns the `NSCollectionViewItem` for the object
|
||||
*/
|
||||
@nonobjc
|
||||
@MainActor
|
||||
public init(
|
||||
collectionView: NSCollectionView,
|
||||
dataStack: DataStack,
|
||||
@@ -102,6 +103,7 @@ extension DiffableDataSource {
|
||||
// MARK: - NSCollectionViewDataSource
|
||||
|
||||
@objc
|
||||
@MainActor
|
||||
public dynamic func numberOfSections(
|
||||
in collectionView: NSCollectionView
|
||||
) -> Int {
|
||||
@@ -110,6 +112,7 @@ extension DiffableDataSource {
|
||||
}
|
||||
|
||||
@objc
|
||||
@MainActor
|
||||
public dynamic func collectionView(
|
||||
_ collectionView: NSCollectionView,
|
||||
numberOfItemsInSection section: Int
|
||||
@@ -119,6 +122,7 @@ extension DiffableDataSource {
|
||||
}
|
||||
|
||||
@objc
|
||||
@MainActor
|
||||
open dynamic func collectionView(
|
||||
_ collectionView: NSCollectionView,
|
||||
itemForRepresentedObjectAt indexPath: IndexPath
|
||||
@@ -140,6 +144,7 @@ extension DiffableDataSource {
|
||||
}
|
||||
|
||||
@objc
|
||||
@MainActor
|
||||
open dynamic func collectionView(
|
||||
_ collectionView: NSCollectionView,
|
||||
viewForSupplementaryElementOfKind kind: NSCollectionView.SupplementaryElementKind,
|
||||
@@ -181,59 +186,92 @@ extension DiffableDataSource {
|
||||
|
||||
public var shouldSuspendBatchUpdates: Bool {
|
||||
|
||||
return MainActor.assumeIsolated {
|
||||
|
||||
return self.base?.window == nil
|
||||
}
|
||||
}
|
||||
|
||||
public func deleteSections(at indices: IndexSet, animated: Bool) {
|
||||
|
||||
MainActor.assumeIsolated {
|
||||
|
||||
self.base?.deleteSections(indices)
|
||||
}
|
||||
}
|
||||
|
||||
public func insertSections(at indices: IndexSet, animated: Bool) {
|
||||
|
||||
MainActor.assumeIsolated {
|
||||
|
||||
self.base?.insertSections(indices)
|
||||
}
|
||||
}
|
||||
|
||||
public func reloadSections(at indices: IndexSet, animated: Bool) {
|
||||
|
||||
MainActor.assumeIsolated {
|
||||
|
||||
self.base?.reloadSections(indices)
|
||||
}
|
||||
}
|
||||
|
||||
public func moveSection(at index: IndexSet.Element, to newIndex: IndexSet.Element, animated: Bool) {
|
||||
|
||||
MainActor.assumeIsolated {
|
||||
|
||||
self.base?.moveSection(index, toSection: newIndex)
|
||||
}
|
||||
}
|
||||
|
||||
public func deleteItems(at indexPaths: [IndexPath], animated: Bool) {
|
||||
|
||||
MainActor.assumeIsolated {
|
||||
|
||||
self.base?.deleteItems(at: Set(indexPaths))
|
||||
}
|
||||
}
|
||||
|
||||
public func insertItems(at indexPaths: [IndexPath], animated: Bool) {
|
||||
|
||||
MainActor.assumeIsolated {
|
||||
|
||||
self.base?.insertItems(at: Set(indexPaths))
|
||||
}
|
||||
}
|
||||
|
||||
public func reloadItems(at indexPaths: [IndexPath], animated: Bool) {
|
||||
|
||||
MainActor.assumeIsolated {
|
||||
|
||||
self.base?.reloadItems(at: Set(indexPaths))
|
||||
}
|
||||
}
|
||||
|
||||
public func moveItem(at indexPath: IndexPath, to newIndexPath: IndexPath, animated: Bool) {
|
||||
|
||||
MainActor.assumeIsolated {
|
||||
|
||||
self.base?.moveItem(at: indexPath, to: newIndexPath)
|
||||
}
|
||||
}
|
||||
|
||||
public func performBatchUpdates(updates: () -> Void, animated: Bool, completion: @escaping () -> Void) {
|
||||
public func performBatchUpdates(updates: @escaping @Sendable () -> Void, animated: Bool, completion: @escaping @Sendable () -> Void) {
|
||||
|
||||
MainActor.assumeIsolated {
|
||||
|
||||
self.base?.animator().performBatchUpdates(updates, completionHandler: { _ in completion() })
|
||||
}
|
||||
}
|
||||
|
||||
public func reloadData() {
|
||||
|
||||
MainActor.assumeIsolated {
|
||||
|
||||
self.base?.reloadData()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -185,59 +185,92 @@ extension DiffableDataSource {
|
||||
|
||||
public var shouldSuspendBatchUpdates: Bool {
|
||||
|
||||
return MainActor.assumeIsolated {
|
||||
|
||||
return self.base?.window == nil
|
||||
}
|
||||
}
|
||||
|
||||
public func deleteSections(at indices: IndexSet, animated: Bool) {
|
||||
|
||||
MainActor.assumeIsolated {
|
||||
|
||||
self.base?.deleteSections(indices)
|
||||
}
|
||||
}
|
||||
|
||||
public func insertSections(at indices: IndexSet, animated: Bool) {
|
||||
|
||||
MainActor.assumeIsolated {
|
||||
|
||||
self.base?.insertSections(indices)
|
||||
}
|
||||
}
|
||||
|
||||
public func reloadSections(at indices: IndexSet, animated: Bool) {
|
||||
|
||||
MainActor.assumeIsolated {
|
||||
|
||||
self.base?.reloadSections(indices)
|
||||
}
|
||||
}
|
||||
|
||||
public func moveSection(at index: IndexSet.Element, to newIndex: IndexSet.Element, animated: Bool) {
|
||||
|
||||
MainActor.assumeIsolated {
|
||||
|
||||
self.base?.moveSection(index, toSection: newIndex)
|
||||
}
|
||||
}
|
||||
|
||||
public func deleteItems(at indexPaths: [IndexPath], animated: Bool) {
|
||||
|
||||
MainActor.assumeIsolated {
|
||||
|
||||
self.base?.deleteItems(at: indexPaths)
|
||||
}
|
||||
}
|
||||
|
||||
public func insertItems(at indexPaths: [IndexPath], animated: Bool) {
|
||||
|
||||
Internals.mainActorImmediate {
|
||||
|
||||
self.base?.insertItems(at: indexPaths)
|
||||
}
|
||||
}
|
||||
|
||||
public func reloadItems(at indexPaths: [IndexPath], animated: Bool) {
|
||||
|
||||
Internals.mainActorImmediate {
|
||||
|
||||
self.base?.reloadItems(at: indexPaths)
|
||||
}
|
||||
}
|
||||
|
||||
public func moveItem(at indexPath: IndexPath, to newIndexPath: IndexPath, animated: Bool) {
|
||||
|
||||
Internals.mainActorImmediate {
|
||||
|
||||
self.base?.moveItem(at: indexPath, to: newIndexPath)
|
||||
}
|
||||
}
|
||||
|
||||
public func performBatchUpdates(updates: () -> Void, animated: Bool, completion: @escaping () -> Void) {
|
||||
public func performBatchUpdates(updates: @escaping @Sendable () -> Void, animated: Bool, completion: @escaping @Sendable () -> Void) {
|
||||
|
||||
Internals.mainActorImmediate {
|
||||
|
||||
self.base?.performBatchUpdates(updates, completion: { _ in completion() })
|
||||
}
|
||||
}
|
||||
|
||||
public func reloadData() {
|
||||
|
||||
Internals.mainActorImmediate {
|
||||
|
||||
self.base?.reloadData()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -85,7 +85,7 @@ extension DiffableDataSource {
|
||||
public init(
|
||||
tableView: UITableView,
|
||||
dataStack: DataStack,
|
||||
cellProvider: @escaping (UITableView, IndexPath, O) -> UITableViewCell?
|
||||
cellProvider: @escaping @MainActor (UITableView, IndexPath, O) -> UITableViewCell?
|
||||
) {
|
||||
|
||||
self.cellProvider = cellProvider
|
||||
@@ -97,6 +97,7 @@ extension DiffableDataSource {
|
||||
/**
|
||||
The target `UITableView`
|
||||
*/
|
||||
@MainActor
|
||||
public var tableView: UITableView? {
|
||||
|
||||
return self.target.base
|
||||
@@ -240,50 +241,79 @@ extension DiffableDataSource {
|
||||
|
||||
public var shouldSuspendBatchUpdates: Bool {
|
||||
|
||||
return MainActor.assumeIsolated {
|
||||
|
||||
return self.base?.window == nil
|
||||
}
|
||||
}
|
||||
|
||||
public func deleteSections(at indices: IndexSet, animated: Bool) {
|
||||
|
||||
MainActor.assumeIsolated {
|
||||
|
||||
self.base?.deleteSections(indices, with: .automatic)
|
||||
}
|
||||
}
|
||||
|
||||
public func insertSections(at indices: IndexSet, animated: Bool) {
|
||||
|
||||
MainActor.assumeIsolated {
|
||||
|
||||
self.base?.insertSections(indices, with: .automatic)
|
||||
}
|
||||
}
|
||||
|
||||
public func reloadSections(at indices: IndexSet, animated: Bool) {
|
||||
|
||||
MainActor.assumeIsolated {
|
||||
|
||||
self.base?.reloadSections(indices, with: .automatic)
|
||||
}
|
||||
}
|
||||
|
||||
public func moveSection(at index: IndexSet.Element, to newIndex: IndexSet.Element, animated: Bool) {
|
||||
|
||||
MainActor.assumeIsolated {
|
||||
|
||||
self.base?.moveSection(index, toSection: newIndex)
|
||||
}
|
||||
}
|
||||
|
||||
public func deleteItems(at indexPaths: [IndexPath], animated: Bool) {
|
||||
|
||||
MainActor.assumeIsolated {
|
||||
|
||||
self.base?.deleteRows(at: indexPaths, with: .automatic)
|
||||
}
|
||||
}
|
||||
|
||||
public func insertItems(at indexPaths: [IndexPath], animated: Bool) {
|
||||
|
||||
MainActor.assumeIsolated {
|
||||
|
||||
self.base?.insertRows(at: indexPaths, with: .automatic)
|
||||
}
|
||||
}
|
||||
|
||||
public func reloadItems(at indexPaths: [IndexPath], animated: Bool) {
|
||||
|
||||
MainActor.assumeIsolated {
|
||||
|
||||
self.base?.reloadRows(at: indexPaths, with: .automatic)
|
||||
}
|
||||
}
|
||||
|
||||
public func moveItem(at indexPath: IndexPath, to newIndexPath: IndexPath, animated: Bool) {
|
||||
|
||||
MainActor.assumeIsolated {
|
||||
|
||||
self.base?.moveRow(at: indexPath, to: newIndexPath)
|
||||
}
|
||||
}
|
||||
|
||||
public func performBatchUpdates(updates: () -> Void, animated: Bool, completion: @escaping () -> Void) {
|
||||
public func performBatchUpdates(updates: @escaping @Sendable () -> Void, animated: Bool, completion: @escaping @Sendable () -> Void) {
|
||||
|
||||
MainActor.assumeIsolated {
|
||||
|
||||
guard let base = self.base else {
|
||||
|
||||
@@ -291,12 +321,16 @@ extension DiffableDataSource {
|
||||
}
|
||||
base.performBatchUpdates(updates, completion: { _ in completion() })
|
||||
}
|
||||
}
|
||||
|
||||
public func reloadData() {
|
||||
|
||||
MainActor.assumeIsolated {
|
||||
|
||||
self.base?.reloadData()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -46,7 +46,7 @@ extension DiffableDataSource {
|
||||
/**
|
||||
The `DiffableDataSource.Target` protocol allows custom views to consume `ListSnapshot` diffable data similar to how `DiffableDataSource.TableViewAdapter` and `DiffableDataSource.CollectionViewAdapter` reloads data for their corresponding views.
|
||||
*/
|
||||
public protocol DiffableDataSourceTarget {
|
||||
public protocol DiffableDataSourceTarget: Sendable {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
@@ -98,7 +98,7 @@ public protocol DiffableDataSourceTarget {
|
||||
/**
|
||||
Animates multiple insert, delete, reload, and move operations as a group.
|
||||
*/
|
||||
func performBatchUpdates(updates: () -> Void, animated: Bool, completion: @escaping () -> Void)
|
||||
func performBatchUpdates(updates: @escaping @Sendable () -> Void, animated: Bool, completion: @escaping @Sendable () -> Void)
|
||||
|
||||
/**
|
||||
Reloads all sections and items.
|
||||
@@ -110,18 +110,24 @@ extension DiffableDataSource.Target {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal func reload<C, O>(
|
||||
internal func reload<C: Sendable, O>(
|
||||
using stagedChangeset: Internals.DiffableDataUIDispatcher<O>.StagedChangeset<C>,
|
||||
animated: Bool,
|
||||
interrupt: ((Internals.DiffableDataUIDispatcher<O>.Changeset<C>) -> Bool)? = nil,
|
||||
setData: (C) -> Void,
|
||||
setData: @escaping @Sendable (C) -> Void,
|
||||
completion: @escaping () -> Void
|
||||
) {
|
||||
|
||||
let group = DispatchGroup()
|
||||
defer {
|
||||
|
||||
group.notify(queue: .main, execute: completion)
|
||||
group.notify(
|
||||
queue: .main,
|
||||
execute: {
|
||||
|
||||
completion()
|
||||
}
|
||||
)
|
||||
}
|
||||
if self.shouldSuspendBatchUpdates, let data = stagedChangeset.last?.data {
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ extension DispatchQueue {
|
||||
|
||||
@nonobjc @inline(__always)
|
||||
internal func cs_async(
|
||||
_ closure: @escaping () -> Void
|
||||
_ closure: @escaping @Sendable () -> Void
|
||||
) {
|
||||
|
||||
self.async { autoreleasepool(invoking: closure) }
|
||||
@@ -115,7 +115,7 @@ extension DispatchQueue {
|
||||
|
||||
@nonobjc @inline(__always)
|
||||
internal func cs_barrierAsync(
|
||||
_ closure: @escaping () -> Void
|
||||
_ closure: @escaping @Sendable () -> Void
|
||||
) {
|
||||
|
||||
self.async(flags: .barrier) { autoreleasepool(invoking: closure) }
|
||||
|
||||
@@ -57,7 +57,7 @@ public protocol ImportableObject: DynamicObject {
|
||||
/**
|
||||
The data type for the import source. This is most commonly an json type, `NSDictionary`, or another external source such as `NSUserDefaults`.
|
||||
*/
|
||||
associatedtype ImportSource
|
||||
associatedtype ImportSource: Sendable
|
||||
|
||||
/**
|
||||
Return `true` if an object should be created from `source`. Return `false` to ignore and skip `source`. The default implementation returns `true`.
|
||||
|
||||
@@ -31,7 +31,7 @@ import CoreData
|
||||
/**
|
||||
A storage interface that is backed only in memory.
|
||||
*/
|
||||
public final class InMemoryStore: StorageInterface {
|
||||
public final class InMemoryStore: StorageInterface, @unchecked Sendable {
|
||||
|
||||
/**
|
||||
Initializes an `InMemoryStore` for the specified configuration
|
||||
@@ -66,7 +66,10 @@ public final class InMemoryStore: StorageInterface {
|
||||
/**
|
||||
The options dictionary for the `NSPersistentStore`. For `InMemoryStore`s, this is always set to `nil`.
|
||||
*/
|
||||
public let storeOptions: [AnyHashable: Any]? = nil
|
||||
public var storeOptions: [AnyHashable: Any]? {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
/**
|
||||
Do not call directly. Used by the `DataStack` internally.
|
||||
|
||||
@@ -95,7 +95,7 @@ extension Internals {
|
||||
|
||||
// MARK: FilePrivate
|
||||
|
||||
fileprivate static var cachedCoders: [NSValueTransformerName: AnyFieldCoder] = [:]
|
||||
fileprivate static nonisolated(unsafe) var cachedCoders: [NSValueTransformerName: AnyFieldCoder] = [:]
|
||||
|
||||
|
||||
// MARK: - TransformableDefaultValueCodingBox
|
||||
|
||||
@@ -36,7 +36,7 @@ extension Internals {
|
||||
|
||||
// Bugfix for NSFetchRequest messing up memory management for `affectedStores`
|
||||
// http://stackoverflow.com/questions/14396375/nsfetchedresultscontroller-crashes-in-ios-6-if-affectedstores-is-specified
|
||||
internal final class CoreStoreFetchRequest<T: NSFetchRequestResult>: NSFetchRequest<NSFetchRequestResult> {
|
||||
internal final class CoreStoreFetchRequest<T: NSFetchRequestResult>: NSFetchRequest<NSFetchRequestResult>, @unchecked Sendable {
|
||||
|
||||
@nonobjc
|
||||
internal func safeAffectedStores() -> [NSPersistentStore]? {
|
||||
|
||||
@@ -33,7 +33,7 @@ extension Internals {
|
||||
|
||||
// MARK: - CoreStoreFetchedResultsController
|
||||
|
||||
internal final class CoreStoreFetchedResultsController: NSFetchedResultsController<NSManagedObject> {
|
||||
internal final class CoreStoreFetchedResultsController: NSFetchedResultsController<NSManagedObject>, @unchecked Sendable {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ extension Internals {
|
||||
// MARK: - DiffableDataSourceSnapshot
|
||||
|
||||
// Implementation based on https://github.com/ra1028/DiffableDataSources
|
||||
internal struct DiffableDataSourceSnapshot: DiffableDataSourceSnapshotProtocol {
|
||||
internal struct DiffableDataSourceSnapshot: DiffableDataSourceSnapshotProtocol, @unchecked Sendable {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
|
||||
@@ -35,7 +35,8 @@ extension Internals.DiffableDataUIDispatcher {
|
||||
// MARK: - ChangeSet
|
||||
|
||||
// Implementation based on https://github.com/ra1028/DifferenceKit
|
||||
internal struct Changeset<C: Collection>: Equatable where C: Equatable {
|
||||
internal struct Changeset<C: Collection & Sendable>: Equatable, Sendable
|
||||
where C: Equatable {
|
||||
|
||||
var data: C
|
||||
var sectionDeleted: [Int]
|
||||
|
||||
@@ -36,7 +36,7 @@ extension Internals.DiffableDataUIDispatcher {
|
||||
|
||||
// Implementation based on https://github.com/ra1028/DifferenceKit
|
||||
@usableFromInline
|
||||
internal struct DiffResult<Index> {
|
||||
internal struct DiffResult<Index: Sendable>: Sendable {
|
||||
|
||||
@usableFromInline
|
||||
internal let deleted: [Index]
|
||||
@@ -213,7 +213,7 @@ extension Internals.DiffableDataUIDispatcher {
|
||||
|
||||
// Implementation based on https://github.com/ra1028/DifferenceKit
|
||||
@usableFromInline
|
||||
internal struct Trace<I> {
|
||||
internal struct Trace<I: Sendable>: Sendable {
|
||||
|
||||
@usableFromInline
|
||||
internal var reference: I?
|
||||
|
||||
@@ -35,7 +35,8 @@ extension Internals.DiffableDataUIDispatcher {
|
||||
// MARK: - StagedChangeset
|
||||
|
||||
// Implementation based on https://github.com/ra1028/DifferenceKit
|
||||
internal struct StagedChangeset<C: Collection>: ExpressibleByArrayLiteral, Equatable, RandomAccessCollection, RangeReplaceableCollection where C: Equatable {
|
||||
internal struct StagedChangeset<C: Collection & Sendable>: ExpressibleByArrayLiteral, Equatable, RandomAccessCollection, RangeReplaceableCollection, Sendable
|
||||
where C: Equatable {
|
||||
|
||||
@usableFromInline
|
||||
var changesets: ContiguousArray<Changeset<C>>
|
||||
@@ -117,7 +118,8 @@ extension Internals.DiffableDataUIDispatcher {
|
||||
|
||||
// MARK: - Internals.DiffableDataUIDispatcher.StagedChangeset where C: RangeReplaceableCollection, C.Element: Differentiable
|
||||
|
||||
extension Internals.DiffableDataUIDispatcher.StagedChangeset where C: RangeReplaceableCollection, C.Element: Differentiable {
|
||||
extension Internals.DiffableDataUIDispatcher.StagedChangeset
|
||||
where C: RangeReplaceableCollection, C.Element: Differentiable {
|
||||
|
||||
@inlinable
|
||||
internal init(source: C, target: C) {
|
||||
@@ -225,7 +227,8 @@ extension Internals.DiffableDataUIDispatcher.StagedChangeset where C: RangeRepla
|
||||
|
||||
// MARK: - Internals.DiffableDataUIDispatcher.StagedChangeset where C: RangeReplaceableCollection, C.Element: DifferentiableSection
|
||||
|
||||
extension Internals.DiffableDataUIDispatcher.StagedChangeset where C: RangeReplaceableCollection, C.Element: DifferentiableSection {
|
||||
extension Internals.DiffableDataUIDispatcher.StagedChangeset
|
||||
where C: RangeReplaceableCollection & Sendable, C.Element: DifferentiableSection & Sendable {
|
||||
|
||||
@inlinable
|
||||
internal init(source: C, target: C) {
|
||||
|
||||
@@ -32,6 +32,8 @@ import QuartzCore
|
||||
|
||||
#endif
|
||||
|
||||
import os
|
||||
|
||||
|
||||
// MARK: - Internals
|
||||
|
||||
@@ -41,7 +43,7 @@ extension Internals {
|
||||
|
||||
// Implementation based on https://github.com/ra1028/DiffableDataSources
|
||||
@usableFromInline
|
||||
internal final class DiffableDataUIDispatcher<O: DynamicObject> {
|
||||
internal final class DiffableDataUIDispatcher<O: DynamicObject>: @unchecked Sendable {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
@@ -55,10 +57,10 @@ extension Internals {
|
||||
func purge<Target: DiffableDataSource.Target>(
|
||||
target: Target?,
|
||||
animatingDifferences: Bool,
|
||||
performUpdates: @escaping (
|
||||
performUpdates: @escaping @Sendable (
|
||||
Target,
|
||||
StagedChangeset<[Internals.DiffableDataSourceSnapshot.Section]>,
|
||||
@escaping ([Internals.DiffableDataSourceSnapshot.Section]) -> Void
|
||||
@escaping @Sendable ([Internals.DiffableDataSourceSnapshot.Section]) -> Void
|
||||
) -> Void
|
||||
) {
|
||||
|
||||
@@ -74,14 +76,14 @@ extension Internals {
|
||||
_ snapshot: DiffableDataSourceSnapshot,
|
||||
target: Target?,
|
||||
animatingDifferences: Bool,
|
||||
performUpdates: @escaping (
|
||||
performUpdates: @escaping @Sendable (
|
||||
Target,
|
||||
StagedChangeset<[Internals.DiffableDataSourceSnapshot.Section]>,
|
||||
@escaping ([Internals.DiffableDataSourceSnapshot.Section]) -> Void
|
||||
@escaping @Sendable ([Internals.DiffableDataSourceSnapshot.Section]) -> Void
|
||||
) -> Void
|
||||
) {
|
||||
|
||||
self.dispatcher.dispatch { [weak self] in
|
||||
self.dispatcher.dispatch { @MainActor [weak self] in
|
||||
|
||||
guard let self = self else {
|
||||
|
||||
@@ -97,13 +99,17 @@ extension Internals {
|
||||
return
|
||||
}
|
||||
|
||||
let performDiffingUpdates: () -> Void = {
|
||||
let performDiffingUpdates: @MainActor () -> Void = {
|
||||
|
||||
let changeset = StagedChangeset(source: self.sections, target: newSections)
|
||||
performUpdates(target, changeset) { sections in
|
||||
performUpdates(
|
||||
target,
|
||||
changeset,
|
||||
{ sections in
|
||||
|
||||
self.sections = sections
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#if os(watchOS) || !canImport(QuartzCore)
|
||||
@@ -212,7 +218,7 @@ extension Internals {
|
||||
// MARK: - ElementPath
|
||||
|
||||
@usableFromInline
|
||||
internal struct ElementPath: Hashable {
|
||||
internal struct ElementPath: Hashable, Sendable {
|
||||
|
||||
@usableFromInline
|
||||
var element: Int
|
||||
@@ -231,18 +237,21 @@ extension Internals {
|
||||
|
||||
// MARK: - MainThreadSerialDispatcher
|
||||
|
||||
fileprivate final class MainThreadSerialDispatcher {
|
||||
fileprivate final class MainThreadSerialDispatcher: Sendable {
|
||||
|
||||
// MARK: FilePrivate
|
||||
|
||||
fileprivate init() {}
|
||||
|
||||
fileprivate func dispatch(_ action: @escaping () -> Void) {
|
||||
fileprivate func dispatch(_ action: @escaping @MainActor () -> Void) {
|
||||
|
||||
let count = self.executingCount.incrementAndGet()
|
||||
if Thread.isMainThread && count == 1 {
|
||||
|
||||
MainActor.assumeIsolated {
|
||||
|
||||
action()
|
||||
}
|
||||
self.executingCount.decrement()
|
||||
}
|
||||
else {
|
||||
@@ -267,36 +276,31 @@ extension Internals {
|
||||
|
||||
// MARK: - AtomicInt
|
||||
|
||||
fileprivate class AtomicInt {
|
||||
fileprivate final class AtomicInt: Sendable {
|
||||
|
||||
// MARK: FilePrivate
|
||||
|
||||
fileprivate func incrementAndGet() -> Int {
|
||||
|
||||
self.lock.wait()
|
||||
defer {
|
||||
return self.value.withLock {
|
||||
|
||||
self.lock.signal()
|
||||
$0 += 1
|
||||
return $0
|
||||
}
|
||||
self.value += 1
|
||||
return self.value
|
||||
}
|
||||
|
||||
fileprivate func decrement() {
|
||||
|
||||
self.lock.wait()
|
||||
defer {
|
||||
self.value.withLock {
|
||||
|
||||
self.lock.signal()
|
||||
$0 -= 1
|
||||
}
|
||||
self.value -= 1
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let lock = DispatchSemaphore(value: 1)
|
||||
private var value = 0
|
||||
private let value: OSAllocatedUnfairLock<Int> = .init(initialState: 0)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ extension Internals {
|
||||
|
||||
// MARK: - FetchedResultsControllerDelegate
|
||||
|
||||
internal final class FetchedResultsControllerDelegate: NSObject, NSFetchedResultsControllerDelegate {
|
||||
internal final class FetchedResultsControllerDelegate: NSObject, NSFetchedResultsControllerDelegate, @unchecked Sendable {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ extension Internals {
|
||||
notificationName: Notification.Name,
|
||||
object: Any?,
|
||||
queue: OperationQueue? = nil,
|
||||
closure: @escaping (_ note: Notification) -> Void
|
||||
closure: @escaping @Sendable (_ note: Notification) -> Void
|
||||
) {
|
||||
|
||||
self.observer = NotificationCenter.default.addObserver(
|
||||
|
||||
@@ -31,7 +31,7 @@ extension Internals {
|
||||
|
||||
// MARK: - SharedNotificationObserver
|
||||
|
||||
internal final class SharedNotificationObserver<T> {
|
||||
internal final class SharedNotificationObserver<T>: @unchecked Sendable {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
@@ -39,7 +39,7 @@ extension Internals {
|
||||
notificationName: Notification.Name,
|
||||
object: Any?,
|
||||
queue: OperationQueue? = nil,
|
||||
sharedValue: @escaping (_ note: Notification) -> T
|
||||
sharedValue: @escaping @Sendable (_ note: Notification) -> T
|
||||
) {
|
||||
|
||||
self.observer = NotificationCenter.default.addObserver(
|
||||
|
||||
+45
-10
@@ -122,6 +122,7 @@ internal enum Internals {
|
||||
return closure()
|
||||
}
|
||||
|
||||
|
||||
@inline(__always)
|
||||
internal static func autoreleasepool<T>(
|
||||
_ closure: () -> T
|
||||
@@ -139,29 +140,63 @@ internal enum Internals {
|
||||
}
|
||||
|
||||
@inline(__always)
|
||||
internal static func autoreleasepool<T>(
|
||||
_ closure: () throws(CoreStoreError) -> T
|
||||
) throws(CoreStoreError) -> T {
|
||||
|
||||
do {
|
||||
internal static func autoreleasepool<T, E>(
|
||||
_ closure: () throws(E) -> T
|
||||
) throws(E) -> T {
|
||||
|
||||
return try ObjectiveC.autoreleasepool(invoking: closure)
|
||||
}
|
||||
catch {
|
||||
|
||||
throw CoreStoreError(error)
|
||||
}
|
||||
}
|
||||
|
||||
@inline(__always)
|
||||
internal static func withCheckedThrowingContinuation<T>(
|
||||
function: String = #function,
|
||||
_ body: (CheckedContinuation<T, any Swift.Error>) -> Void
|
||||
) async throws(any Swift.Error) -> T {
|
||||
) async throws(any Swift.Error) -> sending T {
|
||||
|
||||
return try await _Concurrency.withCheckedThrowingContinuation(
|
||||
function: function,
|
||||
body
|
||||
)
|
||||
}
|
||||
|
||||
@inline(__always)
|
||||
internal static func withCheckedThrowingContinuation<T, E>(
|
||||
function: String = #function,
|
||||
_ body: (CheckedContinuation<T, E>) -> Void
|
||||
) async throws(E) -> sending T {
|
||||
|
||||
return try await _Concurrency.withCheckedThrowingContinuation(
|
||||
function: function,
|
||||
body
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@inline(__always)
|
||||
internal static func mainActorImmediate(
|
||||
_ body: @escaping @MainActor () -> Void
|
||||
) {
|
||||
|
||||
if #available(iOS 26.0, macOS 26.0, watchOS 26.0, tvOS 26.0, *) {
|
||||
|
||||
Task.immediate { @MainActor in
|
||||
|
||||
body()
|
||||
}
|
||||
}
|
||||
else if Thread.isMainThread {
|
||||
|
||||
MainActor.assumeIsolated {
|
||||
body()
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
Task { @MainActor in
|
||||
|
||||
body()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@ import CoreData
|
||||
let person = transaction.create(Into<MyPersonEntity>("Configuration1"))
|
||||
```
|
||||
*/
|
||||
public struct Into<O: DynamicObject>: Hashable {
|
||||
public struct Into<O: DynamicObject>: Hashable, @unchecked Sendable {
|
||||
|
||||
/**
|
||||
The associated `NSManagedObject` or `CoreStoreObject` entity class
|
||||
|
||||
+23
-23
@@ -66,7 +66,7 @@ import CoreData
|
||||
```
|
||||
In the example above, both `person1` and `person2` will contain the object at section=2, index=3.
|
||||
*/
|
||||
public final class ListMonitor<O: DynamicObject>: Hashable {
|
||||
public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable {
|
||||
|
||||
// MARK: Public (Accessors)
|
||||
|
||||
@@ -648,7 +648,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
|
||||
*/
|
||||
public func refetch(
|
||||
_ fetchClauses: FetchClause...,
|
||||
sourceIdentifier: Any? = nil
|
||||
sourceIdentifier: (any Sendable)? = nil
|
||||
) {
|
||||
|
||||
self.refetch(
|
||||
@@ -668,7 +668,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
|
||||
*/
|
||||
public func refetch(
|
||||
_ fetchClauses: [FetchClause],
|
||||
sourceIdentifier: Any? = nil
|
||||
sourceIdentifier: (any Sendable)? = nil
|
||||
) {
|
||||
|
||||
self.refetch(
|
||||
@@ -751,7 +751,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
|
||||
from: From<O>,
|
||||
sectionBy: SectionBy<O>?,
|
||||
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void,
|
||||
createAsynchronously: @escaping (ListMonitor<O>) -> Void
|
||||
createAsynchronously: @escaping @Sendable (ListMonitor<O>) -> Void
|
||||
) {
|
||||
|
||||
self.init(
|
||||
@@ -786,7 +786,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
|
||||
from: From<O>,
|
||||
sectionBy: SectionBy<O>?,
|
||||
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void,
|
||||
createAsynchronously: @escaping (ListMonitor<O>) -> Void
|
||||
createAsynchronously: @escaping @Sendable (ListMonitor<O>) -> Void
|
||||
) {
|
||||
|
||||
self.init(
|
||||
@@ -803,7 +803,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
|
||||
_ notificationKey: UnsafeRawPointer,
|
||||
name: Notification.Name,
|
||||
toObserver observer: AnyObject,
|
||||
callback: @escaping (_ monitor: ListMonitor<O>) -> Void
|
||||
callback: @escaping @Sendable (_ monitor: ListMonitor<O>) -> Void
|
||||
) {
|
||||
|
||||
Internals.setAssociatedRetainedObject(
|
||||
@@ -828,7 +828,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
|
||||
_ notificationKey: UnsafeRawPointer,
|
||||
name: Notification.Name,
|
||||
toObserver observer: AnyObject,
|
||||
callback: @escaping (
|
||||
callback: @escaping @Sendable (
|
||||
_ monitor: ListMonitor<O>,
|
||||
_ object: O,
|
||||
_ indexPath: IndexPath?,
|
||||
@@ -864,7 +864,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
|
||||
_ notificationKey: UnsafeRawPointer,
|
||||
name: Notification.Name,
|
||||
toObserver observer: AnyObject,
|
||||
callback: @escaping (
|
||||
callback: @escaping @Sendable (
|
||||
_ monitor: ListMonitor<O>,
|
||||
_ sectionInfo: NSFetchedResultsSectionInfo,
|
||||
_ sectionIndex: Int
|
||||
@@ -892,21 +892,21 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
|
||||
)
|
||||
}
|
||||
|
||||
internal func registerObserver<U: AnyObject>(
|
||||
internal func registerObserver<U: AnyObject & Sendable>(
|
||||
_ observer: U,
|
||||
willChange: @escaping (
|
||||
willChange: @escaping @Sendable (
|
||||
_ observer: U,
|
||||
_ monitor: ListMonitor<O>
|
||||
) -> Void,
|
||||
didChange: @escaping (
|
||||
didChange: @escaping @Sendable (
|
||||
_ observer: U,
|
||||
_ monitor: ListMonitor<O>
|
||||
) -> Void,
|
||||
willRefetch: @escaping (
|
||||
willRefetch: @escaping @Sendable (
|
||||
_ observer: U,
|
||||
_ monitor: ListMonitor<O>
|
||||
) -> Void,
|
||||
didRefetch: @escaping (
|
||||
didRefetch: @escaping @Sendable (
|
||||
_ observer: U,
|
||||
_ monitor: ListMonitor<O>
|
||||
) -> Void) {
|
||||
@@ -969,27 +969,27 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
|
||||
)
|
||||
}
|
||||
|
||||
internal func registerObserver<U: AnyObject>(
|
||||
internal func registerObserver<U: AnyObject & Sendable>(
|
||||
_ observer: U,
|
||||
didInsertObject: @escaping (
|
||||
didInsertObject: @escaping @Sendable (
|
||||
_ observer: U,
|
||||
_ monitor: ListMonitor<O>,
|
||||
_ object: O,
|
||||
_ toIndexPath: IndexPath
|
||||
) -> Void,
|
||||
didDeleteObject: @escaping (
|
||||
didDeleteObject: @escaping @Sendable (
|
||||
_ observer: U,
|
||||
_ monitor: ListMonitor<O>,
|
||||
_ object: O,
|
||||
_ fromIndexPath: IndexPath
|
||||
) -> Void,
|
||||
didUpdateObject: @escaping (
|
||||
didUpdateObject: @escaping @Sendable (
|
||||
_ observer: U,
|
||||
_ monitor: ListMonitor<O>,
|
||||
_ object: O,
|
||||
_ atIndexPath: IndexPath
|
||||
) -> Void,
|
||||
didMoveObject: @escaping (
|
||||
didMoveObject: @escaping @Sendable (
|
||||
_ observer: U,
|
||||
_ monitor: ListMonitor<O>,
|
||||
_ object: O,
|
||||
@@ -1056,15 +1056,15 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
|
||||
)
|
||||
}
|
||||
|
||||
internal func registerObserver<U: AnyObject>(
|
||||
internal func registerObserver<U: AnyObject & Sendable>(
|
||||
_ observer: U,
|
||||
didInsertSection: @escaping (
|
||||
didInsertSection: @escaping @Sendable (
|
||||
_ observer: U,
|
||||
_ monitor: ListMonitor<O>,
|
||||
_ sectionInfo: NSFetchedResultsSectionInfo,
|
||||
_ toIndex: Int
|
||||
) -> Void,
|
||||
didDeleteSection: @escaping (
|
||||
didDeleteSection: @escaping @Sendable (
|
||||
_ observer: U,
|
||||
_ monitor: ListMonitor<O>,
|
||||
_ sectionInfo: NSFetchedResultsSectionInfo,
|
||||
@@ -1127,7 +1127,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
|
||||
|
||||
internal func refetch(
|
||||
_ applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void,
|
||||
sourceIdentifier: Any?
|
||||
sourceIdentifier: (any Sendable)?
|
||||
) {
|
||||
|
||||
Internals.assert(
|
||||
@@ -1302,7 +1302,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
|
||||
from: From<O>,
|
||||
sectionBy: SectionBy<O>?,
|
||||
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void,
|
||||
createAsynchronously: ((ListMonitor<O>) -> Void)?
|
||||
createAsynchronously: (@Sendable (ListMonitor<O>) -> Void)?
|
||||
) {
|
||||
|
||||
self.isSectioned = (sectionBy != nil)
|
||||
|
||||
@@ -39,7 +39,7 @@ import CoreData
|
||||
monitor.addObserver(self)
|
||||
```
|
||||
*/
|
||||
public protocol ListObserver: AnyObject {
|
||||
public protocol ListObserver: AnyObject, Sendable {
|
||||
|
||||
/**
|
||||
The `NSManagedObject` type for the observed list
|
||||
|
||||
@@ -90,7 +90,8 @@ extension ListPublisher {
|
||||
|
||||
// MARK: - ListSnapshotSubscription
|
||||
|
||||
fileprivate final class ListSnapshotSubscription<S: Subscriber>: Subscription where S.Input == Output, S.Failure == Never {
|
||||
fileprivate final class ListSnapshotSubscription<S: Subscriber>: Subscription, @unchecked Sendable
|
||||
where S.Input == Output, S.Failure == Never {
|
||||
|
||||
// MARK: FilePrivate
|
||||
|
||||
@@ -114,6 +115,8 @@ extension ListPublisher {
|
||||
|
||||
return
|
||||
}
|
||||
Internals.mainActorImmediate { [self] in
|
||||
|
||||
self.publisher.addObserver(
|
||||
self,
|
||||
notifyInitial: self.emitInitialValue,
|
||||
@@ -130,6 +133,7 @@ extension ListPublisher {
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Cancellable
|
||||
@@ -138,17 +142,10 @@ extension ListPublisher {
|
||||
|
||||
self.subscriber = nil
|
||||
|
||||
if Thread.isMainThread {
|
||||
Internals.mainActorImmediate {
|
||||
|
||||
self.publisher.removeObserver(self)
|
||||
}
|
||||
else {
|
||||
|
||||
DispatchQueue.main.async {
|
||||
|
||||
self.publisher.removeObserver(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -94,6 +94,7 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
|
||||
- parameter notifyInitial: if `true`, the callback is executed immediately with the current publisher state. Otherwise only succeeding updates will notify the observer. Default value is `false`.
|
||||
- parameter callback: the closure to execute when changes occur
|
||||
*/
|
||||
@MainActor
|
||||
public func addObserver<T: AnyObject>(
|
||||
_ observer: T,
|
||||
notifyInitial: Bool = false,
|
||||
@@ -128,6 +129,7 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
|
||||
- parameter initialSourceIdentifier: an optional value that identifies the initial callback invocation if `notifyInitial` is `true`.
|
||||
- parameter callback: the closure to execute when changes occur
|
||||
*/
|
||||
@MainActor
|
||||
public func addObserver<T: AnyObject>(
|
||||
_ observer: T,
|
||||
notifyInitial: Bool = false,
|
||||
@@ -159,6 +161,7 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
|
||||
|
||||
- parameter observer: the object whose notifications will be unregistered
|
||||
*/
|
||||
@MainActor
|
||||
public func removeObserver<T: AnyObject>(_ observer: T) {
|
||||
|
||||
Internals.assert(
|
||||
@@ -185,7 +188,7 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
|
||||
*/
|
||||
public func refetch<B: FetchChainableBuilderType>(
|
||||
_ clauseChain: B,
|
||||
sourceIdentifier: Any? = nil
|
||||
sourceIdentifier: (any Sendable)? = nil
|
||||
) throws(any Swift.Error) where B.ObjectType == O {
|
||||
|
||||
try self.refetch(
|
||||
@@ -214,7 +217,7 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
|
||||
*/
|
||||
public func refetch<B: SectionMonitorBuilderType>(
|
||||
_ clauseChain: B,
|
||||
sourceIdentifier: Any? = nil
|
||||
sourceIdentifier: (any Sendable)? = nil
|
||||
) throws(any Swift.Error) where B.ObjectType == O {
|
||||
|
||||
try self.refetch(
|
||||
@@ -342,7 +345,7 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
|
||||
from: From<O>,
|
||||
sectionBy: SectionBy<O>?,
|
||||
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void,
|
||||
sourceIdentifier: Any?
|
||||
sourceIdentifier: (any Sendable)?
|
||||
) throws(any Swift.Error) {
|
||||
|
||||
let (newFetchedResultsController, newFetchedResultsControllerDelegate) = Self.recreateFetchedResultsController(
|
||||
|
||||
+35
-9
@@ -23,9 +23,9 @@
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
#if canImport(Combine) && canImport(SwiftUI)
|
||||
#if canImport(Observation) && canImport(SwiftUI)
|
||||
|
||||
import Combine
|
||||
import Observation
|
||||
import SwiftUI
|
||||
|
||||
|
||||
@@ -65,11 +65,12 @@ public struct ListState<Object: DynamicObject>: DynamicProperty {
|
||||
|
||||
- parameter listPublisher: The `ListPublisher` that the `ListState` will observe changes for
|
||||
*/
|
||||
@MainActor
|
||||
public init(
|
||||
_ listPublisher: ListPublisher<Object>
|
||||
) {
|
||||
|
||||
self.observer = .init(listPublisher: listPublisher)
|
||||
self._observer = .init(wrappedValue: .init(listPublisher: listPublisher))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,6 +99,7 @@ public struct ListState<Object: DynamicObject>: DynamicProperty {
|
||||
|
||||
- parameter clauseChain: a `FetchChainableBuilderType` built from a chain of clauses
|
||||
*/
|
||||
@MainActor
|
||||
public init<B: FetchChainableBuilderType>(
|
||||
_ clauseChain: B,
|
||||
in dataStack: DataStack
|
||||
@@ -139,6 +141,7 @@ public struct ListState<Object: DynamicObject>: DynamicProperty {
|
||||
|
||||
- parameter clauseChain: a `SectionMonitorBuilderType` built from a chain of clauses
|
||||
*/
|
||||
@MainActor
|
||||
public init<B: SectionMonitorBuilderType>(
|
||||
_ clauseChain: B,
|
||||
in dataStack: DataStack
|
||||
@@ -174,6 +177,7 @@ public struct ListState<Object: DynamicObject>: DynamicProperty {
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
*/
|
||||
@MainActor
|
||||
public init(
|
||||
_ from: From<Object>,
|
||||
_ fetchClauses: FetchClause...,
|
||||
@@ -212,6 +216,7 @@ public struct ListState<Object: DynamicObject>: DynamicProperty {
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
*/
|
||||
@MainActor
|
||||
public init(
|
||||
_ from: From<Object>,
|
||||
_ fetchClauses: [FetchClause],
|
||||
@@ -256,6 +261,7 @@ public struct ListState<Object: DynamicObject>: DynamicProperty {
|
||||
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
*/
|
||||
@MainActor
|
||||
public init(
|
||||
_ from: From<Object>,
|
||||
_ sectionBy: SectionBy<Object>,
|
||||
@@ -303,6 +309,7 @@ public struct ListState<Object: DynamicObject>: DynamicProperty {
|
||||
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
*/
|
||||
@MainActor
|
||||
public init(
|
||||
_ from: From<Object>,
|
||||
_ sectionBy: SectionBy<Object>,
|
||||
@@ -316,11 +323,13 @@ public struct ListState<Object: DynamicObject>: DynamicProperty {
|
||||
|
||||
// MARK: @propertyWrapper
|
||||
|
||||
@MainActor
|
||||
public var wrappedValue: ListSnapshot<Object> {
|
||||
|
||||
return self.observer.items
|
||||
}
|
||||
|
||||
@MainActor
|
||||
public var projectedValue: ListPublisher<Object> {
|
||||
|
||||
return self.observer.listPublisher
|
||||
@@ -337,23 +346,40 @@ public struct ListState<Object: DynamicObject>: DynamicProperty {
|
||||
|
||||
// MARK: Private
|
||||
|
||||
@ObservedObject
|
||||
@State
|
||||
private var observer: Observer
|
||||
|
||||
|
||||
// MARK: - Observer
|
||||
|
||||
private final class Observer: ObservableObject {
|
||||
@MainActor
|
||||
private final class Observer: Observation.Observable {
|
||||
|
||||
@Published
|
||||
var items: ListSnapshot<Object>
|
||||
private let registrar = ObservationRegistrar()
|
||||
private var _items: ListSnapshot<Object>
|
||||
|
||||
let listPublisher: ListPublisher<Object>
|
||||
|
||||
var items: ListSnapshot<Object> {
|
||||
|
||||
get {
|
||||
|
||||
self.registrar.access(self, keyPath: \.items)
|
||||
return self._items
|
||||
}
|
||||
set {
|
||||
|
||||
self.registrar.withMutation(of: self, keyPath: \.items) {
|
||||
|
||||
self._items = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init(listPublisher: ListPublisher<Object>) {
|
||||
|
||||
self.listPublisher = listPublisher
|
||||
self.items = listPublisher.snapshot
|
||||
self._items = listPublisher.snapshot
|
||||
|
||||
listPublisher.addObserver(self) { [weak self] (listPublisher) in
|
||||
|
||||
@@ -365,7 +391,7 @@ public struct ListState<Object: DynamicObject>: DynamicProperty {
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
isolated deinit {
|
||||
|
||||
self.listPublisher.removeObserver(self)
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ import Foundation
|
||||
/**
|
||||
The `MigrationType` specifies the type of migration required for a store.
|
||||
*/
|
||||
public enum MigrationType: Hashable {
|
||||
public enum MigrationType: Hashable, Sendable {
|
||||
|
||||
/**
|
||||
Indicates that the persistent store matches the latest model version and no migration is needed
|
||||
|
||||
@@ -56,6 +56,6 @@ extension NSManagedObject {
|
||||
|
||||
private struct PropertyKeys {
|
||||
|
||||
static var coreStoreObject: Void?
|
||||
static nonisolated(unsafe) var coreStoreObject: Void?
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,8 +192,8 @@ extension NSManagedObjectContext {
|
||||
|
||||
private struct PropertyKeys {
|
||||
|
||||
static var observerForWillSaveNotification: Void?
|
||||
static var shouldCascadeSavesToParent: Void?
|
||||
static nonisolated(unsafe) var observerForWillSaveNotification: Void?
|
||||
static nonisolated(unsafe) var shouldCascadeSavesToParent: Void?
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -293,30 +293,23 @@ extension NSManagedObjectContext: FetchableSource, QueryableSource {
|
||||
_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObjectID>
|
||||
) throws(CoreStoreError) -> [NSManagedObjectID] {
|
||||
|
||||
var fetchResults: [NSManagedObjectID]?
|
||||
var fetchError: Error?
|
||||
self.performAndWait {
|
||||
|
||||
do {
|
||||
|
||||
fetchResults = try self.fetch(fetchRequest.dynamicCast())
|
||||
return try self.performAndWait {
|
||||
|
||||
return try self.fetch(fetchRequest.staticCast())
|
||||
}
|
||||
}
|
||||
catch {
|
||||
|
||||
fetchError = error
|
||||
}
|
||||
}
|
||||
if let fetchResults = fetchResults {
|
||||
|
||||
return fetchResults
|
||||
}
|
||||
let coreStoreError = CoreStoreError(fetchError)
|
||||
let coreStoreError = CoreStoreError(error)
|
||||
Internals.log(
|
||||
coreStoreError,
|
||||
"Failed executing fetch request."
|
||||
"Failed executing query request."
|
||||
)
|
||||
throw coreStoreError
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: QueryableSource
|
||||
@@ -443,89 +436,74 @@ extension NSManagedObjectContext {
|
||||
_ fetchRequest: Internals.CoreStoreFetchRequest<O>
|
||||
) throws(CoreStoreError) -> O? {
|
||||
|
||||
var fetchResults: [O]?
|
||||
var fetchError: (any Swift.Error)?
|
||||
self.performAndWait {
|
||||
|
||||
do {
|
||||
|
||||
fetchResults = try self.fetch(fetchRequest.staticCast())
|
||||
let fetchResults = try self.performAndWait {
|
||||
|
||||
return try self.fetch(fetchRequest.staticCast())
|
||||
}
|
||||
return fetchResults.first
|
||||
}
|
||||
catch {
|
||||
|
||||
fetchError = error
|
||||
}
|
||||
}
|
||||
if let fetchResults = fetchResults {
|
||||
|
||||
return fetchResults.first
|
||||
}
|
||||
let coreStoreError = CoreStoreError(fetchError)
|
||||
let coreStoreError = CoreStoreError(error)
|
||||
Internals.log(
|
||||
coreStoreError,
|
||||
"Failed executing fetch request."
|
||||
)
|
||||
throw coreStoreError
|
||||
}
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
internal func fetchAll<O: NSManagedObject>(
|
||||
_ fetchRequest: Internals.CoreStoreFetchRequest<O>
|
||||
) throws(CoreStoreError) -> [O] {
|
||||
|
||||
var fetchResults: [O]?
|
||||
var fetchError: (any Swift.Error)?
|
||||
self.performAndWait {
|
||||
|
||||
do {
|
||||
|
||||
fetchResults = try self.fetch(fetchRequest.staticCast())
|
||||
return try self.performAndWait {
|
||||
|
||||
return try self.fetch(fetchRequest.staticCast())
|
||||
}
|
||||
}
|
||||
catch {
|
||||
|
||||
fetchError = error
|
||||
}
|
||||
}
|
||||
if let fetchResults = fetchResults {
|
||||
|
||||
return fetchResults
|
||||
}
|
||||
let coreStoreError = CoreStoreError(fetchError)
|
||||
let coreStoreError = CoreStoreError(error)
|
||||
Internals.log(
|
||||
coreStoreError,
|
||||
"Failed executing fetch request."
|
||||
)
|
||||
throw coreStoreError
|
||||
}
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
internal func fetchCount(
|
||||
_ fetchRequest: Internals.CoreStoreFetchRequest<NSNumber>
|
||||
) throws(CoreStoreError) -> Int {
|
||||
|
||||
var count = 0
|
||||
var countError: (any Swift.Error)?
|
||||
self.performAndWait {
|
||||
|
||||
do {
|
||||
|
||||
count = try self.count(for: fetchRequest.staticCast())
|
||||
let count = try self.performAndWait {
|
||||
|
||||
return try self.count(for: fetchRequest.staticCast())
|
||||
}
|
||||
guard count != NSNotFound else {
|
||||
|
||||
throw CoreStoreError(nil)
|
||||
}
|
||||
return count
|
||||
}
|
||||
catch {
|
||||
|
||||
countError = error
|
||||
}
|
||||
}
|
||||
if count == NSNotFound {
|
||||
|
||||
let coreStoreError = CoreStoreError(countError)
|
||||
let coreStoreError = CoreStoreError(error)
|
||||
Internals.log(
|
||||
coreStoreError,
|
||||
"Failed executing count request."
|
||||
)
|
||||
throw coreStoreError
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
@@ -533,30 +511,24 @@ extension NSManagedObjectContext {
|
||||
_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObjectID>
|
||||
) throws(CoreStoreError) -> NSManagedObjectID? {
|
||||
|
||||
var fetchResults: [NSManagedObjectID]?
|
||||
var fetchError: (any Swift.Error)?
|
||||
self.performAndWait {
|
||||
|
||||
do {
|
||||
|
||||
fetchResults = try self.fetch(fetchRequest.staticCast())
|
||||
let fetchResults = try self.performAndWait {
|
||||
|
||||
return try self.fetch(fetchRequest.staticCast())
|
||||
}
|
||||
return fetchResults.first
|
||||
}
|
||||
catch {
|
||||
|
||||
fetchError = error
|
||||
}
|
||||
}
|
||||
if let fetchResults = fetchResults {
|
||||
|
||||
return fetchResults.first
|
||||
}
|
||||
let coreStoreError = CoreStoreError(fetchError)
|
||||
let coreStoreError = CoreStoreError(error)
|
||||
Internals.log(
|
||||
coreStoreError,
|
||||
"Failed executing fetch request."
|
||||
)
|
||||
throw coreStoreError
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Querying
|
||||
@@ -567,35 +539,29 @@ extension NSManagedObjectContext {
|
||||
fetchRequest: Internals.CoreStoreFetchRequest<NSDictionary>
|
||||
) throws(CoreStoreError) -> U? {
|
||||
|
||||
var fetchResults: [Any]?
|
||||
var fetchError: (any Swift.Error)?
|
||||
self.performAndWait {
|
||||
|
||||
do {
|
||||
|
||||
fetchResults = try self.fetch(fetchRequest.staticCast())
|
||||
}
|
||||
catch {
|
||||
let fetchResults = try self.performAndWait {
|
||||
|
||||
fetchError = error
|
||||
return try self.fetch(fetchRequest.staticCast())
|
||||
}
|
||||
}
|
||||
if let fetchResults = fetchResults {
|
||||
|
||||
if let rawResult = fetchResults.first as? NSDictionary,
|
||||
if let rawResult = fetchResults.first,
|
||||
let rawObject = rawResult[selectTerms.first!.keyPathString] as? U.QueryableNativeType {
|
||||
|
||||
return Select<O, U>.ReturnType.cs_fromQueryableNativeType(rawObject)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
let coreStoreError = CoreStoreError(fetchError)
|
||||
catch {
|
||||
|
||||
let coreStoreError = CoreStoreError(error)
|
||||
Internals.log(
|
||||
coreStoreError,
|
||||
"Failed executing fetch request."
|
||||
"Failed executing query request."
|
||||
)
|
||||
throw coreStoreError
|
||||
}
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
internal func queryValue<O>(
|
||||
@@ -603,65 +569,53 @@ extension NSManagedObjectContext {
|
||||
fetchRequest: Internals.CoreStoreFetchRequest<NSDictionary>
|
||||
) throws(CoreStoreError) -> Any? {
|
||||
|
||||
var fetchResults: [Any]?
|
||||
var fetchError: (any Swift.Error)?
|
||||
self.performAndWait {
|
||||
|
||||
do {
|
||||
|
||||
fetchResults = try self.fetch(fetchRequest.staticCast())
|
||||
}
|
||||
catch {
|
||||
let fetchResults = try self.performAndWait {
|
||||
|
||||
fetchError = error
|
||||
return try self.fetch(fetchRequest.staticCast())
|
||||
}
|
||||
}
|
||||
if let fetchResults = fetchResults {
|
||||
|
||||
if let rawResult = fetchResults.first as? NSDictionary,
|
||||
if let rawResult = fetchResults.first,
|
||||
let rawObject = rawResult[selectTerms.first!.keyPathString] {
|
||||
|
||||
return rawObject
|
||||
}
|
||||
return nil
|
||||
}
|
||||
let coreStoreError = CoreStoreError(fetchError)
|
||||
catch {
|
||||
|
||||
let coreStoreError = CoreStoreError(error)
|
||||
Internals.log(
|
||||
coreStoreError,
|
||||
"Failed executing fetch request."
|
||||
"Failed executing query request."
|
||||
)
|
||||
throw coreStoreError
|
||||
}
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
internal func queryAttributes(
|
||||
_ fetchRequest: Internals.CoreStoreFetchRequest<NSDictionary>
|
||||
) throws(CoreStoreError) -> [[String: Any]] {
|
||||
|
||||
var fetchResults: [Any]?
|
||||
var fetchError: (any Swift.Error)?
|
||||
self.performAndWait {
|
||||
|
||||
do {
|
||||
|
||||
fetchResults = try self.fetch(fetchRequest.staticCast())
|
||||
let fetchResults = try self.performAndWait {
|
||||
|
||||
return try self.fetch(fetchRequest.staticCast())
|
||||
}
|
||||
return NSDictionary.cs_fromQueryResultsNativeType(fetchResults)
|
||||
}
|
||||
catch {
|
||||
|
||||
fetchError = error
|
||||
}
|
||||
}
|
||||
if let fetchResults = fetchResults {
|
||||
|
||||
return NSDictionary.cs_fromQueryResultsNativeType(fetchResults)
|
||||
}
|
||||
let coreStoreError = CoreStoreError(fetchError)
|
||||
let coreStoreError = CoreStoreError(error)
|
||||
Internals.log(
|
||||
coreStoreError,
|
||||
"Failed executing fetch request."
|
||||
"Failed executing query request."
|
||||
)
|
||||
throw coreStoreError
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Deleting
|
||||
@@ -671,36 +625,29 @@ extension NSManagedObjectContext {
|
||||
_ fetchRequest: Internals.CoreStoreFetchRequest<O>
|
||||
) throws(CoreStoreError) -> Int {
|
||||
|
||||
var numberOfDeletedObjects: Int?
|
||||
var fetchError: (any Swift.Error)?
|
||||
self.performAndWait {
|
||||
|
||||
Internals.autoreleasepool {
|
||||
|
||||
do {
|
||||
|
||||
return try self.performAndWait {
|
||||
|
||||
return try Internals.autoreleasepool {
|
||||
|
||||
let fetchResults = try self.fetch(fetchRequest.staticCast())
|
||||
for object in fetchResults {
|
||||
|
||||
self.delete(object)
|
||||
}
|
||||
numberOfDeletedObjects = fetchResults.count
|
||||
return fetchResults.count
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
|
||||
fetchError = error
|
||||
}
|
||||
}
|
||||
}
|
||||
if let numberOfDeletedObjects = numberOfDeletedObjects {
|
||||
|
||||
return numberOfDeletedObjects
|
||||
}
|
||||
let coreStoreError = CoreStoreError(fetchError)
|
||||
let coreStoreError = CoreStoreError(error)
|
||||
Internals.log(
|
||||
coreStoreError,
|
||||
"Failed executing delete request."
|
||||
)
|
||||
throw coreStoreError
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ extension NSManagedObjectContext {
|
||||
|
||||
let context = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
|
||||
context.persistentStoreCoordinator = coordinator
|
||||
context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
|
||||
context.mergePolicy = NSMergePolicy(merge: .mergeByPropertyObjectTrumpMergePolicyType)
|
||||
context.undoManager = nil
|
||||
context.setupForCoreStoreWithContextName("com.corestore.rootcontext")
|
||||
|
||||
@@ -76,16 +76,21 @@ extension NSManagedObjectContext {
|
||||
object: coordinator,
|
||||
closure: { [weak context] (note) -> Void in
|
||||
|
||||
context?.perform { () -> Void in
|
||||
guard let context = context else {
|
||||
|
||||
return
|
||||
}
|
||||
nonisolated(unsafe) let note = note
|
||||
context.perform {
|
||||
|
||||
if let updatedObjectIDs = (note.userInfo?[NSUpdatedObjectsKey] as? Set<NSManagedObjectID>) {
|
||||
|
||||
for objectID in updatedObjectIDs {
|
||||
|
||||
context?.registeredObject(for: objectID)?.willAccessValue(forKey: nil)
|
||||
context.registeredObject(for: objectID)?.willAccessValue(forKey: nil)
|
||||
}
|
||||
}
|
||||
context?.mergeChanges(fromContextDidSave: note)
|
||||
context.mergeChanges(fromContextDidSave: note)
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -100,7 +105,7 @@ extension NSManagedObjectContext {
|
||||
|
||||
let context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
|
||||
context.parent = rootContext
|
||||
context.mergePolicy = NSRollbackMergePolicy
|
||||
context.mergePolicy = NSMergePolicy(merge: .rollbackMergePolicyType)
|
||||
context.undoManager = nil
|
||||
context.setupForCoreStoreWithContextName("com.corestore.maincontext")
|
||||
context.observerForDidSaveNotification = Internals.NotificationObserver(
|
||||
@@ -108,8 +113,10 @@ extension NSManagedObjectContext {
|
||||
object: rootContext,
|
||||
closure: { [weak context] (note) -> Void in
|
||||
|
||||
guard let rootContext = note.object as? NSManagedObjectContext,
|
||||
let context = context else {
|
||||
guard
|
||||
let rootContext = note.object as? NSManagedObjectContext,
|
||||
let context = context
|
||||
else {
|
||||
|
||||
return
|
||||
}
|
||||
@@ -117,7 +124,8 @@ extension NSManagedObjectContext {
|
||||
let saveMetadata = rootContext.saveMetadata
|
||||
context.saveMetadata = saveMetadata
|
||||
|
||||
let mergeChanges = { () -> Void in
|
||||
nonisolated(unsafe) let note = note
|
||||
let mergeChanges = { @Sendable () -> Void in
|
||||
|
||||
if let updatedObjects = (note.userInfo?[NSUpdatedObjectsKey] as? Set<NSManagedObject>) {
|
||||
|
||||
@@ -147,9 +155,9 @@ extension NSManagedObjectContext {
|
||||
|
||||
private struct PropertyKeys {
|
||||
|
||||
static var parentStack: Void?
|
||||
static var observerForDidSaveNotification: Void?
|
||||
static var observerForDidImportUbiquitousContentChangesNotification: Void?
|
||||
static nonisolated(unsafe) var parentStack: Void?
|
||||
static nonisolated(unsafe) var observerForDidSaveNotification: Void?
|
||||
static nonisolated(unsafe) var observerForDidImportUbiquitousContentChangesNotification: Void?
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
|
||||
@@ -142,15 +142,14 @@ extension NSManagedObjectContext {
|
||||
@nonobjc
|
||||
internal func saveSynchronously(
|
||||
waitForMerge: Bool,
|
||||
sourceIdentifier: Any?
|
||||
sourceIdentifier: (any Sendable)?
|
||||
) -> (hasChanges: Bool, error: CoreStoreError?) {
|
||||
|
||||
var result: (hasChanges: Bool, error: CoreStoreError?) = (false, nil)
|
||||
self.performAndWait {
|
||||
return self.performAndWait {
|
||||
|
||||
guard self.hasChanges else {
|
||||
|
||||
return
|
||||
return (false, nil)
|
||||
}
|
||||
do {
|
||||
|
||||
@@ -168,8 +167,7 @@ extension NSManagedObjectContext {
|
||||
saveError,
|
||||
"Failed to save \(Internals.typeName(NSManagedObjectContext.self))."
|
||||
)
|
||||
result = (true, saveError)
|
||||
return
|
||||
return (true, saveError)
|
||||
}
|
||||
if let parentContext = self.parent, self.shouldCascadeSavesToParent {
|
||||
|
||||
@@ -177,20 +175,19 @@ extension NSManagedObjectContext {
|
||||
waitForMerge: waitForMerge,
|
||||
sourceIdentifier: sourceIdentifier
|
||||
)
|
||||
result = (true, error)
|
||||
return (true, error)
|
||||
}
|
||||
else {
|
||||
|
||||
result = (true, nil)
|
||||
return (true, nil)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
internal func saveAsynchronously(
|
||||
sourceIdentifier: Any?,
|
||||
completion: @escaping (_ hasChanges: Bool, _ error: CoreStoreError?) -> Void = { (_, _) in }
|
||||
sourceIdentifier: (any Sendable)?,
|
||||
completion: @escaping @MainActor (_ hasChanges: Bool, _ error: CoreStoreError?) -> Void = { (_, _) in }
|
||||
) {
|
||||
|
||||
self.perform {
|
||||
@@ -259,11 +256,11 @@ extension NSManagedObjectContext {
|
||||
// MARK: Internal
|
||||
|
||||
internal let isSavingSynchronously: Bool
|
||||
internal let sourceIdentifier: Any?
|
||||
internal let sourceIdentifier: (any Sendable)?
|
||||
|
||||
internal init(
|
||||
isSavingSynchronously: Bool,
|
||||
sourceIdentifier: Any?
|
||||
sourceIdentifier: (any Sendable)?
|
||||
) {
|
||||
|
||||
self.isSavingSynchronously = isSavingSynchronously
|
||||
@@ -276,9 +273,9 @@ extension NSManagedObjectContext {
|
||||
|
||||
private struct PropertyKeys {
|
||||
|
||||
static var parentTransaction: Void?
|
||||
static var saveMetadata: Void?
|
||||
static var isTransactionContext: Void?
|
||||
static var isDataStackContext: Void?
|
||||
static nonisolated(unsafe) var parentTransaction: Void?
|
||||
static nonisolated(unsafe) var saveMetadata: Void?
|
||||
static nonisolated(unsafe) var isTransactionContext: Void?
|
||||
static nonisolated(unsafe) var isDataStackContext: Void?
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ extension NSPersistentStore {
|
||||
|
||||
private struct PropertyKeys {
|
||||
|
||||
static var storageInterface: Void?
|
||||
static nonisolated(unsafe) var storageInterface: Void?
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ extension NSPersistentStoreCoordinator {
|
||||
|
||||
@nonobjc
|
||||
internal func performAsynchronously(
|
||||
_ closure: @escaping () -> Void
|
||||
_ closure: @escaping @Sendable () -> Void
|
||||
) {
|
||||
|
||||
self.perform(closure)
|
||||
@@ -41,41 +41,29 @@ extension NSPersistentStoreCoordinator {
|
||||
|
||||
@nonobjc
|
||||
internal func performSynchronously<T>(
|
||||
_ closure: @escaping () -> T
|
||||
_ closure: @Sendable () -> T
|
||||
) -> T {
|
||||
|
||||
var result: T?
|
||||
self.performAndWait {
|
||||
|
||||
result = closure()
|
||||
}
|
||||
return result!
|
||||
return self.performAndWait(closure)
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
internal func performSynchronously<T>(
|
||||
_ closure: @escaping () throws(any Swift.Error) -> T
|
||||
_ closure: @Sendable () throws(any Swift.Error) -> T
|
||||
) throws(CoreStoreError) -> T {
|
||||
|
||||
var closureError: (any Swift.Error)?
|
||||
var result: T?
|
||||
self.performAndWait {
|
||||
|
||||
do {
|
||||
|
||||
result = try closure()
|
||||
return try self.performAndWait {
|
||||
|
||||
return try closure()
|
||||
}
|
||||
}
|
||||
catch {
|
||||
|
||||
closureError = error
|
||||
throw CoreStoreError(error)
|
||||
}
|
||||
}
|
||||
if let closureError = closureError {
|
||||
|
||||
throw CoreStoreError(closureError)
|
||||
}
|
||||
return result!
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
internal func addPersistentStoreSynchronously(
|
||||
@@ -85,6 +73,7 @@ extension NSPersistentStoreCoordinator {
|
||||
options: [AnyHashable: Any]?
|
||||
) throws(CoreStoreError) -> NSPersistentStore {
|
||||
|
||||
nonisolated(unsafe) let options = options
|
||||
return try self.performSynchronously {
|
||||
|
||||
return try self.addPersistentStore(
|
||||
|
||||
@@ -39,7 +39,7 @@ import CoreData
|
||||
|
||||
Observers registered via `addObserver(_:)` are not retained. `ObjectMonitor` only keeps a `weak` reference to all observers, thus keeping itself free from retain-cycles.
|
||||
*/
|
||||
public final class ObjectMonitor<O: DynamicObject>: Hashable, ObjectRepresentation {
|
||||
public final class ObjectMonitor<O: DynamicObject>: Hashable, ObjectRepresentation, @unchecked Sendable {
|
||||
|
||||
/**
|
||||
Returns the `DynamicObject` instance being observed, or `nil` if the object was already deleted.
|
||||
@@ -71,7 +71,7 @@ public final class ObjectMonitor<O: DynamicObject>: Hashable, ObjectRepresentati
|
||||
|
||||
- parameter observer: an `ObjectObserver` to send change notifications to
|
||||
*/
|
||||
public func addObserver<U: ObjectObserver>(_ observer: U) where U.ObjectEntityType == O {
|
||||
public func addObserver<U: ObjectObserver & Sendable>(_ observer: U) where U.ObjectEntityType == O {
|
||||
|
||||
self.unregisterObserver(observer)
|
||||
self.registerObserver(
|
||||
@@ -243,19 +243,19 @@ public final class ObjectMonitor<O: DynamicObject>: Hashable, ObjectRepresentati
|
||||
self.lastCommittedAttributes = (self.object?.cs_toRaw().committedValues(forKeys: nil) as? [String: NSObject]) ?? [:]
|
||||
}
|
||||
|
||||
internal func registerObserver<U: AnyObject>(
|
||||
internal func registerObserver<U: AnyObject & Sendable>(
|
||||
_ observer: U,
|
||||
willChangeObject: @escaping (
|
||||
willChangeObject: @escaping @Sendable (
|
||||
_ observer: U,
|
||||
_ monitor: ObjectMonitor<O>,
|
||||
_ object: O
|
||||
) -> Void,
|
||||
didDeleteObject: @escaping (
|
||||
didDeleteObject: @escaping @Sendable (
|
||||
_ observer: U,
|
||||
_ monitor: ObjectMonitor<O>,
|
||||
_ object: O
|
||||
) -> Void,
|
||||
didUpdateObject: @escaping (
|
||||
didUpdateObject: @escaping @Sendable (
|
||||
_ observer: U,
|
||||
_ monitor: ObjectMonitor<O>,
|
||||
_ object: O,
|
||||
@@ -361,7 +361,7 @@ public final class ObjectMonitor<O: DynamicObject>: Hashable, ObjectRepresentati
|
||||
_ notificationKey: UnsafeRawPointer,
|
||||
name: Notification.Name,
|
||||
toObserver observer: AnyObject,
|
||||
callback: @escaping (_ monitor: ObjectMonitor<O>) -> Void
|
||||
callback: @escaping @Sendable (_ monitor: ObjectMonitor<O>) -> Void
|
||||
) {
|
||||
|
||||
Internals.setAssociatedRetainedObject(
|
||||
@@ -386,7 +386,7 @@ public final class ObjectMonitor<O: DynamicObject>: Hashable, ObjectRepresentati
|
||||
_ notificationKey: UnsafeRawPointer,
|
||||
name: Notification.Name,
|
||||
toObserver observer: AnyObject,
|
||||
callback: @escaping (_ monitor: ObjectMonitor<O>, _ object: O) -> Void
|
||||
callback: @escaping @Sendable (_ monitor: ObjectMonitor<O>, _ object: O) -> Void
|
||||
) {
|
||||
|
||||
Internals.setAssociatedRetainedObject(
|
||||
|
||||
@@ -36,12 +36,12 @@ import CoreData
|
||||
monitor.addObserver(self)
|
||||
```
|
||||
*/
|
||||
public protocol ObjectObserver: AnyObject {
|
||||
public protocol ObjectObserver: AnyObject & Sendable {
|
||||
|
||||
/**
|
||||
The `DynamicObject` type for the observed object
|
||||
*/
|
||||
associatedtype ObjectEntityType: DynamicObject
|
||||
associatedtype ObjectEntityType: DynamicObject & Sendable
|
||||
|
||||
/**
|
||||
Handles processing just before a change to the observed `object` occurs. (Optional)
|
||||
|
||||
@@ -90,7 +90,8 @@ extension ObjectPublisher {
|
||||
|
||||
// MARK: - ObjectSnapshotSubscription
|
||||
|
||||
fileprivate final class ObjectSnapshotSubscription<S: Subscriber>: Subscription where S.Input == Output, S.Failure == Never {
|
||||
fileprivate final class ObjectSnapshotSubscription<S: Subscriber>: Subscription, @unchecked Sendable
|
||||
where S.Input == Output, S.Failure == Never {
|
||||
|
||||
// MARK: FilePrivate
|
||||
|
||||
@@ -114,6 +115,8 @@ extension ObjectPublisher {
|
||||
|
||||
return
|
||||
}
|
||||
Internals.mainActorImmediate { [self] in
|
||||
|
||||
self.publisher.addObserver(
|
||||
self,
|
||||
notifyInitial: self.emitInitialValue,
|
||||
@@ -130,6 +133,7 @@ extension ObjectPublisher {
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Cancellable
|
||||
@@ -138,17 +142,10 @@ extension ObjectPublisher {
|
||||
|
||||
self.subscriber = nil
|
||||
|
||||
if Thread.isMainThread {
|
||||
Internals.mainActorImmediate {
|
||||
|
||||
self.publisher.removeObserver(self)
|
||||
}
|
||||
else {
|
||||
|
||||
DispatchQueue.main.async {
|
||||
|
||||
self.publisher.removeObserver(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -76,6 +76,7 @@ public final class ObjectPublisher<O: DynamicObject>: ObjectRepresentation, Hash
|
||||
- parameter notifyInitial: if `true`, the callback is executed immediately with the current publisher state. Otherwise only succeeding updates will notify the observer. Default value is `false`.
|
||||
- parameter callback: the closure to execute when changes occur
|
||||
*/
|
||||
@MainActor
|
||||
public func addObserver<T: AnyObject>(
|
||||
_ observer: T,
|
||||
notifyInitial: Bool = false,
|
||||
@@ -112,6 +113,7 @@ public final class ObjectPublisher<O: DynamicObject>: ObjectRepresentation, Hash
|
||||
- parameter initialSourceIdentifier: an optional value that identifies the initial callback invocation if `notifyInitial` is `true`.
|
||||
- parameter callback: the closure to execute when changes occur
|
||||
*/
|
||||
@MainActor
|
||||
public func addObserver<T: AnyObject>(
|
||||
_ observer: T,
|
||||
notifyInitial: Bool = false,
|
||||
@@ -145,6 +147,7 @@ public final class ObjectPublisher<O: DynamicObject>: ObjectRepresentation, Hash
|
||||
|
||||
- parameter observer: the object whose notifications will be unregistered
|
||||
*/
|
||||
@MainActor
|
||||
public func removeObserver<T: AnyObject>(_ observer: T) {
|
||||
|
||||
Internals.assert(
|
||||
|
||||
+30
-10
@@ -23,9 +23,9 @@
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
#if canImport(Combine) && canImport(SwiftUI)
|
||||
#if canImport(Observation) && canImport(SwiftUI)
|
||||
|
||||
import Combine
|
||||
import Observation
|
||||
import SwiftUI
|
||||
|
||||
|
||||
@@ -62,19 +62,22 @@ public struct ObjectState<O: DynamicObject>: DynamicProperty {
|
||||
|
||||
- parameter objectPublisher: The `ObjectPublisher` that the `ObjectState` will observe changes for
|
||||
*/
|
||||
@MainActor
|
||||
public init(_ objectPublisher: ObjectPublisher<O>?) {
|
||||
|
||||
self.observer = .init(objectPublisher: objectPublisher)
|
||||
self._observer = .init(wrappedValue: .init(objectPublisher: objectPublisher))
|
||||
}
|
||||
|
||||
|
||||
// MARK: @propertyWrapper
|
||||
|
||||
@MainActor
|
||||
public var wrappedValue: ObjectSnapshot<O>? {
|
||||
|
||||
return self.observer.item
|
||||
}
|
||||
|
||||
@MainActor
|
||||
public var projectedValue: ObjectPublisher<O>? {
|
||||
|
||||
return self.observer.objectPublisher
|
||||
@@ -91,19 +94,36 @@ public struct ObjectState<O: DynamicObject>: DynamicProperty {
|
||||
|
||||
// MARK: Private
|
||||
|
||||
@ObservedObject
|
||||
@State
|
||||
private var observer: Observer
|
||||
|
||||
|
||||
// MARK: - Observer
|
||||
|
||||
private final class Observer: ObservableObject {
|
||||
@MainActor
|
||||
private final class Observer: Observation.Observable {
|
||||
|
||||
@Published
|
||||
var item: ObjectSnapshot<O>?
|
||||
private let registrar = ObservationRegistrar()
|
||||
private var _item: ObjectSnapshot<O>?
|
||||
|
||||
let objectPublisher: ObjectPublisher<O>?
|
||||
|
||||
var item: ObjectSnapshot<O>? {
|
||||
|
||||
get {
|
||||
|
||||
self.registrar.access(self, keyPath: \.item)
|
||||
return self._item
|
||||
}
|
||||
set {
|
||||
|
||||
self.registrar.withMutation(of: self, keyPath: \.item) {
|
||||
|
||||
self._item = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init(objectPublisher: ObjectPublisher<O>?) {
|
||||
|
||||
guard
|
||||
@@ -112,12 +132,12 @@ public struct ObjectState<O: DynamicObject>: DynamicProperty {
|
||||
else {
|
||||
|
||||
self.objectPublisher = nil
|
||||
self.item = nil
|
||||
self._item = nil
|
||||
return
|
||||
}
|
||||
|
||||
self.objectPublisher = objectPublisher
|
||||
self.item = objectPublisher.snapshot
|
||||
self._item = objectPublisher.snapshot
|
||||
|
||||
objectPublisher.addObserver(self) { [weak self] (objectPublisher) in
|
||||
|
||||
@@ -129,7 +149,7 @@ public struct ObjectState<O: DynamicObject>: DynamicProperty {
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
isolated deinit {
|
||||
|
||||
self.objectPublisher?.removeObserver(self)
|
||||
}
|
||||
|
||||
@@ -36,7 +36,8 @@ extension Progress {
|
||||
- parameter closure: the closure to execute on progress change
|
||||
*/
|
||||
@nonobjc
|
||||
public func setProgressHandler(_ closure: ((_ progress: Progress) -> Void)?) {
|
||||
@MainActor
|
||||
public func setProgressHandler(_ closure: (@MainActor (_ progress: Progress) -> Void)?) {
|
||||
|
||||
self.progressObserver.progressHandler = closure
|
||||
}
|
||||
@@ -46,15 +47,19 @@ extension Progress {
|
||||
|
||||
private struct PropertyKeys {
|
||||
|
||||
static var progressObserver: Void?
|
||||
static nonisolated(unsafe) var progressObserver: Void?
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
@MainActor
|
||||
private var progressObserver: ProgressObserver {
|
||||
|
||||
get {
|
||||
|
||||
let object: ProgressObserver? = Internals.getAssociatedObjectForKey(&PropertyKeys.progressObserver, inObject: self)
|
||||
let object: ProgressObserver? = Internals.getAssociatedObjectForKey(
|
||||
&PropertyKeys.progressObserver,
|
||||
inObject: self
|
||||
)
|
||||
if let observer = object {
|
||||
|
||||
return observer
|
||||
@@ -76,11 +81,12 @@ extension Progress {
|
||||
// MARK: - ProgressObserver
|
||||
|
||||
@objc
|
||||
private final class ProgressObserver: NSObject {
|
||||
private final class ProgressObserver: NSObject, @unchecked Sendable {
|
||||
|
||||
private unowned let progress: Progress
|
||||
|
||||
fileprivate var progressHandler: ((_ progress: Progress) -> Void)? {
|
||||
@MainActor
|
||||
fileprivate var progressHandler: (@MainActor (_ progress: Progress) -> Void)? {
|
||||
|
||||
didSet {
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ import CoreData
|
||||
|
||||
- Warning: The default SQLite file location for the `LegacySQLiteStore` and `SQLiteStore` are different. If the app was depending on CoreStore's default directories prior to 2.0.0, make sure to use the `SQLiteStore.legacy(...)` factory methods to create the `SQLiteStore` instead of using initializers directly.
|
||||
*/
|
||||
public final class SQLiteStore: LocalStorage {
|
||||
public final class SQLiteStore: LocalStorage, @unchecked Sendable {
|
||||
|
||||
/**
|
||||
Initializes an SQLite store interface from the given SQLite file URL. When this instance is passed to the `DataStack`'s `addStorage()` methods, a new SQLite file will be created if it does not exist.
|
||||
@@ -165,10 +165,13 @@ public final class SQLiteStore: LocalStorage {
|
||||
[NSSQLitePragmasOption: ["journal_mode": "WAL"]]
|
||||
```
|
||||
*/
|
||||
public let storeOptions: [AnyHashable: Any]? = [
|
||||
public var storeOptions: [AnyHashable: Any]? {
|
||||
|
||||
return [
|
||||
NSSQLitePragmasOption: ["journal_mode": "WAL"],
|
||||
NSBinaryStoreInsecureDecodingCompatibilityOption: true
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
Do not call directly. Used by the `DataStack` internally.
|
||||
|
||||
@@ -31,7 +31,7 @@ import CoreData
|
||||
/**
|
||||
The `StorageInterface` represents the data store managed (or to be managed) by the `DataStack`. When added to the `DataStack`, the `StorageInterface` serves as the interface for the `NSPersistentStore`. This may be a database file, an in-memory store, etc.
|
||||
*/
|
||||
public protocol StorageInterface: AnyObject {
|
||||
public protocol StorageInterface: AnyObject, Sendable {
|
||||
|
||||
/**
|
||||
The string identifier for the `NSPersistentStore`'s `type` property. This is the same string CoreStore will use to create the `NSPersistentStore` from the `NSPersistentStoreCoordinator`'s `addPersistentStoreWithType(...)` method.
|
||||
@@ -68,7 +68,7 @@ public protocol StorageInterface: AnyObject {
|
||||
/**
|
||||
The `LocalStorageOptions` provides settings that tells the `DataStack` how to setup the persistent store for `LocalStorage` implementers.
|
||||
*/
|
||||
public struct LocalStorageOptions: OptionSet, ExpressibleByNilLiteral {
|
||||
public struct LocalStorageOptions: OptionSet, ExpressibleByNilLiteral, Sendable {
|
||||
|
||||
/**
|
||||
Tells the `DataStack` that the store should not be migrated or recreated, and should simply fail on model mismatch
|
||||
|
||||
@@ -164,7 +164,7 @@ public final class SynchronousDataTransaction: BaseDataTransaction {
|
||||
internal init(
|
||||
mainContext: NSManagedObjectContext,
|
||||
queue: DispatchQueue,
|
||||
sourceIdentifier: Any?
|
||||
sourceIdentifier: (any Sendable)?
|
||||
) {
|
||||
|
||||
super.init(
|
||||
|
||||
@@ -119,7 +119,7 @@ extension UnsafeDataTransaction {
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
*/
|
||||
public func monitorList<O>(
|
||||
createAsynchronously: @escaping (ListMonitor<O>) -> Void,
|
||||
createAsynchronously: @escaping @Sendable (ListMonitor<O>) -> Void,
|
||||
_ from: From<O>,
|
||||
_ fetchClauses: FetchClause...
|
||||
) {
|
||||
@@ -135,7 +135,7 @@ extension UnsafeDataTransaction {
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
*/
|
||||
public func monitorList<O>(
|
||||
createAsynchronously: @escaping (ListMonitor<O>) -> Void,
|
||||
createAsynchronously: @escaping @Sendable (ListMonitor<O>) -> Void,
|
||||
_ from: From<O>,
|
||||
_ fetchClauses: [FetchClause]
|
||||
) {
|
||||
@@ -174,7 +174,7 @@ extension UnsafeDataTransaction {
|
||||
- parameter clauseChain: a `FetchChainableBuilderType` built from a chain of clauses
|
||||
*/
|
||||
public func monitorList<B: FetchChainableBuilderType>(
|
||||
createAsynchronously: @escaping (ListMonitor<B.ObjectType>) -> Void,
|
||||
createAsynchronously: @escaping @Sendable (ListMonitor<B.ObjectType>) -> Void,
|
||||
_ clauseChain: B
|
||||
) {
|
||||
|
||||
@@ -265,7 +265,7 @@ extension UnsafeDataTransaction {
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
*/
|
||||
public func monitorSectionedList<O>(
|
||||
createAsynchronously: @escaping (ListMonitor<O>) -> Void,
|
||||
createAsynchronously: @escaping @Sendable (ListMonitor<O>) -> Void,
|
||||
_ from: From<O>,
|
||||
_ sectionBy: SectionBy<O>,
|
||||
_ fetchClauses: FetchClause...
|
||||
@@ -283,7 +283,7 @@ extension UnsafeDataTransaction {
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
*/
|
||||
public func monitorSectionedList<O>(
|
||||
createAsynchronously: @escaping (ListMonitor<O>) -> Void,
|
||||
createAsynchronously: @escaping @Sendable (ListMonitor<O>) -> Void,
|
||||
_ from: From<O>,
|
||||
_ sectionBy: SectionBy<O>,
|
||||
_ fetchClauses: [FetchClause]
|
||||
@@ -323,7 +323,7 @@ extension UnsafeDataTransaction {
|
||||
- parameter clauseChain: a `SectionMonitorBuilderType` built from a chain of clauses
|
||||
*/
|
||||
public func monitorSectionedList<B: SectionMonitorBuilderType>(
|
||||
createAsynchronously: @escaping (ListMonitor<B.ObjectType>) -> Void,
|
||||
createAsynchronously: @escaping @Sendable (ListMonitor<B.ObjectType>) -> Void,
|
||||
_ clauseChain: B
|
||||
) {
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ import CoreData
|
||||
/**
|
||||
The `UnsafeDataTransaction` provides an interface for non-contiguous `NSManagedObject` or `CoreStoreObject` creates, updates, and deletes. This is useful for making temporary changes, such as partially filled forms. An unsafe transaction object should typically be only used from the main queue.
|
||||
*/
|
||||
public final class UnsafeDataTransaction: BaseDataTransaction {
|
||||
public final class UnsafeDataTransaction: BaseDataTransaction, @unchecked Sendable {
|
||||
|
||||
// MARK: -
|
||||
|
||||
@@ -42,7 +42,7 @@ public final class UnsafeDataTransaction: BaseDataTransaction {
|
||||
- parameter completion: the block executed after the save completes. Success or failure is reported by the optional `error` argument of the block.
|
||||
*/
|
||||
public func commit(
|
||||
_ completion: @escaping (_ error: CoreStoreError?) -> Void
|
||||
_ completion: @escaping @Sendable (_ error: CoreStoreError?) -> Void
|
||||
) {
|
||||
|
||||
self.context.saveAsynchronously(
|
||||
@@ -142,7 +142,7 @@ public final class UnsafeDataTransaction: BaseDataTransaction {
|
||||
*/
|
||||
public func beginUnsafe(
|
||||
supportsUndo: Bool = false,
|
||||
sourceIdentifier: Any? = nil
|
||||
sourceIdentifier: (any Sendable)? = nil
|
||||
) -> UnsafeDataTransaction {
|
||||
|
||||
return UnsafeDataTransaction(
|
||||
@@ -160,7 +160,7 @@ public final class UnsafeDataTransaction: BaseDataTransaction {
|
||||
mainContext: NSManagedObjectContext,
|
||||
queue: DispatchQueue,
|
||||
supportsUndo: Bool,
|
||||
sourceIdentifier: Any?
|
||||
sourceIdentifier: (any Sendable)?
|
||||
) {
|
||||
|
||||
super.init(
|
||||
|
||||
Reference in New Issue
Block a user