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_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
INFOPLIST_FILE = Sources/Info.plist;
|
INFOPLIST_FILE = Sources/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
|
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 13.5;
|
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = YES;
|
MTL_ENABLE_DEBUG_INFO = YES;
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
OTHER_SWIFT_FLAGS = "-D DEBUG";
|
OTHER_SWIFT_FLAGS = "-D DEBUG";
|
||||||
@@ -3027,12 +3027,12 @@
|
|||||||
SWIFT_COMPILATION_MODE = singlefile;
|
SWIFT_COMPILATION_MODE = singlefile;
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
SWIFT_SWIFT3_OBJC_INFERENCE = Off;
|
SWIFT_SWIFT3_OBJC_INFERENCE = Off;
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 6.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
TVOS_DEPLOYMENT_TARGET = 16.6;
|
TVOS_DEPLOYMENT_TARGET = 17.0;
|
||||||
VERSIONING_SYSTEM = "apple-generic";
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
VERSION_INFO_PREFIX = "";
|
VERSION_INFO_PREFIX = "";
|
||||||
WATCHOS_DEPLOYMENT_TARGET = 9.6;
|
WATCHOS_DEPLOYMENT_TARGET = 10.0;
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
};
|
};
|
||||||
@@ -3083,8 +3083,8 @@
|
|||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
INFOPLIST_FILE = Sources/Info.plist;
|
INFOPLIST_FILE = Sources/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
|
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 13.5;
|
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.johnestropia.CoreStore;
|
PRODUCT_BUNDLE_IDENTIFIER = com.johnestropia.CoreStore;
|
||||||
PRODUCT_NAME = CoreStore;
|
PRODUCT_NAME = CoreStore;
|
||||||
@@ -3092,13 +3092,13 @@
|
|||||||
SWIFT_COMPILATION_MODE = wholemodule;
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||||
SWIFT_SWIFT3_OBJC_INFERENCE = Off;
|
SWIFT_SWIFT3_OBJC_INFERENCE = Off;
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 6.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
TVOS_DEPLOYMENT_TARGET = 16.6;
|
TVOS_DEPLOYMENT_TARGET = 17.0;
|
||||||
VALIDATE_PRODUCT = YES;
|
VALIDATE_PRODUCT = YES;
|
||||||
VERSIONING_SYSTEM = "apple-generic";
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
VERSION_INFO_PREFIX = "";
|
VERSION_INFO_PREFIX = "";
|
||||||
WATCHOS_DEPLOYMENT_TARGET = 9.6;
|
WATCHOS_DEPLOYMENT_TARGET = 10.0;
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
@@ -3366,7 +3366,7 @@
|
|||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
"@loader_path/Frameworks",
|
"@loader_path/Frameworks",
|
||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 13.5;
|
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||||
MARKETING_VERSION = 9.3.0;
|
MARKETING_VERSION = 9.3.0;
|
||||||
OTHER_LDFLAGS = (
|
OTHER_LDFLAGS = (
|
||||||
"-weak_framework",
|
"-weak_framework",
|
||||||
@@ -3401,7 +3401,7 @@
|
|||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
"@loader_path/Frameworks",
|
"@loader_path/Frameworks",
|
||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 13.5;
|
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||||
MARKETING_VERSION = 9.3.0;
|
MARKETING_VERSION = 9.3.0;
|
||||||
OTHER_LDFLAGS = (
|
OTHER_LDFLAGS = (
|
||||||
"-weak_framework",
|
"-weak_framework",
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import XCTest
|
import XCTest
|
||||||
|
import os
|
||||||
|
|
||||||
@testable
|
@testable
|
||||||
import CoreStore
|
import CoreStore
|
||||||
@@ -47,7 +48,11 @@ class BaseTestCase: XCTestCase {
|
|||||||
// MARK: Internal
|
// MARK: Internal
|
||||||
|
|
||||||
@nonobjc
|
@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(
|
let stack = DataStack(
|
||||||
xcodeModelName: "Model",
|
xcodeModelName: "Model",
|
||||||
@@ -79,6 +84,7 @@ class BaseTestCase: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@nonobjc
|
@nonobjc
|
||||||
|
@MainActor
|
||||||
func expectLogger<T>(_ expectations: [TestLogger.Expectation], closure: () throws -> T) rethrows -> T {
|
func expectLogger<T>(_ expectations: [TestLogger.Expectation], closure: () throws -> T) rethrows -> T {
|
||||||
|
|
||||||
CoreStoreDefaults.logger = TestLogger(self.prepareLoggerExpectations(expectations))
|
CoreStoreDefaults.logger = TestLogger(self.prepareLoggerExpectations(expectations))
|
||||||
@@ -97,6 +103,7 @@ class BaseTestCase: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@nonobjc
|
@nonobjc
|
||||||
|
@MainActor
|
||||||
func expectError<T>(code: CoreStoreErrorCode, closure: () throws -> T) {
|
func expectError<T>(code: CoreStoreErrorCode, closure: () throws -> T) {
|
||||||
|
|
||||||
CoreStoreDefaults.logger = TestLogger(self.prepareLoggerExpectations([.logError]))
|
CoreStoreDefaults.logger = TestLogger(self.prepareLoggerExpectations([.logError]))
|
||||||
@@ -135,12 +142,14 @@ class BaseTestCase: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@nonobjc
|
@nonobjc
|
||||||
|
@MainActor
|
||||||
func checkExpectationsImmediately() {
|
func checkExpectationsImmediately() {
|
||||||
|
|
||||||
self.waitForExpectations(timeout: 0, handler: { _ in })
|
self.waitForExpectations(timeout: 0, handler: { _ in })
|
||||||
}
|
}
|
||||||
|
|
||||||
@nonobjc
|
@nonobjc
|
||||||
|
@MainActor
|
||||||
func waitAndCheckExpectations() {
|
func waitAndCheckExpectations() {
|
||||||
|
|
||||||
self.waitForExpectations(timeout: 10, handler: {_ in })
|
self.waitForExpectations(timeout: 10, handler: {_ in })
|
||||||
@@ -174,7 +183,7 @@ class BaseTestCase: XCTestCase {
|
|||||||
|
|
||||||
// MARK: - TestLogger
|
// MARK: - TestLogger
|
||||||
|
|
||||||
class TestLogger: CoreStoreLogger {
|
final class TestLogger: CoreStoreLogger {
|
||||||
|
|
||||||
enum Expectation {
|
enum Expectation {
|
||||||
|
|
||||||
@@ -187,13 +196,13 @@ class TestLogger: CoreStoreLogger {
|
|||||||
|
|
||||||
init(_ expectations: [Expectation: XCTestExpectation]) {
|
init(_ expectations: [Expectation: XCTestExpectation]) {
|
||||||
|
|
||||||
self.expectations = expectations
|
self.expectations = .init(initialState: expectations)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// MARK: CoreStoreLogger
|
// MARK: CoreStoreLogger
|
||||||
|
|
||||||
var enableObjectConcurrencyDebugging: Bool = true
|
let enableObjectConcurrencyDebugging: Bool = true
|
||||||
|
|
||||||
func log(level: LogLevel, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
func log(level: LogLevel, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
||||||
|
|
||||||
@@ -227,18 +236,21 @@ class TestLogger: CoreStoreLogger {
|
|||||||
|
|
||||||
// MARK: Private
|
// MARK: Private
|
||||||
|
|
||||||
private var expectations: [Expectation: XCTestExpectation]
|
private let expectations: OSAllocatedUnfairLock<[Expectation: XCTestExpectation]>
|
||||||
|
|
||||||
private func fulfill(_ expectation: Expectation) {
|
private func fulfill(_ expectation: Expectation) {
|
||||||
|
|
||||||
if let instance = self.expectations[expectation] {
|
self.expectations.withLock {
|
||||||
|
|
||||||
|
if let instance = $0[expectation] {
|
||||||
|
|
||||||
instance.fulfill()
|
instance.fulfill()
|
||||||
self.expectations[expectation] = nil
|
$0[expectation] = nil
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
||||||
XCTFail("Unexpected Logger Action: \(expectation)")
|
XCTFail("Unexpected Logger Action: \(expectation)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import CoreStore
|
|||||||
class ConvenienceTests: BaseTestCase {
|
class ConvenienceTests: BaseTestCase {
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
|
@MainActor
|
||||||
dynamic func test_ThatDataStacks_CanCreateFetchedResultsControllers() {
|
dynamic func test_ThatDataStacks_CanCreateFetchedResultsControllers() {
|
||||||
|
|
||||||
self.prepareStack { (stack) in
|
self.prepareStack { (stack) in
|
||||||
@@ -62,6 +63,7 @@ class ConvenienceTests: BaseTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
|
@MainActor
|
||||||
dynamic func test_ThatUnsafeDataTransactions_CanCreateFetchedResultsControllers() {
|
dynamic func test_ThatUnsafeDataTransactions_CanCreateFetchedResultsControllers() {
|
||||||
|
|
||||||
self.prepareStack { (stack) in
|
self.prepareStack { (stack) in
|
||||||
|
|||||||
@@ -162,6 +162,18 @@ class Person: CoreStoreObject {
|
|||||||
@Field.Relationship("_spouseInverse", inverse: \.$spouse)
|
@Field.Relationship("_spouseInverse", inverse: \.$spouse)
|
||||||
private var spouseInverse: Person?
|
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? {
|
private static func getDisplayName(_ object: ObjectProxy<Person>, _ field: ObjectProxy<Person>.FieldProxy<String?>) -> String? {
|
||||||
|
|
||||||
if let value = field.primitiveValue {
|
if let value = field.primitiveValue {
|
||||||
@@ -203,7 +215,7 @@ class DynamicModelTests: BaseTestDataTestCase {
|
|||||||
versionLock: [
|
versionLock: [
|
||||||
"Animal": [0x1b59d511019695cf, 0xdeb97e86c5eff179, 0x1cfd80745646cb3, 0x4ff99416175b5b9a],
|
"Animal": [0x1b59d511019695cf, 0xdeb97e86c5eff179, 0x1cfd80745646cb3, 0x4ff99416175b5b9a],
|
||||||
"Dog": [0xad6de93adc5565d, 0x7897e51253eba5a3, 0xd12b9ce0b13600f3, 0x5a4827cd794cd15e],
|
"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 willSetPriorObserverDone = self.expectation(description: "willSet-observe-prior-done")
|
||||||
let willSetNotPriorObserverDone = self.expectation(description: "willSet-observe-notPrior-done")
|
let willSetNotPriorObserverDone = self.expectation(description: "willSet-observe-notPrior-done")
|
||||||
let didSetObserverDone = self.expectation(description: "didSet-observe-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(
|
stack.perform(
|
||||||
asynchronous: { (transaction) in
|
asynchronous: { (transaction) in
|
||||||
|
|
||||||
@@ -373,11 +393,19 @@ class DynamicModelTests: BaseTestDataTestCase {
|
|||||||
person.job = .engineer
|
person.job = .engineer
|
||||||
XCTAssertEqual(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)!
|
let personSnapshot2 = person.asSnapshot(in: transaction)!
|
||||||
XCTAssertEqual(person.name, personSnapshot2.$name)
|
XCTAssertEqual(person.name, personSnapshot2.$name)
|
||||||
XCTAssertEqual(person.title, personSnapshot2.$title)
|
XCTAssertEqual(person.title, personSnapshot2.$title)
|
||||||
XCTAssertEqual(person.displayName, personSnapshot2.$displayName)
|
XCTAssertEqual(person.displayName, personSnapshot2.$displayName)
|
||||||
XCTAssertEqual(person.job, personSnapshot2.$job)
|
XCTAssertEqual(person.job, personSnapshot2.$job)
|
||||||
|
XCTAssertEqual(person.ownerInfosJson, personSnapshot2.$ownerInfosJson)
|
||||||
|
XCTAssertEqual(person.ownerInfosPlist, personSnapshot2.$ownerInfosPlist)
|
||||||
|
|
||||||
var personSnapshot3 = personSnapshot2
|
var personSnapshot3 = personSnapshot2
|
||||||
personSnapshot3.$name = "James"
|
personSnapshot3.$name = "James"
|
||||||
@@ -390,6 +418,8 @@ class DynamicModelTests: BaseTestDataTestCase {
|
|||||||
XCTAssertEqual(personSnapshot3.$name, "James")
|
XCTAssertEqual(personSnapshot3.$name, "James")
|
||||||
XCTAssertEqual(personSnapshot3.$displayName, "Sir John")
|
XCTAssertEqual(personSnapshot3.$displayName, "Sir John")
|
||||||
XCTAssertEqual(personSnapshot3.$job, .engineer)
|
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.$name, "John")
|
||||||
XCTAssertEqual(personPublisher.$displayName, "Sir John")
|
XCTAssertEqual(personPublisher.$displayName, "Sir John")
|
||||||
XCTAssertEqual(personPublisher.$job, .engineer)
|
XCTAssertEqual(personPublisher.$job, .engineer)
|
||||||
|
XCTAssertEqual(personPublisher.$ownerInfosJson, ownerInfos)
|
||||||
|
XCTAssertEqual(personPublisher.$ownerInfosPlist, ownerInfos)
|
||||||
|
|
||||||
updateDone.fulfill()
|
updateDone.fulfill()
|
||||||
},
|
},
|
||||||
@@ -444,6 +476,8 @@ class DynamicModelTests: BaseTestDataTestCase {
|
|||||||
XCTAssertEqual(person!.customField.string, "customString")
|
XCTAssertEqual(person!.customField.string, "customString")
|
||||||
XCTAssertEqual(person!.job, .engineer)
|
XCTAssertEqual(person!.job, .engineer)
|
||||||
XCTAssertEqual(person!.pets.first, dog)
|
XCTAssertEqual(person!.pets.first, dog)
|
||||||
|
XCTAssertEqual(person!.ownerInfosJson, ownerInfos)
|
||||||
|
XCTAssertEqual(person!.ownerInfosPlist, ownerInfos)
|
||||||
|
|
||||||
let p3 = Where<Dog>({ $0.$age == 10 })
|
let p3 = Where<Dog>({ $0.$age == 10 })
|
||||||
XCTAssertEqual(p3.predicate, NSPredicate(format: "%K == %d", "age", 10))
|
XCTAssertEqual(p3.predicate, NSPredicate(format: "%K == %d", "age", 10))
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ final class GroupByTests: BaseTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
|
@MainActor
|
||||||
dynamic func test_ThatGroupByClauses_ApplyToFetchRequestsCorrectly() {
|
dynamic func test_ThatGroupByClauses_ApplyToFetchRequestsCorrectly() {
|
||||||
|
|
||||||
self.prepareStack { (dataStack) in
|
self.prepareStack { (dataStack) in
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import CoreStore
|
|||||||
class ListPublisherTests: BaseTestDataTestCase {
|
class ListPublisherTests: BaseTestDataTestCase {
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
|
@MainActor
|
||||||
dynamic func test_ThatListPublishers_CanReceiveInsertNotifications() {
|
dynamic func test_ThatListPublishers_CanReceiveInsertNotifications() {
|
||||||
|
|
||||||
self.prepareStack { (stack) in
|
self.prepareStack { (stack) in
|
||||||
@@ -62,7 +63,7 @@ class ListPublisherTests: BaseTestDataTestCase {
|
|||||||
|
|
||||||
let saveExpectation = self.expectation(description: "save")
|
let saveExpectation = self.expectation(description: "save")
|
||||||
stack.perform(
|
stack.perform(
|
||||||
asynchronous: { (transaction) -> Bool in
|
asynchronous: { [dateFormatter] (transaction) -> Bool in
|
||||||
|
|
||||||
let object = transaction.create(Into<TestEntity1>())
|
let object = transaction.create(Into<TestEntity1>())
|
||||||
object.testBoolean = NSNumber(value: true)
|
object.testBoolean = NSNumber(value: true)
|
||||||
@@ -70,7 +71,7 @@ class ListPublisherTests: BaseTestDataTestCase {
|
|||||||
object.testDecimal = NSDecimalNumber(string: "1")
|
object.testDecimal = NSDecimalNumber(string: "1")
|
||||||
object.testString = "nil:TestEntity1:1"
|
object.testString = "nil:TestEntity1:1"
|
||||||
object.testData = ("nil:TestEntity1:1" as NSString).data(using: String.Encoding.utf8.rawValue)!
|
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
|
return transaction.hasChanges
|
||||||
},
|
},
|
||||||
@@ -92,6 +93,7 @@ class ListPublisherTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
|
@MainActor
|
||||||
dynamic func test_ThatListPublishers_CanReceiveUpdateNotifications() {
|
dynamic func test_ThatListPublishers_CanReceiveUpdateNotifications() {
|
||||||
|
|
||||||
self.prepareStack { (stack) in
|
self.prepareStack { (stack) in
|
||||||
@@ -126,7 +128,7 @@ class ListPublisherTests: BaseTestDataTestCase {
|
|||||||
|
|
||||||
let saveExpectation = self.expectation(description: "save")
|
let saveExpectation = self.expectation(description: "save")
|
||||||
stack.perform(
|
stack.perform(
|
||||||
asynchronous: { (transaction) -> Bool in
|
asynchronous: { [dateFormatter] (transaction) -> Bool in
|
||||||
|
|
||||||
if let object = try transaction.fetchOne(
|
if let object = try transaction.fetchOne(
|
||||||
From<TestEntity1>(),
|
From<TestEntity1>(),
|
||||||
@@ -136,7 +138,7 @@ class ListPublisherTests: BaseTestDataTestCase {
|
|||||||
object.testDecimal = NSDecimalNumber(string: "11")
|
object.testDecimal = NSDecimalNumber(string: "11")
|
||||||
object.testString = "nil:TestEntity1:11"
|
object.testString = "nil:TestEntity1:11"
|
||||||
object.testData = ("nil:TestEntity1:11" as NSString).data(using: String.Encoding.utf8.rawValue)!
|
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 {
|
else {
|
||||||
|
|
||||||
@@ -150,7 +152,7 @@ class ListPublisherTests: BaseTestDataTestCase {
|
|||||||
object.testDecimal = NSDecimalNumber(string: "22")
|
object.testDecimal = NSDecimalNumber(string: "22")
|
||||||
object.testString = "nil:TestEntity1:22"
|
object.testString = "nil:TestEntity1:22"
|
||||||
object.testData = ("nil:TestEntity1:22" as NSString).data(using: String.Encoding.utf8.rawValue)!
|
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 {
|
else {
|
||||||
|
|
||||||
@@ -176,6 +178,7 @@ class ListPublisherTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
|
@MainActor
|
||||||
dynamic func test_ThatListPublishers_CanReceiveMoveNotifications() {
|
dynamic func test_ThatListPublishers_CanReceiveMoveNotifications() {
|
||||||
|
|
||||||
self.prepareStack { (stack) in
|
self.prepareStack { (stack) in
|
||||||
@@ -242,6 +245,7 @@ class ListPublisherTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
|
@MainActor
|
||||||
dynamic func test_ThatListPublishers_CanReceiveDeleteNotifications() {
|
dynamic func test_ThatListPublishers_CanReceiveDeleteNotifications() {
|
||||||
|
|
||||||
self.prepareStack { (stack) in
|
self.prepareStack { (stack) in
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import XCTest
|
import XCTest
|
||||||
|
import os
|
||||||
|
|
||||||
@testable
|
@testable
|
||||||
import CoreStore
|
import CoreStore
|
||||||
@@ -34,6 +35,7 @@ import CoreStore
|
|||||||
class ObjectObserverTests: BaseTestDataTestCase {
|
class ObjectObserverTests: BaseTestDataTestCase {
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
|
@MainActor
|
||||||
dynamic func test_ThatObjectObservers_CanReceiveUpdateNotifications() {
|
dynamic func test_ThatObjectObservers_CanReceiveUpdateNotifications() {
|
||||||
|
|
||||||
self.prepareStack { (stack) in
|
self.prepareStack { (stack) in
|
||||||
@@ -54,13 +56,16 @@ class ObjectObserverTests: BaseTestDataTestCase {
|
|||||||
XCTAssertEqual(monitor.object, object)
|
XCTAssertEqual(monitor.object, object)
|
||||||
XCTAssertFalse(monitor.isObjectDeleted)
|
XCTAssertFalse(monitor.isObjectDeleted)
|
||||||
|
|
||||||
var events = 0
|
let events: OSAllocatedUnfairLock<Int> = .init(initialState: 0)
|
||||||
|
|
||||||
_ = self.expectation(
|
_ = self.expectation(
|
||||||
forNotification: NSNotification.Name(rawValue: "objectMonitor:willUpdateObject:"),
|
forNotification: NSNotification.Name(rawValue: "objectMonitor:willUpdateObject:"),
|
||||||
object: observer,
|
object: observer,
|
||||||
handler: { (note) -> Bool in
|
handler: { (note) -> Bool in
|
||||||
|
|
||||||
|
nonisolated(unsafe) let note = note
|
||||||
|
return events.withLock { events in
|
||||||
|
|
||||||
XCTAssertEqual(events, 0)
|
XCTAssertEqual(events, 0)
|
||||||
XCTAssertEqual(
|
XCTAssertEqual(
|
||||||
((note.userInfo as NSDictionary?) ?? [:]),
|
((note.userInfo as NSDictionary?) ?? [:]),
|
||||||
@@ -72,12 +77,16 @@ class ObjectObserverTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
return events == 0
|
return events == 0
|
||||||
}
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
_ = self.expectation(
|
_ = self.expectation(
|
||||||
forNotification: NSNotification.Name(rawValue: "objectMonitor:didUpdateObject:changedPersistentKeys:"),
|
forNotification: NSNotification.Name(rawValue: "objectMonitor:didUpdateObject:changedPersistentKeys:"),
|
||||||
object: observer,
|
object: observer,
|
||||||
handler: { (note) -> Bool in
|
handler: { (note) -> Bool in
|
||||||
|
|
||||||
|
nonisolated(unsafe) let note = note
|
||||||
|
return events.withLock { events in
|
||||||
|
|
||||||
XCTAssertEqual(events, 1)
|
XCTAssertEqual(events, 1)
|
||||||
XCTAssertEqual(
|
XCTAssertEqual(
|
||||||
((note.userInfo as NSDictionary?) ?? [:]),
|
((note.userInfo as NSDictionary?) ?? [:]),
|
||||||
@@ -101,6 +110,7 @@ class ObjectObserverTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
return events == 1
|
return events == 1
|
||||||
}
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
let saveExpectation = self.expectation(description: "save")
|
let saveExpectation = self.expectation(description: "save")
|
||||||
stack.perform(
|
stack.perform(
|
||||||
@@ -131,6 +141,7 @@ class ObjectObserverTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
|
@MainActor
|
||||||
dynamic func test_ThatObjectObservers_CanReceiveDeleteNotifications() {
|
dynamic func test_ThatObjectObservers_CanReceiveDeleteNotifications() {
|
||||||
|
|
||||||
self.prepareStack { (stack) in
|
self.prepareStack { (stack) in
|
||||||
@@ -151,13 +162,16 @@ class ObjectObserverTests: BaseTestDataTestCase {
|
|||||||
XCTAssertEqual(monitor.object, object)
|
XCTAssertEqual(monitor.object, object)
|
||||||
XCTAssertFalse(monitor.isObjectDeleted)
|
XCTAssertFalse(monitor.isObjectDeleted)
|
||||||
|
|
||||||
var events = 0
|
let events: OSAllocatedUnfairLock<Int> = .init(initialState: 0)
|
||||||
|
|
||||||
_ = self.expectation(
|
_ = self.expectation(
|
||||||
forNotification: NSNotification.Name(rawValue: "objectMonitor:didDeleteObject:"),
|
forNotification: NSNotification.Name(rawValue: "objectMonitor:didDeleteObject:"),
|
||||||
object: observer,
|
object: observer,
|
||||||
handler: { (note) -> Bool in
|
handler: { (note) -> Bool in
|
||||||
|
|
||||||
|
nonisolated(unsafe) let note = note
|
||||||
|
return events.withLock { events in
|
||||||
|
|
||||||
XCTAssertEqual(events, 0)
|
XCTAssertEqual(events, 0)
|
||||||
XCTAssertEqual(
|
XCTAssertEqual(
|
||||||
((note.userInfo as NSDictionary?) ?? [:]),
|
((note.userInfo as NSDictionary?) ?? [:]),
|
||||||
@@ -169,6 +183,7 @@ class ObjectObserverTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
return events == 0
|
return events == 0
|
||||||
}
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
let saveExpectation = self.expectation(description: "save")
|
let saveExpectation = self.expectation(description: "save")
|
||||||
stack.perform(
|
stack.perform(
|
||||||
@@ -202,7 +217,7 @@ class ObjectObserverTests: BaseTestDataTestCase {
|
|||||||
|
|
||||||
// MARK: TestObjectObserver
|
// MARK: TestObjectObserver
|
||||||
|
|
||||||
class TestObjectObserver: ObjectObserver {
|
final class TestObjectObserver: ObjectObserver {
|
||||||
|
|
||||||
typealias ObjectEntityType = TestEntity1
|
typealias ObjectEntityType = TestEntity1
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import CoreStore
|
|||||||
class ObjectPublisherTests: BaseTestDataTestCase {
|
class ObjectPublisherTests: BaseTestDataTestCase {
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
|
@MainActor
|
||||||
dynamic func test_ThatObjectPublishers_CanReceiveUpdateNotifications() {
|
dynamic func test_ThatObjectPublishers_CanReceiveUpdateNotifications() {
|
||||||
|
|
||||||
self.prepareStack { (stack) in
|
self.prepareStack { (stack) in
|
||||||
@@ -93,6 +94,7 @@ class ObjectPublisherTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
|
@MainActor
|
||||||
dynamic func test_ThatObjectPublishers_CanReceiveDeleteNotifications() {
|
dynamic func test_ThatObjectPublishers_CanReceiveDeleteNotifications() {
|
||||||
|
|
||||||
self.prepareStack { (stack) in
|
self.prepareStack { (stack) in
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import CoreStore
|
|||||||
class SetupTests: BaseTestDataTestCase {
|
class SetupTests: BaseTestDataTestCase {
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
|
@MainActor
|
||||||
dynamic func test_ThatDataStacks_ConfigureCorrectly() {
|
dynamic func test_ThatDataStacks_ConfigureCorrectly() {
|
||||||
|
|
||||||
do {
|
do {
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ extension Modern {
|
|||||||
|
|
||||||
// MARK: - TransactionSource
|
// MARK: - TransactionSource
|
||||||
|
|
||||||
enum TransactionSource {
|
enum TransactionSource: Sendable {
|
||||||
|
|
||||||
case add
|
case add
|
||||||
case delete
|
case delete
|
||||||
|
|||||||
+1
-1
@@ -29,7 +29,7 @@ import PackageDescription
|
|||||||
let package = Package(
|
let package = Package(
|
||||||
name: "CoreStore",
|
name: "CoreStore",
|
||||||
platforms: [
|
platforms: [
|
||||||
.macOS(.v13), .iOS(.v16), .tvOS(.v16), .watchOS(.v9)
|
.macOS(.v14), .iOS(.v17), .tvOS(.v17), .watchOS(.v10)
|
||||||
],
|
],
|
||||||
products: [
|
products: [
|
||||||
.library(name: "CoreStore", targets: ["CoreStore"])
|
.library(name: "CoreStore", targets: ["CoreStore"])
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
|
|||||||
internal init(
|
internal init(
|
||||||
mainContext: NSManagedObjectContext,
|
mainContext: NSManagedObjectContext,
|
||||||
queue: DispatchQueue,
|
queue: DispatchQueue,
|
||||||
sourceIdentifier: Any?
|
sourceIdentifier: (any Sendable)?
|
||||||
) {
|
) {
|
||||||
|
|
||||||
super.init(
|
super.init(
|
||||||
@@ -188,7 +188,7 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal func autoCommit(
|
internal func autoCommit(
|
||||||
_ completion: @escaping (
|
_ completion: @escaping @MainActor (
|
||||||
_ hasChanges: Bool,
|
_ hasChanges: Bool,
|
||||||
_ error: CoreStoreError?
|
_ error: CoreStoreError?
|
||||||
) -> Void
|
) -> Void
|
||||||
@@ -197,12 +197,14 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
|
|||||||
self.isCommitted = true
|
self.isCommitted = true
|
||||||
let group = DispatchGroup()
|
let group = DispatchGroup()
|
||||||
group.enter()
|
group.enter()
|
||||||
|
|
||||||
|
nonisolated(unsafe) let transaction = self
|
||||||
self.context.saveAsynchronously(
|
self.context.saveAsynchronously(
|
||||||
sourceIdentifier: self.sourceIdentifier,
|
sourceIdentifier: self.sourceIdentifier,
|
||||||
completion: { (hasChanges, error) -> Void in
|
completion: { (hasChanges, error) -> Void in
|
||||||
|
|
||||||
completion(hasChanges, error)
|
completion(hasChanges, error)
|
||||||
self.result = (hasChanges, error)
|
transaction.result = (hasChanges, error)
|
||||||
group.leave()
|
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.
|
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.
|
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,
|
queue: DispatchQueue,
|
||||||
supportsUndo: Bool,
|
supportsUndo: Bool,
|
||||||
bypassesQueueing: Bool,
|
bypassesQueueing: Bool,
|
||||||
sourceIdentifier: Any?
|
sourceIdentifier: (any Sendable)?
|
||||||
) {
|
) {
|
||||||
|
|
||||||
let context = mainContext.temporaryContextInTransactionWithConcurrencyType(
|
let context = mainContext.temporaryContextInTransactionWithConcurrencyType(
|
||||||
@@ -493,9 +493,12 @@ public /*abstract*/ class BaseDataTransaction {
|
|||||||
}
|
}
|
||||||
else if context.undoManager == nil {
|
else if context.undoManager == nil {
|
||||||
|
|
||||||
|
Internals.mainActorImmediate {
|
||||||
|
|
||||||
context.undoManager = UndoManager()
|
context.undoManager = UndoManager()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal func isRunningInAllowedQueue() -> Bool {
|
internal func isRunningInAllowedQueue() -> Bool {
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
// MARK: - CoreStoreDefaults
|
// MARK: - CoreStoreDefaults
|
||||||
@@ -36,7 +37,29 @@ public enum CoreStoreDefaults {
|
|||||||
/**
|
/**
|
||||||
The `CoreStoreLogger` instance to be used. The default logger is an instance of a `DefaultLogger`.
|
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.
|
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 {
|
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 {
|
set {
|
||||||
|
|
||||||
self.defaultStackBarrierQueue.async(flags: .barrier) {
|
self.defaultStackInstance.withLock {
|
||||||
|
|
||||||
self.defaultStackInstance = newValue
|
$0 = newValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -69,7 +94,6 @@ public enum CoreStoreDefaults {
|
|||||||
|
|
||||||
// MARK: Private
|
// MARK: Private
|
||||||
|
|
||||||
private static let defaultStackBarrierQueue = DispatchQueue.concurrent("com.coreStore.defaultStackBarrierQueue", qos: .userInteractive)
|
private static let defaultStackInstance: OSAllocatedUnfairLock<DataStack?> = .init(initialState: nil)
|
||||||
|
private static let loggerInstance: OSAllocatedUnfairLock<(any CoreStoreLogger)?> = .init(initialState: nil)
|
||||||
private static var defaultStackInstance: DataStack?
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import CoreData
|
@preconcurrency import CoreData
|
||||||
|
|
||||||
|
|
||||||
// MARK: - CoreStoreError
|
// MARK: - CoreStoreError
|
||||||
@@ -32,7 +32,7 @@ import CoreData
|
|||||||
/**
|
/**
|
||||||
All errors thrown from CoreStore are expressed in `CoreStoreError` enum values.
|
All errors thrown from CoreStore are expressed in `CoreStoreError` enum values.
|
||||||
*/
|
*/
|
||||||
public enum CoreStoreError: Error, CustomNSError, Hashable {
|
public enum CoreStoreError: Error, CustomNSError, Hashable, @unchecked Sendable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
A failure occured because of an unknown error.
|
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.
|
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.
|
Handles log messages sent by the `CoreStore` framework.
|
||||||
|
|||||||
@@ -29,7 +29,8 @@ import Foundation
|
|||||||
|
|
||||||
// MARK: - CoreStoreManagedObject
|
// MARK: - CoreStoreManagedObject
|
||||||
|
|
||||||
@objc internal class CoreStoreManagedObject: NSManagedObject {
|
@objc
|
||||||
|
internal class CoreStoreManagedObject: NSManagedObject {
|
||||||
|
|
||||||
internal typealias CustomGetter = @convention(block) (_ rawObject: Any) -> Any?
|
internal typealias CustomGetter = @convention(block) (_ rawObject: Any) -> Any?
|
||||||
internal typealias CustomSetter = @convention(block) (_ rawObject: Any, _ newValue: Any?) -> Void
|
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)"
|
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
|
// workaround for <rdar://problem/31640524> Erroneous (?) error when using bridging in the Foundation overlay
|
||||||
@nonobjc
|
@nonobjc
|
||||||
static var swizzler: Any? = Internals.with {
|
static let swizzler: Void? = Internals.with {
|
||||||
|
|
||||||
let bridgeClass: AnyClass = _CoreStoreObjectKeyValueObservation.self
|
let bridgeClass: AnyClass = _CoreStoreObjectKeyValueObservation.self
|
||||||
let rootObserveImpl = class_getInstanceMethod(
|
let rootObserveImpl = class_getInstanceMethod(
|
||||||
|
|||||||
@@ -266,6 +266,6 @@ fileprivate enum Static {
|
|||||||
|
|
||||||
// MARK: FilePrivate
|
// MARK: FilePrivate
|
||||||
|
|
||||||
fileprivate static var metaCache: [ObjectIdentifier: Any] = [:]
|
fileprivate static nonisolated(unsafe) var metaCache: [ObjectIdentifier: Any] = [:]
|
||||||
fileprivate static var propertiesCache: [ObjectIdentifier: [PropertyProtocol]] = [:]
|
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`.
|
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
|
// MARK: Public
|
||||||
|
|
||||||
@@ -124,7 +124,7 @@ extension DataStack.AsyncNamespace {
|
|||||||
bufferingPolicy: .unbounded,
|
bufferingPolicy: .unbounded,
|
||||||
{ continuation in
|
{ continuation in
|
||||||
|
|
||||||
var progress: Progress? = nil
|
nonisolated(unsafe) var progress: Progress? = nil
|
||||||
progress = self.base.addStorage(
|
progress = self.base.addStorage(
|
||||||
storage,
|
storage,
|
||||||
completion: { result in
|
completion: { result in
|
||||||
@@ -151,6 +151,8 @@ extension DataStack.AsyncNamespace {
|
|||||||
)
|
)
|
||||||
if let progress = progress {
|
if let progress = progress {
|
||||||
|
|
||||||
|
Internals.mainActorImmediate { @MainActor in
|
||||||
|
|
||||||
progress.setProgressHandler { progress in
|
progress.setProgressHandler { progress in
|
||||||
|
|
||||||
continuation.yield(
|
continuation.yield(
|
||||||
@@ -162,6 +164,7 @@ extension DataStack.AsyncNamespace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,7 +188,7 @@ extension DataStack.AsyncNamespace {
|
|||||||
|
|
||||||
return try await Internals.withCheckedThrowingContinuation { continuation in
|
return try await Internals.withCheckedThrowingContinuation { continuation in
|
||||||
|
|
||||||
self.base.perform(
|
return self.base.perform(
|
||||||
asynchronous: { (transaction) -> O? in
|
asynchronous: { (transaction) -> O? in
|
||||||
|
|
||||||
return try transaction.importObject(
|
return try transaction.importObject(
|
||||||
@@ -196,10 +199,13 @@ extension DataStack.AsyncNamespace {
|
|||||||
success: {
|
success: {
|
||||||
|
|
||||||
continuation.resume(
|
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
|
source: O.ImportSource
|
||||||
) async throws(any Swift.Error) -> O? {
|
) async throws(any Swift.Error) -> O? {
|
||||||
|
|
||||||
|
nonisolated(unsafe) let object = object
|
||||||
return try await Internals.withCheckedThrowingContinuation { continuation in
|
return try await Internals.withCheckedThrowingContinuation { continuation in
|
||||||
|
|
||||||
self.base.perform(
|
self.base.perform(
|
||||||
@@ -240,10 +247,13 @@ extension DataStack.AsyncNamespace {
|
|||||||
success: {
|
success: {
|
||||||
|
|
||||||
continuation.resume(
|
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: {
|
success: {
|
||||||
|
|
||||||
continuation.resume(
|
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`.
|
- returns: The imported objects correctly associated for the `DataStack`.
|
||||||
- throws: A `CoreStoreError` value indicating the failure reason
|
- 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>,
|
_ into: Into<O>,
|
||||||
sourceArray: S,
|
sourceArray: S,
|
||||||
preProcess: @escaping @Sendable (
|
preProcess: @escaping @Sendable (
|
||||||
@@ -329,10 +342,13 @@ extension DataStack.AsyncNamespace {
|
|||||||
success: {
|
success: {
|
||||||
|
|
||||||
continuation.resume(
|
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.
|
- returns: The value returned from the `task` closure.
|
||||||
- throws: A `CoreStoreError` value indicating the failure reason
|
- 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
|
_ asynchronous: @escaping @Sendable (AsynchronousDataTransaction) throws(any Swift.Error) -> Output
|
||||||
) async throws(any Swift.Error) -> Output {
|
) async throws(any Swift.Error) -> Output {
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,8 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import CoreData
|
@preconcurrency import CoreData
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
// MARK: - DataStack
|
// MARK: - DataStack
|
||||||
@@ -49,7 +50,7 @@ extension DataStack {
|
|||||||
*/
|
*/
|
||||||
public func addStorage<T>(
|
public func addStorage<T>(
|
||||||
_ storage: T,
|
_ storage: T,
|
||||||
completion: @escaping (SetupResult<T>) -> Void
|
completion: @escaping @MainActor (SetupResult<T>) -> Void
|
||||||
) {
|
) {
|
||||||
|
|
||||||
self.coordinator.performAsynchronously {
|
self.coordinator.performAsynchronously {
|
||||||
@@ -110,7 +111,7 @@ extension DataStack {
|
|||||||
*/
|
*/
|
||||||
public func addStorage<T: LocalStorage>(
|
public func addStorage<T: LocalStorage>(
|
||||||
_ storage: T,
|
_ storage: T,
|
||||||
completion: @escaping (SetupResult<T>) -> Void
|
completion: @escaping @MainActor (SetupResult<T>) -> Void
|
||||||
) -> Progress? {
|
) -> Progress? {
|
||||||
|
|
||||||
let fileURL = storage.fileURL
|
let fileURL = storage.fileURL
|
||||||
@@ -162,7 +163,7 @@ extension DataStack {
|
|||||||
attributes: nil
|
attributes: nil
|
||||||
)
|
)
|
||||||
|
|
||||||
let metadata = try NSPersistentStoreCoordinator.metadataForPersistentStore(
|
nonisolated(unsafe) let metadata = try NSPersistentStoreCoordinator.metadataForPersistentStore(
|
||||||
ofType: type(of: storage).storeType,
|
ofType: type(of: storage).storeType,
|
||||||
at: fileURL as URL,
|
at: fileURL as URL,
|
||||||
options: storage.storeOptions
|
options: storage.storeOptions
|
||||||
@@ -192,12 +193,18 @@ extension DataStack {
|
|||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
|
||||||
completion(.failure(CoreStoreError(error)))
|
completion(.failure(CoreStoreError(error)))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
|
||||||
completion(.failure(CoreStoreError(error)))
|
completion(.failure(CoreStoreError(error)))
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,9 +219,12 @@ extension DataStack {
|
|||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
|
||||||
completion(.failure(CoreStoreError(error)))
|
completion(.failure(CoreStoreError(error)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
catch let error as NSError
|
catch let error as NSError
|
||||||
@@ -264,7 +274,7 @@ extension DataStack {
|
|||||||
*/
|
*/
|
||||||
public func upgradeStorageIfNeeded<T: LocalStorage>(
|
public func upgradeStorageIfNeeded<T: LocalStorage>(
|
||||||
_ storage: T,
|
_ storage: T,
|
||||||
completion: @escaping (MigrationResult) -> Void
|
completion: @escaping @MainActor (MigrationResult) -> Void
|
||||||
) throws(CoreStoreError) -> Progress? {
|
) throws(CoreStoreError) -> Progress? {
|
||||||
|
|
||||||
return try self.coordinator.performSynchronously {
|
return try self.coordinator.performSynchronously {
|
||||||
@@ -376,7 +386,7 @@ extension DataStack {
|
|||||||
private func upgradeStorageIfNeeded<T: LocalStorage>(
|
private func upgradeStorageIfNeeded<T: LocalStorage>(
|
||||||
_ storage: T,
|
_ storage: T,
|
||||||
metadata: [String: Any],
|
metadata: [String: Any],
|
||||||
completion: @escaping (MigrationResult) -> Void
|
completion: @escaping @MainActor (MigrationResult) -> Void
|
||||||
) -> Progress? {
|
) -> Progress? {
|
||||||
|
|
||||||
guard let migrationSteps = self.computeMigrationFromStorage(storage, metadata: metadata) else {
|
guard let migrationSteps = self.computeMigrationFromStorage(storage, metadata: metadata) else {
|
||||||
@@ -423,9 +433,13 @@ extension DataStack {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let migrationTypes = migrationSteps.map { $0.migrationType }
|
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 operations = [Operation]()
|
||||||
var cancelled = false
|
|
||||||
|
|
||||||
let progress = Progress(parent: nil, userInfo: nil)
|
let progress = Progress(parent: nil, userInfo: nil)
|
||||||
progress.totalUnitCount = numberOfMigrations
|
progress.totalUnitCount = numberOfMigrations
|
||||||
@@ -440,12 +454,15 @@ extension DataStack {
|
|||||||
operations.append(
|
operations.append(
|
||||||
BlockOperation { [weak self] in
|
BlockOperation { [weak self] in
|
||||||
|
|
||||||
guard let self = self, !cancelled else {
|
guard
|
||||||
|
let self = self,
|
||||||
|
!migrationState.withLock({ $0.cancelled })
|
||||||
|
else {
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
autoreleasepool {
|
Internals.autoreleasepool {
|
||||||
|
|
||||||
do {
|
do {
|
||||||
|
|
||||||
@@ -465,8 +482,14 @@ extension DataStack {
|
|||||||
migrationError,
|
migrationError,
|
||||||
"Failed to migrate version model \"\(migrationType.sourceVersion)\" to version \"\(migrationType.destinationVersion)\"."
|
"Failed to migrate version model \"\(migrationType.sourceVersion)\" to version \"\(migrationType.destinationVersion)\"."
|
||||||
)
|
)
|
||||||
migrationResult = .failure(migrationError)
|
migrationState.withLock { state in
|
||||||
cancelled = true
|
|
||||||
|
if !state.cancelled {
|
||||||
|
|
||||||
|
state.migrationResult = .failure(migrationError)
|
||||||
|
state.cancelled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -488,7 +511,10 @@ extension DataStack {
|
|||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
|
|
||||||
progress.setProgressHandler(nil)
|
progress.setProgressHandler(nil)
|
||||||
completion(migrationResult ?? .success(migrationTypes))
|
completion(
|
||||||
|
migrationState.withLock { $0.migrationResult }
|
||||||
|
?? .success(migrationTypes)
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -594,23 +620,29 @@ extension DataStack {
|
|||||||
let estimatedTime: TimeInterval = 60 * 3 // 3 mins
|
let estimatedTime: TimeInterval = 60 * 3 // 3 mins
|
||||||
let interval: TimeInterval = 1
|
let interval: TimeInterval = 1
|
||||||
let fakeTotalUnitCount: Float = 0.9 * Float(progress.totalUnitCount)
|
let fakeTotalUnitCount: Float = 0.9 * Float(progress.totalUnitCount)
|
||||||
var fakeProgress: Float = 0
|
let fakeProgress: OSAllocatedUnfairLock<Float> = .init(initialState: 0)
|
||||||
|
|
||||||
var recursiveCheck: () -> Void = {}
|
@Sendable
|
||||||
recursiveCheck = { [weak timerQueue] in
|
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)
|
progress.completedUnitCount = Int64(fakeTotalUnitCount * $0)
|
||||||
fakeProgress += Float(interval / estimatedTime)
|
$0 += Float(interval / estimatedTime)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if enqueueNext {
|
||||||
|
|
||||||
timerQueue.asyncAfter(
|
timerQueue.asyncAfter(
|
||||||
deadline: .now() + interval,
|
deadline: .now() + interval,
|
||||||
execute: recursiveCheck
|
execute: recursiveCheck
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
timerQueue.async(execute: recursiveCheck)
|
timerQueue.async(execute: recursiveCheck)
|
||||||
|
|
||||||
_ = try withExtendedLifetime(NSPersistentStoreCoordinator(managedObjectModel: destinationModel)) { (coordinator: NSPersistentStoreCoordinator) in
|
_ = try withExtendedLifetime(NSPersistentStoreCoordinator(managedObjectModel: destinationModel)) { (coordinator: NSPersistentStoreCoordinator) in
|
||||||
@@ -626,7 +658,7 @@ extension DataStack {
|
|||||||
}
|
}
|
||||||
timerQueue.sync {
|
timerQueue.sync {
|
||||||
|
|
||||||
fakeProgress = 1
|
fakeProgress.withLock({ $0 = 1.0 })
|
||||||
}
|
}
|
||||||
_ = try? storage.cs_finalizeStorageAndWait(soureModelHint: destinationModel)
|
_ = try? storage.cs_finalizeStorageAndWait(soureModelHint: destinationModel)
|
||||||
progress.completedUnitCount = progress.totalUnitCount
|
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.
|
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||||
*/
|
*/
|
||||||
public func monitorList<O>(
|
public func monitorList<O>(
|
||||||
createAsynchronously: @escaping (ListMonitor<O>) -> Void,
|
createAsynchronously: @escaping @Sendable (ListMonitor<O>) -> Void,
|
||||||
_ from: From<O>,
|
_ from: From<O>,
|
||||||
_ fetchClauses: FetchClause...
|
_ 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.
|
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||||
*/
|
*/
|
||||||
public func monitorList<O>(
|
public func monitorList<O>(
|
||||||
createAsynchronously: @escaping (ListMonitor<O>) -> Void,
|
createAsynchronously: @escaping @Sendable (ListMonitor<O>) -> Void,
|
||||||
_ from: From<O>,
|
_ from: From<O>,
|
||||||
_ fetchClauses: [FetchClause]
|
_ fetchClauses: [FetchClause]
|
||||||
) {
|
) {
|
||||||
@@ -188,7 +188,7 @@ extension DataStack {
|
|||||||
- parameter clauseChain: a `FetchChainableBuilderType` built from a chain of clauses
|
- parameter clauseChain: a `FetchChainableBuilderType` built from a chain of clauses
|
||||||
*/
|
*/
|
||||||
public func monitorList<B: FetchChainableBuilderType>(
|
public func monitorList<B: FetchChainableBuilderType>(
|
||||||
createAsynchronously: @escaping (ListMonitor<B.ObjectType>) -> Void,
|
createAsynchronously: @escaping @Sendable (ListMonitor<B.ObjectType>) -> Void,
|
||||||
_ clauseChain: B
|
_ 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.
|
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||||
*/
|
*/
|
||||||
public func monitorSectionedList<O>(
|
public func monitorSectionedList<O>(
|
||||||
createAsynchronously: @escaping (ListMonitor<O>) -> Void,
|
createAsynchronously: @escaping @Sendable (ListMonitor<O>) -> Void,
|
||||||
_ from: From<O>,
|
_ from: From<O>,
|
||||||
_ sectionBy: SectionBy<O>,
|
_ sectionBy: SectionBy<O>,
|
||||||
_ fetchClauses: FetchClause...
|
_ 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.
|
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||||
*/
|
*/
|
||||||
public func monitorSectionedList<O>(
|
public func monitorSectionedList<O>(
|
||||||
createAsynchronously: @escaping (ListMonitor<O>) -> Void,
|
createAsynchronously: @escaping @Sendable (ListMonitor<O>) -> Void,
|
||||||
_ from: From<O>,
|
_ from: From<O>,
|
||||||
_ sectionBy: SectionBy<O>,
|
_ sectionBy: SectionBy<O>,
|
||||||
_ fetchClauses: [FetchClause]
|
_ fetchClauses: [FetchClause]
|
||||||
@@ -356,7 +356,7 @@ extension DataStack {
|
|||||||
- parameter clauseChain: a `SectionMonitorBuilderType` built from a chain of clauses
|
- parameter clauseChain: a `SectionMonitorBuilderType` built from a chain of clauses
|
||||||
*/
|
*/
|
||||||
public func monitorSectionedList<B: SectionMonitorBuilderType>(
|
public func monitorSectionedList<B: SectionMonitorBuilderType>(
|
||||||
createAsynchronously: @escaping (ListMonitor<B.ObjectType>) -> Void,
|
createAsynchronously: @escaping @Sendable (ListMonitor<B.ObjectType>) -> Void,
|
||||||
_ clauseChain: B
|
_ 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`.
|
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
|
// MARK: Public
|
||||||
|
|
||||||
@@ -99,6 +99,7 @@ extension DataStack.ReactiveNamespace {
|
|||||||
|
|
||||||
return .init { (promise) in
|
return .init { (promise) in
|
||||||
|
|
||||||
|
nonisolated(unsafe) let promise = promise
|
||||||
self.base.addStorage(
|
self.base.addStorage(
|
||||||
storage,
|
storage,
|
||||||
completion: { (result) in
|
completion: { (result) in
|
||||||
@@ -179,6 +180,7 @@ extension DataStack.ReactiveNamespace {
|
|||||||
|
|
||||||
return .init { (promise) in
|
return .init { (promise) in
|
||||||
|
|
||||||
|
nonisolated(unsafe) let promise = promise
|
||||||
self.base.perform(
|
self.base.perform(
|
||||||
asynchronous: { (transaction) -> O? in
|
asynchronous: { (transaction) -> O? in
|
||||||
|
|
||||||
@@ -222,8 +224,10 @@ extension DataStack.ReactiveNamespace {
|
|||||||
source: O.ImportSource
|
source: O.ImportSource
|
||||||
) -> Future<O?, CoreStoreError> {
|
) -> Future<O?, CoreStoreError> {
|
||||||
|
|
||||||
|
nonisolated(unsafe) let object = object
|
||||||
return .init { (promise) in
|
return .init { (promise) in
|
||||||
|
|
||||||
|
nonisolated(unsafe) let promise = promise
|
||||||
self.base.perform(
|
self.base.perform(
|
||||||
asynchronous: { (transaction) -> O? in
|
asynchronous: { (transaction) -> O? in
|
||||||
|
|
||||||
@@ -273,6 +277,7 @@ extension DataStack.ReactiveNamespace {
|
|||||||
|
|
||||||
return .init { (promise) in
|
return .init { (promise) in
|
||||||
|
|
||||||
|
nonisolated(unsafe) let promise = promise
|
||||||
self.base.perform(
|
self.base.perform(
|
||||||
asynchronous: { (transaction) -> O? in
|
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.
|
- 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`.
|
- 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>,
|
_ into: Into<O>,
|
||||||
sourceArray: S,
|
sourceArray: S,
|
||||||
preProcess: @escaping (
|
preProcess: @escaping @Sendable (
|
||||||
_ mapping: [O.UniqueIDType: O.ImportSource]
|
_ mapping: [O.UniqueIDType: O.ImportSource]
|
||||||
) throws(any Swift.Error) -> [O.UniqueIDType: O.ImportSource] = { $0 }
|
) throws(any Swift.Error) -> [O.UniqueIDType: O.ImportSource] = { $0 }
|
||||||
) -> Future<[O], CoreStoreError> where S.Iterator.Element == O.ImportSource {
|
) -> Future<[O], CoreStoreError> where S.Iterator.Element == O.ImportSource {
|
||||||
|
|
||||||
return .init { (promise) in
|
return .init { (promise) in
|
||||||
|
|
||||||
|
nonisolated(unsafe) let promise = promise
|
||||||
self.base.perform(
|
self.base.perform(
|
||||||
asynchronous: { (transaction) -> [O] in
|
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`.
|
- 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.
|
- returns: A `Future` whose event value be the value returned from the `task` closure.
|
||||||
*/
|
*/
|
||||||
public func perform<Output>(
|
public func perform<Output: Sendable>(
|
||||||
_ asynchronous: @escaping (
|
_ asynchronous: @escaping @Sendable (
|
||||||
_ transaction: AsynchronousDataTransaction
|
_ transaction: AsynchronousDataTransaction
|
||||||
) throws(any Swift.Error) -> Output
|
) throws(any Swift.Error) -> Output
|
||||||
) -> Future<Output, CoreStoreError> {
|
) -> Future<Output, CoreStoreError> {
|
||||||
|
|
||||||
return .init { (promise) in
|
return .init { (promise) in
|
||||||
|
|
||||||
|
nonisolated(unsafe) let promise = promise
|
||||||
self.base.perform(
|
self.base.perform(
|
||||||
asynchronous: asynchronous,
|
asynchronous: asynchronous,
|
||||||
success: { promise(.success($0)) },
|
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 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)`.
|
- 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>(
|
public func perform<T: Sendable>(
|
||||||
asynchronous task: @escaping (
|
asynchronous task: @escaping @Sendable (
|
||||||
_ transaction: AsynchronousDataTransaction
|
_ transaction: AsynchronousDataTransaction
|
||||||
) throws(any Swift.Error) -> T,
|
) throws(any Swift.Error) -> T,
|
||||||
sourceIdentifier: Any? = nil,
|
sourceIdentifier: (any Sendable)? = nil,
|
||||||
completion: @escaping (AsynchronousDataTransaction.Result<T>) -> Void
|
completion: @escaping @Sendable (AsynchronousDataTransaction.Result<T>) -> Void
|
||||||
) {
|
) {
|
||||||
|
|
||||||
self.perform(
|
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)`.
|
- 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>(
|
public func perform<T>(
|
||||||
asynchronous task: @escaping (
|
asynchronous task: @escaping @Sendable (
|
||||||
_ transaction: AsynchronousDataTransaction
|
_ transaction: AsynchronousDataTransaction
|
||||||
) throws(any Swift.Error) -> T,
|
) throws(any Swift.Error) -> T,
|
||||||
sourceIdentifier: Any? = nil,
|
sourceIdentifier: (any Sendable)? = nil,
|
||||||
success: @escaping (T) -> Void,
|
success: @escaping @Sendable (sending T) -> Void,
|
||||||
failure: @escaping (CoreStoreError) -> Void
|
failure: @escaping @Sendable (CoreStoreError) -> Void
|
||||||
) {
|
) {
|
||||||
|
|
||||||
let transaction = AsynchronousDataTransaction(
|
nonisolated(unsafe) let transaction = AsynchronousDataTransaction(
|
||||||
mainContext: self.rootSavingContext,
|
mainContext: self.rootSavingContext,
|
||||||
queue: self.childTransactionQueue,
|
queue: self.childTransactionQueue,
|
||||||
sourceIdentifier: sourceIdentifier
|
sourceIdentifier: sourceIdentifier
|
||||||
)
|
)
|
||||||
transaction.transactionQueue.cs_async {
|
transaction.transactionQueue.cs_async {
|
||||||
|
|
||||||
let userInfo: T
|
nonisolated(unsafe) let userInfo: T
|
||||||
do {
|
do {
|
||||||
|
|
||||||
userInfo = try task(transaction)
|
userInfo = try task(transaction)
|
||||||
@@ -125,7 +125,7 @@ extension DataStack {
|
|||||||
_ transaction: SynchronousDataTransaction
|
_ transaction: SynchronousDataTransaction
|
||||||
) throws(any Swift.Error) -> T,
|
) throws(any Swift.Error) -> T,
|
||||||
waitForAllObservers: Bool = true,
|
waitForAllObservers: Bool = true,
|
||||||
sourceIdentifier: Any? = nil
|
sourceIdentifier: (any Sendable)? = nil
|
||||||
) throws(CoreStoreError) -> T {
|
) throws(CoreStoreError) -> T {
|
||||||
|
|
||||||
let transaction = SynchronousDataTransaction(
|
let transaction = SynchronousDataTransaction(
|
||||||
@@ -172,7 +172,7 @@ extension DataStack {
|
|||||||
*/
|
*/
|
||||||
public func beginUnsafe(
|
public func beginUnsafe(
|
||||||
supportsUndo: Bool = false,
|
supportsUndo: Bool = false,
|
||||||
sourceIdentifier: Any? = nil
|
sourceIdentifier: (any Sendable)? = nil
|
||||||
) -> UnsafeDataTransaction {
|
) -> UnsafeDataTransaction {
|
||||||
|
|
||||||
return UnsafeDataTransaction(
|
return UnsafeDataTransaction(
|
||||||
|
|||||||
@@ -90,7 +90,8 @@ extension DataStack {
|
|||||||
|
|
||||||
// MARK: - AddStorageSubscription
|
// 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
|
// MARK: FilePrivate
|
||||||
|
|
||||||
@@ -114,7 +115,7 @@ extension DataStack {
|
|||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var progress: Progress? = nil
|
nonisolated(unsafe) var progress: Progress? = nil
|
||||||
progress = self.dataStack.addStorage(
|
progress = self.dataStack.addStorage(
|
||||||
self.storage,
|
self.storage,
|
||||||
completion: { [weak self] result in
|
completion: { [weak self] result in
|
||||||
@@ -150,7 +151,9 @@ extension DataStack {
|
|||||||
)
|
)
|
||||||
if let progress = progress {
|
if let progress = progress {
|
||||||
|
|
||||||
progress.setProgressHandler { [weak self] progress in
|
Internals.mainActorImmediate { @MainActor [weak self] in
|
||||||
|
|
||||||
|
progress.setProgressHandler { progress in
|
||||||
|
|
||||||
guard
|
guard
|
||||||
let self = self,
|
let self = self,
|
||||||
@@ -168,6 +171,7 @@ extension DataStack {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// MARK: Cancellable
|
// 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`.
|
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.
|
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
|
- parameter completion: the closure to execute after all persistent stores are removed
|
||||||
*/
|
*/
|
||||||
public func unsafeRemoveAllPersistentStores(
|
public func unsafeRemoveAllPersistentStores(
|
||||||
completion: @escaping () -> Void = {}
|
completion: @escaping @MainActor () -> Void = {}
|
||||||
) {
|
) {
|
||||||
|
|
||||||
let coordinator = self.coordinator
|
let coordinator = self.coordinator
|
||||||
@@ -459,7 +459,7 @@ public final class DataStack: Equatable {
|
|||||||
|
|
||||||
// MARK: Internal
|
// MARK: Internal
|
||||||
|
|
||||||
internal static var defaultConfigurationName = "PF_DEFAULT_CONFIGURATION_NAME"
|
internal static let defaultConfigurationName = "PF_DEFAULT_CONFIGURATION_NAME"
|
||||||
|
|
||||||
internal let coordinator: NSPersistentStoreCoordinator
|
internal let coordinator: NSPersistentStoreCoordinator
|
||||||
internal let rootSavingContext: NSManagedObjectContext
|
internal let rootSavingContext: NSManagedObjectContext
|
||||||
|
|||||||
@@ -95,7 +95,10 @@ extension DiffableDataSource {
|
|||||||
- parameter dataStack: the `DataStack` instance that the dataSource will fetch objects from
|
- 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
|
- 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.target = target
|
||||||
self.dataStack = dataStack
|
self.dataStack = dataStack
|
||||||
@@ -106,7 +109,10 @@ extension DiffableDataSource {
|
|||||||
Clears the target.
|
Clears the target.
|
||||||
- parameter animatingDifferences: if `true`, animations may be applied accordingly. Defaults to `true`.
|
- 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(
|
self.dispatcher.purge(
|
||||||
target: self.target,
|
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 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`.
|
- 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
|
let diffableSnapshot = snapshot.diffableSnapshot
|
||||||
self.dispatcher.apply(
|
self.dispatcher.apply(
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ extension DiffableDataSource {
|
|||||||
- parameter itemProvider: a closure that configures and returns the `NSCollectionViewItem` for the object
|
- parameter itemProvider: a closure that configures and returns the `NSCollectionViewItem` for the object
|
||||||
*/
|
*/
|
||||||
@nonobjc
|
@nonobjc
|
||||||
|
@MainActor
|
||||||
public init(
|
public init(
|
||||||
collectionView: NSCollectionView,
|
collectionView: NSCollectionView,
|
||||||
dataStack: DataStack,
|
dataStack: DataStack,
|
||||||
@@ -102,6 +103,7 @@ extension DiffableDataSource {
|
|||||||
// MARK: - NSCollectionViewDataSource
|
// MARK: - NSCollectionViewDataSource
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
|
@MainActor
|
||||||
public dynamic func numberOfSections(
|
public dynamic func numberOfSections(
|
||||||
in collectionView: NSCollectionView
|
in collectionView: NSCollectionView
|
||||||
) -> Int {
|
) -> Int {
|
||||||
@@ -110,6 +112,7 @@ extension DiffableDataSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
|
@MainActor
|
||||||
public dynamic func collectionView(
|
public dynamic func collectionView(
|
||||||
_ collectionView: NSCollectionView,
|
_ collectionView: NSCollectionView,
|
||||||
numberOfItemsInSection section: Int
|
numberOfItemsInSection section: Int
|
||||||
@@ -119,6 +122,7 @@ extension DiffableDataSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
|
@MainActor
|
||||||
open dynamic func collectionView(
|
open dynamic func collectionView(
|
||||||
_ collectionView: NSCollectionView,
|
_ collectionView: NSCollectionView,
|
||||||
itemForRepresentedObjectAt indexPath: IndexPath
|
itemForRepresentedObjectAt indexPath: IndexPath
|
||||||
@@ -140,6 +144,7 @@ extension DiffableDataSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
|
@MainActor
|
||||||
open dynamic func collectionView(
|
open dynamic func collectionView(
|
||||||
_ collectionView: NSCollectionView,
|
_ collectionView: NSCollectionView,
|
||||||
viewForSupplementaryElementOfKind kind: NSCollectionView.SupplementaryElementKind,
|
viewForSupplementaryElementOfKind kind: NSCollectionView.SupplementaryElementKind,
|
||||||
@@ -181,59 +186,92 @@ extension DiffableDataSource {
|
|||||||
|
|
||||||
public var shouldSuspendBatchUpdates: Bool {
|
public var shouldSuspendBatchUpdates: Bool {
|
||||||
|
|
||||||
|
return MainActor.assumeIsolated {
|
||||||
|
|
||||||
return self.base?.window == nil
|
return self.base?.window == nil
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func deleteSections(at indices: IndexSet, animated: Bool) {
|
public func deleteSections(at indices: IndexSet, animated: Bool) {
|
||||||
|
|
||||||
|
MainActor.assumeIsolated {
|
||||||
|
|
||||||
self.base?.deleteSections(indices)
|
self.base?.deleteSections(indices)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func insertSections(at indices: IndexSet, animated: Bool) {
|
public func insertSections(at indices: IndexSet, animated: Bool) {
|
||||||
|
|
||||||
|
MainActor.assumeIsolated {
|
||||||
|
|
||||||
self.base?.insertSections(indices)
|
self.base?.insertSections(indices)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func reloadSections(at indices: IndexSet, animated: Bool) {
|
public func reloadSections(at indices: IndexSet, animated: Bool) {
|
||||||
|
|
||||||
|
MainActor.assumeIsolated {
|
||||||
|
|
||||||
self.base?.reloadSections(indices)
|
self.base?.reloadSections(indices)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func moveSection(at index: IndexSet.Element, to newIndex: IndexSet.Element, animated: Bool) {
|
public func moveSection(at index: IndexSet.Element, to newIndex: IndexSet.Element, animated: Bool) {
|
||||||
|
|
||||||
|
MainActor.assumeIsolated {
|
||||||
|
|
||||||
self.base?.moveSection(index, toSection: newIndex)
|
self.base?.moveSection(index, toSection: newIndex)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func deleteItems(at indexPaths: [IndexPath], animated: Bool) {
|
public func deleteItems(at indexPaths: [IndexPath], animated: Bool) {
|
||||||
|
|
||||||
|
MainActor.assumeIsolated {
|
||||||
|
|
||||||
self.base?.deleteItems(at: Set(indexPaths))
|
self.base?.deleteItems(at: Set(indexPaths))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func insertItems(at indexPaths: [IndexPath], animated: Bool) {
|
public func insertItems(at indexPaths: [IndexPath], animated: Bool) {
|
||||||
|
|
||||||
|
MainActor.assumeIsolated {
|
||||||
|
|
||||||
self.base?.insertItems(at: Set(indexPaths))
|
self.base?.insertItems(at: Set(indexPaths))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func reloadItems(at indexPaths: [IndexPath], animated: Bool) {
|
public func reloadItems(at indexPaths: [IndexPath], animated: Bool) {
|
||||||
|
|
||||||
|
MainActor.assumeIsolated {
|
||||||
|
|
||||||
self.base?.reloadItems(at: Set(indexPaths))
|
self.base?.reloadItems(at: Set(indexPaths))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func moveItem(at indexPath: IndexPath, to newIndexPath: IndexPath, animated: Bool) {
|
public func moveItem(at indexPath: IndexPath, to newIndexPath: IndexPath, animated: Bool) {
|
||||||
|
|
||||||
|
MainActor.assumeIsolated {
|
||||||
|
|
||||||
self.base?.moveItem(at: indexPath, to: newIndexPath)
|
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() })
|
self.base?.animator().performBatchUpdates(updates, completionHandler: { _ in completion() })
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func reloadData() {
|
public func reloadData() {
|
||||||
|
|
||||||
|
MainActor.assumeIsolated {
|
||||||
|
|
||||||
self.base?.reloadData()
|
self.base?.reloadData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -185,59 +185,92 @@ extension DiffableDataSource {
|
|||||||
|
|
||||||
public var shouldSuspendBatchUpdates: Bool {
|
public var shouldSuspendBatchUpdates: Bool {
|
||||||
|
|
||||||
|
return MainActor.assumeIsolated {
|
||||||
|
|
||||||
return self.base?.window == nil
|
return self.base?.window == nil
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func deleteSections(at indices: IndexSet, animated: Bool) {
|
public func deleteSections(at indices: IndexSet, animated: Bool) {
|
||||||
|
|
||||||
|
MainActor.assumeIsolated {
|
||||||
|
|
||||||
self.base?.deleteSections(indices)
|
self.base?.deleteSections(indices)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func insertSections(at indices: IndexSet, animated: Bool) {
|
public func insertSections(at indices: IndexSet, animated: Bool) {
|
||||||
|
|
||||||
|
MainActor.assumeIsolated {
|
||||||
|
|
||||||
self.base?.insertSections(indices)
|
self.base?.insertSections(indices)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func reloadSections(at indices: IndexSet, animated: Bool) {
|
public func reloadSections(at indices: IndexSet, animated: Bool) {
|
||||||
|
|
||||||
|
MainActor.assumeIsolated {
|
||||||
|
|
||||||
self.base?.reloadSections(indices)
|
self.base?.reloadSections(indices)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func moveSection(at index: IndexSet.Element, to newIndex: IndexSet.Element, animated: Bool) {
|
public func moveSection(at index: IndexSet.Element, to newIndex: IndexSet.Element, animated: Bool) {
|
||||||
|
|
||||||
|
MainActor.assumeIsolated {
|
||||||
|
|
||||||
self.base?.moveSection(index, toSection: newIndex)
|
self.base?.moveSection(index, toSection: newIndex)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func deleteItems(at indexPaths: [IndexPath], animated: Bool) {
|
public func deleteItems(at indexPaths: [IndexPath], animated: Bool) {
|
||||||
|
|
||||||
|
MainActor.assumeIsolated {
|
||||||
|
|
||||||
self.base?.deleteItems(at: indexPaths)
|
self.base?.deleteItems(at: indexPaths)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func insertItems(at indexPaths: [IndexPath], animated: Bool) {
|
public func insertItems(at indexPaths: [IndexPath], animated: Bool) {
|
||||||
|
|
||||||
|
Internals.mainActorImmediate {
|
||||||
|
|
||||||
self.base?.insertItems(at: indexPaths)
|
self.base?.insertItems(at: indexPaths)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func reloadItems(at indexPaths: [IndexPath], animated: Bool) {
|
public func reloadItems(at indexPaths: [IndexPath], animated: Bool) {
|
||||||
|
|
||||||
|
Internals.mainActorImmediate {
|
||||||
|
|
||||||
self.base?.reloadItems(at: indexPaths)
|
self.base?.reloadItems(at: indexPaths)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func moveItem(at indexPath: IndexPath, to newIndexPath: IndexPath, animated: Bool) {
|
public func moveItem(at indexPath: IndexPath, to newIndexPath: IndexPath, animated: Bool) {
|
||||||
|
|
||||||
|
Internals.mainActorImmediate {
|
||||||
|
|
||||||
self.base?.moveItem(at: indexPath, to: newIndexPath)
|
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() })
|
self.base?.performBatchUpdates(updates, completion: { _ in completion() })
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func reloadData() {
|
public func reloadData() {
|
||||||
|
|
||||||
|
Internals.mainActorImmediate {
|
||||||
|
|
||||||
self.base?.reloadData()
|
self.base?.reloadData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ extension DiffableDataSource {
|
|||||||
public init(
|
public init(
|
||||||
tableView: UITableView,
|
tableView: UITableView,
|
||||||
dataStack: DataStack,
|
dataStack: DataStack,
|
||||||
cellProvider: @escaping (UITableView, IndexPath, O) -> UITableViewCell?
|
cellProvider: @escaping @MainActor (UITableView, IndexPath, O) -> UITableViewCell?
|
||||||
) {
|
) {
|
||||||
|
|
||||||
self.cellProvider = cellProvider
|
self.cellProvider = cellProvider
|
||||||
@@ -97,6 +97,7 @@ extension DiffableDataSource {
|
|||||||
/**
|
/**
|
||||||
The target `UITableView`
|
The target `UITableView`
|
||||||
*/
|
*/
|
||||||
|
@MainActor
|
||||||
public var tableView: UITableView? {
|
public var tableView: UITableView? {
|
||||||
|
|
||||||
return self.target.base
|
return self.target.base
|
||||||
@@ -240,50 +241,79 @@ extension DiffableDataSource {
|
|||||||
|
|
||||||
public var shouldSuspendBatchUpdates: Bool {
|
public var shouldSuspendBatchUpdates: Bool {
|
||||||
|
|
||||||
|
return MainActor.assumeIsolated {
|
||||||
|
|
||||||
return self.base?.window == nil
|
return self.base?.window == nil
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func deleteSections(at indices: IndexSet, animated: Bool) {
|
public func deleteSections(at indices: IndexSet, animated: Bool) {
|
||||||
|
|
||||||
|
MainActor.assumeIsolated {
|
||||||
|
|
||||||
self.base?.deleteSections(indices, with: .automatic)
|
self.base?.deleteSections(indices, with: .automatic)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func insertSections(at indices: IndexSet, animated: Bool) {
|
public func insertSections(at indices: IndexSet, animated: Bool) {
|
||||||
|
|
||||||
|
MainActor.assumeIsolated {
|
||||||
|
|
||||||
self.base?.insertSections(indices, with: .automatic)
|
self.base?.insertSections(indices, with: .automatic)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func reloadSections(at indices: IndexSet, animated: Bool) {
|
public func reloadSections(at indices: IndexSet, animated: Bool) {
|
||||||
|
|
||||||
|
MainActor.assumeIsolated {
|
||||||
|
|
||||||
self.base?.reloadSections(indices, with: .automatic)
|
self.base?.reloadSections(indices, with: .automatic)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func moveSection(at index: IndexSet.Element, to newIndex: IndexSet.Element, animated: Bool) {
|
public func moveSection(at index: IndexSet.Element, to newIndex: IndexSet.Element, animated: Bool) {
|
||||||
|
|
||||||
|
MainActor.assumeIsolated {
|
||||||
|
|
||||||
self.base?.moveSection(index, toSection: newIndex)
|
self.base?.moveSection(index, toSection: newIndex)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func deleteItems(at indexPaths: [IndexPath], animated: Bool) {
|
public func deleteItems(at indexPaths: [IndexPath], animated: Bool) {
|
||||||
|
|
||||||
|
MainActor.assumeIsolated {
|
||||||
|
|
||||||
self.base?.deleteRows(at: indexPaths, with: .automatic)
|
self.base?.deleteRows(at: indexPaths, with: .automatic)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func insertItems(at indexPaths: [IndexPath], animated: Bool) {
|
public func insertItems(at indexPaths: [IndexPath], animated: Bool) {
|
||||||
|
|
||||||
|
MainActor.assumeIsolated {
|
||||||
|
|
||||||
self.base?.insertRows(at: indexPaths, with: .automatic)
|
self.base?.insertRows(at: indexPaths, with: .automatic)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func reloadItems(at indexPaths: [IndexPath], animated: Bool) {
|
public func reloadItems(at indexPaths: [IndexPath], animated: Bool) {
|
||||||
|
|
||||||
|
MainActor.assumeIsolated {
|
||||||
|
|
||||||
self.base?.reloadRows(at: indexPaths, with: .automatic)
|
self.base?.reloadRows(at: indexPaths, with: .automatic)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func moveItem(at indexPath: IndexPath, to newIndexPath: IndexPath, animated: Bool) {
|
public func moveItem(at indexPath: IndexPath, to newIndexPath: IndexPath, animated: Bool) {
|
||||||
|
|
||||||
|
MainActor.assumeIsolated {
|
||||||
|
|
||||||
self.base?.moveRow(at: indexPath, to: newIndexPath)
|
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 {
|
guard let base = self.base else {
|
||||||
|
|
||||||
@@ -291,12 +321,16 @@ extension DiffableDataSource {
|
|||||||
}
|
}
|
||||||
base.performBatchUpdates(updates, completion: { _ in completion() })
|
base.performBatchUpdates(updates, completion: { _ in completion() })
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func reloadData() {
|
public func reloadData() {
|
||||||
|
|
||||||
|
MainActor.assumeIsolated {
|
||||||
|
|
||||||
self.base?.reloadData()
|
self.base?.reloadData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#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.
|
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
|
// MARK: Public
|
||||||
|
|
||||||
@@ -98,7 +98,7 @@ public protocol DiffableDataSourceTarget {
|
|||||||
/**
|
/**
|
||||||
Animates multiple insert, delete, reload, and move operations as a group.
|
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.
|
Reloads all sections and items.
|
||||||
@@ -110,18 +110,24 @@ extension DiffableDataSource.Target {
|
|||||||
|
|
||||||
// MARK: Internal
|
// MARK: Internal
|
||||||
|
|
||||||
internal func reload<C, O>(
|
internal func reload<C: Sendable, O>(
|
||||||
using stagedChangeset: Internals.DiffableDataUIDispatcher<O>.StagedChangeset<C>,
|
using stagedChangeset: Internals.DiffableDataUIDispatcher<O>.StagedChangeset<C>,
|
||||||
animated: Bool,
|
animated: Bool,
|
||||||
interrupt: ((Internals.DiffableDataUIDispatcher<O>.Changeset<C>) -> Bool)? = nil,
|
interrupt: ((Internals.DiffableDataUIDispatcher<O>.Changeset<C>) -> Bool)? = nil,
|
||||||
setData: (C) -> Void,
|
setData: @escaping @Sendable (C) -> Void,
|
||||||
completion: @escaping () -> Void
|
completion: @escaping () -> Void
|
||||||
) {
|
) {
|
||||||
|
|
||||||
let group = DispatchGroup()
|
let group = DispatchGroup()
|
||||||
defer {
|
defer {
|
||||||
|
|
||||||
group.notify(queue: .main, execute: completion)
|
group.notify(
|
||||||
|
queue: .main,
|
||||||
|
execute: {
|
||||||
|
|
||||||
|
completion()
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if self.shouldSuspendBatchUpdates, let data = stagedChangeset.last?.data {
|
if self.shouldSuspendBatchUpdates, let data = stagedChangeset.last?.data {
|
||||||
|
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ extension DispatchQueue {
|
|||||||
|
|
||||||
@nonobjc @inline(__always)
|
@nonobjc @inline(__always)
|
||||||
internal func cs_async(
|
internal func cs_async(
|
||||||
_ closure: @escaping () -> Void
|
_ closure: @escaping @Sendable () -> Void
|
||||||
) {
|
) {
|
||||||
|
|
||||||
self.async { autoreleasepool(invoking: closure) }
|
self.async { autoreleasepool(invoking: closure) }
|
||||||
@@ -115,7 +115,7 @@ extension DispatchQueue {
|
|||||||
|
|
||||||
@nonobjc @inline(__always)
|
@nonobjc @inline(__always)
|
||||||
internal func cs_barrierAsync(
|
internal func cs_barrierAsync(
|
||||||
_ closure: @escaping () -> Void
|
_ closure: @escaping @Sendable () -> Void
|
||||||
) {
|
) {
|
||||||
|
|
||||||
self.async(flags: .barrier) { autoreleasepool(invoking: closure) }
|
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`.
|
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`.
|
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.
|
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
|
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`.
|
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.
|
Do not call directly. Used by the `DataStack` internally.
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ extension Internals {
|
|||||||
|
|
||||||
// MARK: FilePrivate
|
// MARK: FilePrivate
|
||||||
|
|
||||||
fileprivate static var cachedCoders: [NSValueTransformerName: AnyFieldCoder] = [:]
|
fileprivate static nonisolated(unsafe) var cachedCoders: [NSValueTransformerName: AnyFieldCoder] = [:]
|
||||||
|
|
||||||
|
|
||||||
// MARK: - TransformableDefaultValueCodingBox
|
// MARK: - TransformableDefaultValueCodingBox
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ extension Internals {
|
|||||||
|
|
||||||
// Bugfix for NSFetchRequest messing up memory management for `affectedStores`
|
// Bugfix for NSFetchRequest messing up memory management for `affectedStores`
|
||||||
// http://stackoverflow.com/questions/14396375/nsfetchedresultscontroller-crashes-in-ios-6-if-affectedstores-is-specified
|
// 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
|
@nonobjc
|
||||||
internal func safeAffectedStores() -> [NSPersistentStore]? {
|
internal func safeAffectedStores() -> [NSPersistentStore]? {
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ extension Internals {
|
|||||||
|
|
||||||
// MARK: - CoreStoreFetchedResultsController
|
// MARK: - CoreStoreFetchedResultsController
|
||||||
|
|
||||||
internal final class CoreStoreFetchedResultsController: NSFetchedResultsController<NSManagedObject> {
|
internal final class CoreStoreFetchedResultsController: NSFetchedResultsController<NSManagedObject>, @unchecked Sendable {
|
||||||
|
|
||||||
// MARK: Internal
|
// MARK: Internal
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ extension Internals {
|
|||||||
// MARK: - DiffableDataSourceSnapshot
|
// MARK: - DiffableDataSourceSnapshot
|
||||||
|
|
||||||
// Implementation based on https://github.com/ra1028/DiffableDataSources
|
// Implementation based on https://github.com/ra1028/DiffableDataSources
|
||||||
internal struct DiffableDataSourceSnapshot: DiffableDataSourceSnapshotProtocol {
|
internal struct DiffableDataSourceSnapshot: DiffableDataSourceSnapshotProtocol, @unchecked Sendable {
|
||||||
|
|
||||||
// MARK: Internal
|
// MARK: Internal
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,8 @@ extension Internals.DiffableDataUIDispatcher {
|
|||||||
// MARK: - ChangeSet
|
// MARK: - ChangeSet
|
||||||
|
|
||||||
// Implementation based on https://github.com/ra1028/DifferenceKit
|
// 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 data: C
|
||||||
var sectionDeleted: [Int]
|
var sectionDeleted: [Int]
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ extension Internals.DiffableDataUIDispatcher {
|
|||||||
|
|
||||||
// Implementation based on https://github.com/ra1028/DifferenceKit
|
// Implementation based on https://github.com/ra1028/DifferenceKit
|
||||||
@usableFromInline
|
@usableFromInline
|
||||||
internal struct DiffResult<Index> {
|
internal struct DiffResult<Index: Sendable>: Sendable {
|
||||||
|
|
||||||
@usableFromInline
|
@usableFromInline
|
||||||
internal let deleted: [Index]
|
internal let deleted: [Index]
|
||||||
@@ -213,7 +213,7 @@ extension Internals.DiffableDataUIDispatcher {
|
|||||||
|
|
||||||
// Implementation based on https://github.com/ra1028/DifferenceKit
|
// Implementation based on https://github.com/ra1028/DifferenceKit
|
||||||
@usableFromInline
|
@usableFromInline
|
||||||
internal struct Trace<I> {
|
internal struct Trace<I: Sendable>: Sendable {
|
||||||
|
|
||||||
@usableFromInline
|
@usableFromInline
|
||||||
internal var reference: I?
|
internal var reference: I?
|
||||||
|
|||||||
@@ -35,7 +35,8 @@ extension Internals.DiffableDataUIDispatcher {
|
|||||||
// MARK: - StagedChangeset
|
// MARK: - StagedChangeset
|
||||||
|
|
||||||
// Implementation based on https://github.com/ra1028/DifferenceKit
|
// 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
|
@usableFromInline
|
||||||
var changesets: ContiguousArray<Changeset<C>>
|
var changesets: ContiguousArray<Changeset<C>>
|
||||||
@@ -117,7 +118,8 @@ extension Internals.DiffableDataUIDispatcher {
|
|||||||
|
|
||||||
// MARK: - Internals.DiffableDataUIDispatcher.StagedChangeset where C: RangeReplaceableCollection, C.Element: Differentiable
|
// 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
|
@inlinable
|
||||||
internal init(source: C, target: C) {
|
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
|
// 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
|
@inlinable
|
||||||
internal init(source: C, target: C) {
|
internal init(source: C, target: C) {
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ import QuartzCore
|
|||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
// MARK: - Internals
|
// MARK: - Internals
|
||||||
|
|
||||||
@@ -41,7 +43,7 @@ extension Internals {
|
|||||||
|
|
||||||
// Implementation based on https://github.com/ra1028/DiffableDataSources
|
// Implementation based on https://github.com/ra1028/DiffableDataSources
|
||||||
@usableFromInline
|
@usableFromInline
|
||||||
internal final class DiffableDataUIDispatcher<O: DynamicObject> {
|
internal final class DiffableDataUIDispatcher<O: DynamicObject>: @unchecked Sendable {
|
||||||
|
|
||||||
// MARK: Internal
|
// MARK: Internal
|
||||||
|
|
||||||
@@ -55,10 +57,10 @@ extension Internals {
|
|||||||
func purge<Target: DiffableDataSource.Target>(
|
func purge<Target: DiffableDataSource.Target>(
|
||||||
target: Target?,
|
target: Target?,
|
||||||
animatingDifferences: Bool,
|
animatingDifferences: Bool,
|
||||||
performUpdates: @escaping (
|
performUpdates: @escaping @Sendable (
|
||||||
Target,
|
Target,
|
||||||
StagedChangeset<[Internals.DiffableDataSourceSnapshot.Section]>,
|
StagedChangeset<[Internals.DiffableDataSourceSnapshot.Section]>,
|
||||||
@escaping ([Internals.DiffableDataSourceSnapshot.Section]) -> Void
|
@escaping @Sendable ([Internals.DiffableDataSourceSnapshot.Section]) -> Void
|
||||||
) -> Void
|
) -> Void
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@@ -74,14 +76,14 @@ extension Internals {
|
|||||||
_ snapshot: DiffableDataSourceSnapshot,
|
_ snapshot: DiffableDataSourceSnapshot,
|
||||||
target: Target?,
|
target: Target?,
|
||||||
animatingDifferences: Bool,
|
animatingDifferences: Bool,
|
||||||
performUpdates: @escaping (
|
performUpdates: @escaping @Sendable (
|
||||||
Target,
|
Target,
|
||||||
StagedChangeset<[Internals.DiffableDataSourceSnapshot.Section]>,
|
StagedChangeset<[Internals.DiffableDataSourceSnapshot.Section]>,
|
||||||
@escaping ([Internals.DiffableDataSourceSnapshot.Section]) -> Void
|
@escaping @Sendable ([Internals.DiffableDataSourceSnapshot.Section]) -> Void
|
||||||
) -> Void
|
) -> Void
|
||||||
) {
|
) {
|
||||||
|
|
||||||
self.dispatcher.dispatch { [weak self] in
|
self.dispatcher.dispatch { @MainActor [weak self] in
|
||||||
|
|
||||||
guard let self = self else {
|
guard let self = self else {
|
||||||
|
|
||||||
@@ -97,13 +99,17 @@ extension Internals {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let performDiffingUpdates: () -> Void = {
|
let performDiffingUpdates: @MainActor () -> Void = {
|
||||||
|
|
||||||
let changeset = StagedChangeset(source: self.sections, target: newSections)
|
let changeset = StagedChangeset(source: self.sections, target: newSections)
|
||||||
performUpdates(target, changeset) { sections in
|
performUpdates(
|
||||||
|
target,
|
||||||
|
changeset,
|
||||||
|
{ sections in
|
||||||
|
|
||||||
self.sections = sections
|
self.sections = sections
|
||||||
}
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#if os(watchOS) || !canImport(QuartzCore)
|
#if os(watchOS) || !canImport(QuartzCore)
|
||||||
@@ -212,7 +218,7 @@ extension Internals {
|
|||||||
// MARK: - ElementPath
|
// MARK: - ElementPath
|
||||||
|
|
||||||
@usableFromInline
|
@usableFromInline
|
||||||
internal struct ElementPath: Hashable {
|
internal struct ElementPath: Hashable, Sendable {
|
||||||
|
|
||||||
@usableFromInline
|
@usableFromInline
|
||||||
var element: Int
|
var element: Int
|
||||||
@@ -231,18 +237,21 @@ extension Internals {
|
|||||||
|
|
||||||
// MARK: - MainThreadSerialDispatcher
|
// MARK: - MainThreadSerialDispatcher
|
||||||
|
|
||||||
fileprivate final class MainThreadSerialDispatcher {
|
fileprivate final class MainThreadSerialDispatcher: Sendable {
|
||||||
|
|
||||||
// MARK: FilePrivate
|
// MARK: FilePrivate
|
||||||
|
|
||||||
fileprivate init() {}
|
fileprivate init() {}
|
||||||
|
|
||||||
fileprivate func dispatch(_ action: @escaping () -> Void) {
|
fileprivate func dispatch(_ action: @escaping @MainActor () -> Void) {
|
||||||
|
|
||||||
let count = self.executingCount.incrementAndGet()
|
let count = self.executingCount.incrementAndGet()
|
||||||
if Thread.isMainThread && count == 1 {
|
if Thread.isMainThread && count == 1 {
|
||||||
|
|
||||||
|
MainActor.assumeIsolated {
|
||||||
|
|
||||||
action()
|
action()
|
||||||
|
}
|
||||||
self.executingCount.decrement()
|
self.executingCount.decrement()
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -267,36 +276,31 @@ extension Internals {
|
|||||||
|
|
||||||
// MARK: - AtomicInt
|
// MARK: - AtomicInt
|
||||||
|
|
||||||
fileprivate class AtomicInt {
|
fileprivate final class AtomicInt: Sendable {
|
||||||
|
|
||||||
// MARK: FilePrivate
|
// MARK: FilePrivate
|
||||||
|
|
||||||
fileprivate func incrementAndGet() -> Int {
|
fileprivate func incrementAndGet() -> Int {
|
||||||
|
|
||||||
self.lock.wait()
|
return self.value.withLock {
|
||||||
defer {
|
|
||||||
|
|
||||||
self.lock.signal()
|
$0 += 1
|
||||||
|
return $0
|
||||||
}
|
}
|
||||||
self.value += 1
|
|
||||||
return self.value
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func decrement() {
|
fileprivate func decrement() {
|
||||||
|
|
||||||
self.lock.wait()
|
self.value.withLock {
|
||||||
defer {
|
|
||||||
|
|
||||||
self.lock.signal()
|
$0 -= 1
|
||||||
}
|
}
|
||||||
self.value -= 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// MARK: Private
|
// MARK: Private
|
||||||
|
|
||||||
private let lock = DispatchSemaphore(value: 1)
|
private let value: OSAllocatedUnfairLock<Int> = .init(initialState: 0)
|
||||||
private var value = 0
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ extension Internals {
|
|||||||
|
|
||||||
// MARK: - FetchedResultsControllerDelegate
|
// MARK: - FetchedResultsControllerDelegate
|
||||||
|
|
||||||
internal final class FetchedResultsControllerDelegate: NSObject, NSFetchedResultsControllerDelegate {
|
internal final class FetchedResultsControllerDelegate: NSObject, NSFetchedResultsControllerDelegate, @unchecked Sendable {
|
||||||
|
|
||||||
// MARK: Internal
|
// MARK: Internal
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ extension Internals {
|
|||||||
notificationName: Notification.Name,
|
notificationName: Notification.Name,
|
||||||
object: Any?,
|
object: Any?,
|
||||||
queue: OperationQueue? = nil,
|
queue: OperationQueue? = nil,
|
||||||
closure: @escaping (_ note: Notification) -> Void
|
closure: @escaping @Sendable (_ note: Notification) -> Void
|
||||||
) {
|
) {
|
||||||
|
|
||||||
self.observer = NotificationCenter.default.addObserver(
|
self.observer = NotificationCenter.default.addObserver(
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ extension Internals {
|
|||||||
|
|
||||||
// MARK: - SharedNotificationObserver
|
// MARK: - SharedNotificationObserver
|
||||||
|
|
||||||
internal final class SharedNotificationObserver<T> {
|
internal final class SharedNotificationObserver<T>: @unchecked Sendable {
|
||||||
|
|
||||||
// MARK: Internal
|
// MARK: Internal
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ extension Internals {
|
|||||||
notificationName: Notification.Name,
|
notificationName: Notification.Name,
|
||||||
object: Any?,
|
object: Any?,
|
||||||
queue: OperationQueue? = nil,
|
queue: OperationQueue? = nil,
|
||||||
sharedValue: @escaping (_ note: Notification) -> T
|
sharedValue: @escaping @Sendable (_ note: Notification) -> T
|
||||||
) {
|
) {
|
||||||
|
|
||||||
self.observer = NotificationCenter.default.addObserver(
|
self.observer = NotificationCenter.default.addObserver(
|
||||||
|
|||||||
+45
-10
@@ -122,6 +122,7 @@ internal enum Internals {
|
|||||||
return closure()
|
return closure()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@inline(__always)
|
@inline(__always)
|
||||||
internal static func autoreleasepool<T>(
|
internal static func autoreleasepool<T>(
|
||||||
_ closure: () -> T
|
_ closure: () -> T
|
||||||
@@ -139,29 +140,63 @@ internal enum Internals {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@inline(__always)
|
@inline(__always)
|
||||||
internal static func autoreleasepool<T>(
|
internal static func autoreleasepool<T, E>(
|
||||||
_ closure: () throws(CoreStoreError) -> T
|
_ closure: () throws(E) -> T
|
||||||
) throws(CoreStoreError) -> T {
|
) throws(E) -> T {
|
||||||
|
|
||||||
do {
|
|
||||||
|
|
||||||
return try ObjectiveC.autoreleasepool(invoking: closure)
|
return try ObjectiveC.autoreleasepool(invoking: closure)
|
||||||
}
|
}
|
||||||
catch {
|
|
||||||
|
|
||||||
throw CoreStoreError(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@inline(__always)
|
@inline(__always)
|
||||||
internal static func withCheckedThrowingContinuation<T>(
|
internal static func withCheckedThrowingContinuation<T>(
|
||||||
function: String = #function,
|
function: String = #function,
|
||||||
_ body: (CheckedContinuation<T, any Swift.Error>) -> Void
|
_ body: (CheckedContinuation<T, any Swift.Error>) -> Void
|
||||||
) async throws(any Swift.Error) -> T {
|
) async throws(any Swift.Error) -> sending T {
|
||||||
|
|
||||||
return try await _Concurrency.withCheckedThrowingContinuation(
|
return try await _Concurrency.withCheckedThrowingContinuation(
|
||||||
function: function,
|
function: function,
|
||||||
body
|
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"))
|
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
|
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.
|
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)
|
// MARK: Public (Accessors)
|
||||||
|
|
||||||
@@ -648,7 +648,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
|
|||||||
*/
|
*/
|
||||||
public func refetch(
|
public func refetch(
|
||||||
_ fetchClauses: FetchClause...,
|
_ fetchClauses: FetchClause...,
|
||||||
sourceIdentifier: Any? = nil
|
sourceIdentifier: (any Sendable)? = nil
|
||||||
) {
|
) {
|
||||||
|
|
||||||
self.refetch(
|
self.refetch(
|
||||||
@@ -668,7 +668,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
|
|||||||
*/
|
*/
|
||||||
public func refetch(
|
public func refetch(
|
||||||
_ fetchClauses: [FetchClause],
|
_ fetchClauses: [FetchClause],
|
||||||
sourceIdentifier: Any? = nil
|
sourceIdentifier: (any Sendable)? = nil
|
||||||
) {
|
) {
|
||||||
|
|
||||||
self.refetch(
|
self.refetch(
|
||||||
@@ -751,7 +751,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
|
|||||||
from: From<O>,
|
from: From<O>,
|
||||||
sectionBy: SectionBy<O>?,
|
sectionBy: SectionBy<O>?,
|
||||||
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void,
|
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void,
|
||||||
createAsynchronously: @escaping (ListMonitor<O>) -> Void
|
createAsynchronously: @escaping @Sendable (ListMonitor<O>) -> Void
|
||||||
) {
|
) {
|
||||||
|
|
||||||
self.init(
|
self.init(
|
||||||
@@ -786,7 +786,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
|
|||||||
from: From<O>,
|
from: From<O>,
|
||||||
sectionBy: SectionBy<O>?,
|
sectionBy: SectionBy<O>?,
|
||||||
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void,
|
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void,
|
||||||
createAsynchronously: @escaping (ListMonitor<O>) -> Void
|
createAsynchronously: @escaping @Sendable (ListMonitor<O>) -> Void
|
||||||
) {
|
) {
|
||||||
|
|
||||||
self.init(
|
self.init(
|
||||||
@@ -803,7 +803,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
|
|||||||
_ notificationKey: UnsafeRawPointer,
|
_ notificationKey: UnsafeRawPointer,
|
||||||
name: Notification.Name,
|
name: Notification.Name,
|
||||||
toObserver observer: AnyObject,
|
toObserver observer: AnyObject,
|
||||||
callback: @escaping (_ monitor: ListMonitor<O>) -> Void
|
callback: @escaping @Sendable (_ monitor: ListMonitor<O>) -> Void
|
||||||
) {
|
) {
|
||||||
|
|
||||||
Internals.setAssociatedRetainedObject(
|
Internals.setAssociatedRetainedObject(
|
||||||
@@ -828,7 +828,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
|
|||||||
_ notificationKey: UnsafeRawPointer,
|
_ notificationKey: UnsafeRawPointer,
|
||||||
name: Notification.Name,
|
name: Notification.Name,
|
||||||
toObserver observer: AnyObject,
|
toObserver observer: AnyObject,
|
||||||
callback: @escaping (
|
callback: @escaping @Sendable (
|
||||||
_ monitor: ListMonitor<O>,
|
_ monitor: ListMonitor<O>,
|
||||||
_ object: O,
|
_ object: O,
|
||||||
_ indexPath: IndexPath?,
|
_ indexPath: IndexPath?,
|
||||||
@@ -864,7 +864,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
|
|||||||
_ notificationKey: UnsafeRawPointer,
|
_ notificationKey: UnsafeRawPointer,
|
||||||
name: Notification.Name,
|
name: Notification.Name,
|
||||||
toObserver observer: AnyObject,
|
toObserver observer: AnyObject,
|
||||||
callback: @escaping (
|
callback: @escaping @Sendable (
|
||||||
_ monitor: ListMonitor<O>,
|
_ monitor: ListMonitor<O>,
|
||||||
_ sectionInfo: NSFetchedResultsSectionInfo,
|
_ sectionInfo: NSFetchedResultsSectionInfo,
|
||||||
_ sectionIndex: Int
|
_ 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,
|
_ observer: U,
|
||||||
willChange: @escaping (
|
willChange: @escaping @Sendable (
|
||||||
_ observer: U,
|
_ observer: U,
|
||||||
_ monitor: ListMonitor<O>
|
_ monitor: ListMonitor<O>
|
||||||
) -> Void,
|
) -> Void,
|
||||||
didChange: @escaping (
|
didChange: @escaping @Sendable (
|
||||||
_ observer: U,
|
_ observer: U,
|
||||||
_ monitor: ListMonitor<O>
|
_ monitor: ListMonitor<O>
|
||||||
) -> Void,
|
) -> Void,
|
||||||
willRefetch: @escaping (
|
willRefetch: @escaping @Sendable (
|
||||||
_ observer: U,
|
_ observer: U,
|
||||||
_ monitor: ListMonitor<O>
|
_ monitor: ListMonitor<O>
|
||||||
) -> Void,
|
) -> Void,
|
||||||
didRefetch: @escaping (
|
didRefetch: @escaping @Sendable (
|
||||||
_ observer: U,
|
_ observer: U,
|
||||||
_ monitor: ListMonitor<O>
|
_ monitor: ListMonitor<O>
|
||||||
) -> Void) {
|
) -> 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,
|
_ observer: U,
|
||||||
didInsertObject: @escaping (
|
didInsertObject: @escaping @Sendable (
|
||||||
_ observer: U,
|
_ observer: U,
|
||||||
_ monitor: ListMonitor<O>,
|
_ monitor: ListMonitor<O>,
|
||||||
_ object: O,
|
_ object: O,
|
||||||
_ toIndexPath: IndexPath
|
_ toIndexPath: IndexPath
|
||||||
) -> Void,
|
) -> Void,
|
||||||
didDeleteObject: @escaping (
|
didDeleteObject: @escaping @Sendable (
|
||||||
_ observer: U,
|
_ observer: U,
|
||||||
_ monitor: ListMonitor<O>,
|
_ monitor: ListMonitor<O>,
|
||||||
_ object: O,
|
_ object: O,
|
||||||
_ fromIndexPath: IndexPath
|
_ fromIndexPath: IndexPath
|
||||||
) -> Void,
|
) -> Void,
|
||||||
didUpdateObject: @escaping (
|
didUpdateObject: @escaping @Sendable (
|
||||||
_ observer: U,
|
_ observer: U,
|
||||||
_ monitor: ListMonitor<O>,
|
_ monitor: ListMonitor<O>,
|
||||||
_ object: O,
|
_ object: O,
|
||||||
_ atIndexPath: IndexPath
|
_ atIndexPath: IndexPath
|
||||||
) -> Void,
|
) -> Void,
|
||||||
didMoveObject: @escaping (
|
didMoveObject: @escaping @Sendable (
|
||||||
_ observer: U,
|
_ observer: U,
|
||||||
_ monitor: ListMonitor<O>,
|
_ monitor: ListMonitor<O>,
|
||||||
_ object: 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,
|
_ observer: U,
|
||||||
didInsertSection: @escaping (
|
didInsertSection: @escaping @Sendable (
|
||||||
_ observer: U,
|
_ observer: U,
|
||||||
_ monitor: ListMonitor<O>,
|
_ monitor: ListMonitor<O>,
|
||||||
_ sectionInfo: NSFetchedResultsSectionInfo,
|
_ sectionInfo: NSFetchedResultsSectionInfo,
|
||||||
_ toIndex: Int
|
_ toIndex: Int
|
||||||
) -> Void,
|
) -> Void,
|
||||||
didDeleteSection: @escaping (
|
didDeleteSection: @escaping @Sendable (
|
||||||
_ observer: U,
|
_ observer: U,
|
||||||
_ monitor: ListMonitor<O>,
|
_ monitor: ListMonitor<O>,
|
||||||
_ sectionInfo: NSFetchedResultsSectionInfo,
|
_ sectionInfo: NSFetchedResultsSectionInfo,
|
||||||
@@ -1127,7 +1127,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
|
|||||||
|
|
||||||
internal func refetch(
|
internal func refetch(
|
||||||
_ applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void,
|
_ applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void,
|
||||||
sourceIdentifier: Any?
|
sourceIdentifier: (any Sendable)?
|
||||||
) {
|
) {
|
||||||
|
|
||||||
Internals.assert(
|
Internals.assert(
|
||||||
@@ -1302,7 +1302,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
|
|||||||
from: From<O>,
|
from: From<O>,
|
||||||
sectionBy: SectionBy<O>?,
|
sectionBy: SectionBy<O>?,
|
||||||
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void,
|
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void,
|
||||||
createAsynchronously: ((ListMonitor<O>) -> Void)?
|
createAsynchronously: (@Sendable (ListMonitor<O>) -> Void)?
|
||||||
) {
|
) {
|
||||||
|
|
||||||
self.isSectioned = (sectionBy != nil)
|
self.isSectioned = (sectionBy != nil)
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ import CoreData
|
|||||||
monitor.addObserver(self)
|
monitor.addObserver(self)
|
||||||
```
|
```
|
||||||
*/
|
*/
|
||||||
public protocol ListObserver: AnyObject {
|
public protocol ListObserver: AnyObject, Sendable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
The `NSManagedObject` type for the observed list
|
The `NSManagedObject` type for the observed list
|
||||||
|
|||||||
@@ -90,7 +90,8 @@ extension ListPublisher {
|
|||||||
|
|
||||||
// MARK: - ListSnapshotSubscription
|
// 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
|
// MARK: FilePrivate
|
||||||
|
|
||||||
@@ -114,6 +115,8 @@ extension ListPublisher {
|
|||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
Internals.mainActorImmediate { [self] in
|
||||||
|
|
||||||
self.publisher.addObserver(
|
self.publisher.addObserver(
|
||||||
self,
|
self,
|
||||||
notifyInitial: self.emitInitialValue,
|
notifyInitial: self.emitInitialValue,
|
||||||
@@ -130,6 +133,7 @@ extension ListPublisher {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// MARK: Cancellable
|
// MARK: Cancellable
|
||||||
@@ -138,17 +142,10 @@ extension ListPublisher {
|
|||||||
|
|
||||||
self.subscriber = nil
|
self.subscriber = nil
|
||||||
|
|
||||||
if Thread.isMainThread {
|
Internals.mainActorImmediate {
|
||||||
|
|
||||||
self.publisher.removeObserver(self)
|
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 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
|
- parameter callback: the closure to execute when changes occur
|
||||||
*/
|
*/
|
||||||
|
@MainActor
|
||||||
public func addObserver<T: AnyObject>(
|
public func addObserver<T: AnyObject>(
|
||||||
_ observer: T,
|
_ observer: T,
|
||||||
notifyInitial: Bool = false,
|
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 initialSourceIdentifier: an optional value that identifies the initial callback invocation if `notifyInitial` is `true`.
|
||||||
- parameter callback: the closure to execute when changes occur
|
- parameter callback: the closure to execute when changes occur
|
||||||
*/
|
*/
|
||||||
|
@MainActor
|
||||||
public func addObserver<T: AnyObject>(
|
public func addObserver<T: AnyObject>(
|
||||||
_ observer: T,
|
_ observer: T,
|
||||||
notifyInitial: Bool = false,
|
notifyInitial: Bool = false,
|
||||||
@@ -159,6 +161,7 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
|
|||||||
|
|
||||||
- parameter observer: the object whose notifications will be unregistered
|
- parameter observer: the object whose notifications will be unregistered
|
||||||
*/
|
*/
|
||||||
|
@MainActor
|
||||||
public func removeObserver<T: AnyObject>(_ observer: T) {
|
public func removeObserver<T: AnyObject>(_ observer: T) {
|
||||||
|
|
||||||
Internals.assert(
|
Internals.assert(
|
||||||
@@ -185,7 +188,7 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
|
|||||||
*/
|
*/
|
||||||
public func refetch<B: FetchChainableBuilderType>(
|
public func refetch<B: FetchChainableBuilderType>(
|
||||||
_ clauseChain: B,
|
_ clauseChain: B,
|
||||||
sourceIdentifier: Any? = nil
|
sourceIdentifier: (any Sendable)? = nil
|
||||||
) throws(any Swift.Error) where B.ObjectType == O {
|
) throws(any Swift.Error) where B.ObjectType == O {
|
||||||
|
|
||||||
try self.refetch(
|
try self.refetch(
|
||||||
@@ -214,7 +217,7 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
|
|||||||
*/
|
*/
|
||||||
public func refetch<B: SectionMonitorBuilderType>(
|
public func refetch<B: SectionMonitorBuilderType>(
|
||||||
_ clauseChain: B,
|
_ clauseChain: B,
|
||||||
sourceIdentifier: Any? = nil
|
sourceIdentifier: (any Sendable)? = nil
|
||||||
) throws(any Swift.Error) where B.ObjectType == O {
|
) throws(any Swift.Error) where B.ObjectType == O {
|
||||||
|
|
||||||
try self.refetch(
|
try self.refetch(
|
||||||
@@ -342,7 +345,7 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
|
|||||||
from: From<O>,
|
from: From<O>,
|
||||||
sectionBy: SectionBy<O>?,
|
sectionBy: SectionBy<O>?,
|
||||||
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void,
|
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void,
|
||||||
sourceIdentifier: Any?
|
sourceIdentifier: (any Sendable)?
|
||||||
) throws(any Swift.Error) {
|
) throws(any Swift.Error) {
|
||||||
|
|
||||||
let (newFetchedResultsController, newFetchedResultsControllerDelegate) = Self.recreateFetchedResultsController(
|
let (newFetchedResultsController, newFetchedResultsControllerDelegate) = Self.recreateFetchedResultsController(
|
||||||
|
|||||||
+35
-9
@@ -23,9 +23,9 @@
|
|||||||
// SOFTWARE.
|
// SOFTWARE.
|
||||||
//
|
//
|
||||||
|
|
||||||
#if canImport(Combine) && canImport(SwiftUI)
|
#if canImport(Observation) && canImport(SwiftUI)
|
||||||
|
|
||||||
import Combine
|
import Observation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
|
|
||||||
@@ -65,11 +65,12 @@ public struct ListState<Object: DynamicObject>: DynamicProperty {
|
|||||||
|
|
||||||
- parameter listPublisher: The `ListPublisher` that the `ListState` will observe changes for
|
- parameter listPublisher: The `ListPublisher` that the `ListState` will observe changes for
|
||||||
*/
|
*/
|
||||||
|
@MainActor
|
||||||
public init(
|
public init(
|
||||||
_ listPublisher: ListPublisher<Object>
|
_ 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
|
- parameter clauseChain: a `FetchChainableBuilderType` built from a chain of clauses
|
||||||
*/
|
*/
|
||||||
|
@MainActor
|
||||||
public init<B: FetchChainableBuilderType>(
|
public init<B: FetchChainableBuilderType>(
|
||||||
_ clauseChain: B,
|
_ clauseChain: B,
|
||||||
in dataStack: DataStack
|
in dataStack: DataStack
|
||||||
@@ -139,6 +141,7 @@ public struct ListState<Object: DynamicObject>: DynamicProperty {
|
|||||||
|
|
||||||
- parameter clauseChain: a `SectionMonitorBuilderType` built from a chain of clauses
|
- parameter clauseChain: a `SectionMonitorBuilderType` built from a chain of clauses
|
||||||
*/
|
*/
|
||||||
|
@MainActor
|
||||||
public init<B: SectionMonitorBuilderType>(
|
public init<B: SectionMonitorBuilderType>(
|
||||||
_ clauseChain: B,
|
_ clauseChain: B,
|
||||||
in dataStack: DataStack
|
in dataStack: DataStack
|
||||||
@@ -174,6 +177,7 @@ public struct ListState<Object: DynamicObject>: DynamicProperty {
|
|||||||
- parameter from: a `From` clause indicating the entity type
|
- 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.
|
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||||
*/
|
*/
|
||||||
|
@MainActor
|
||||||
public init(
|
public init(
|
||||||
_ from: From<Object>,
|
_ from: From<Object>,
|
||||||
_ fetchClauses: FetchClause...,
|
_ fetchClauses: FetchClause...,
|
||||||
@@ -212,6 +216,7 @@ public struct ListState<Object: DynamicObject>: DynamicProperty {
|
|||||||
- parameter from: a `From` clause indicating the entity type
|
- 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.
|
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||||
*/
|
*/
|
||||||
|
@MainActor
|
||||||
public init(
|
public init(
|
||||||
_ from: From<Object>,
|
_ from: From<Object>,
|
||||||
_ fetchClauses: [FetchClause],
|
_ 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 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.
|
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||||
*/
|
*/
|
||||||
|
@MainActor
|
||||||
public init(
|
public init(
|
||||||
_ from: From<Object>,
|
_ from: From<Object>,
|
||||||
_ sectionBy: SectionBy<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 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.
|
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||||
*/
|
*/
|
||||||
|
@MainActor
|
||||||
public init(
|
public init(
|
||||||
_ from: From<Object>,
|
_ from: From<Object>,
|
||||||
_ sectionBy: SectionBy<Object>,
|
_ sectionBy: SectionBy<Object>,
|
||||||
@@ -316,11 +323,13 @@ public struct ListState<Object: DynamicObject>: DynamicProperty {
|
|||||||
|
|
||||||
// MARK: @propertyWrapper
|
// MARK: @propertyWrapper
|
||||||
|
|
||||||
|
@MainActor
|
||||||
public var wrappedValue: ListSnapshot<Object> {
|
public var wrappedValue: ListSnapshot<Object> {
|
||||||
|
|
||||||
return self.observer.items
|
return self.observer.items
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
public var projectedValue: ListPublisher<Object> {
|
public var projectedValue: ListPublisher<Object> {
|
||||||
|
|
||||||
return self.observer.listPublisher
|
return self.observer.listPublisher
|
||||||
@@ -337,23 +346,40 @@ public struct ListState<Object: DynamicObject>: DynamicProperty {
|
|||||||
|
|
||||||
// MARK: Private
|
// MARK: Private
|
||||||
|
|
||||||
@ObservedObject
|
@State
|
||||||
private var observer: Observer
|
private var observer: Observer
|
||||||
|
|
||||||
|
|
||||||
// MARK: - Observer
|
// MARK: - Observer
|
||||||
|
|
||||||
private final class Observer: ObservableObject {
|
@MainActor
|
||||||
|
private final class Observer: Observation.Observable {
|
||||||
|
|
||||||
@Published
|
private let registrar = ObservationRegistrar()
|
||||||
var items: ListSnapshot<Object>
|
private var _items: ListSnapshot<Object>
|
||||||
|
|
||||||
let listPublisher: ListPublisher<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>) {
|
init(listPublisher: ListPublisher<Object>) {
|
||||||
|
|
||||||
self.listPublisher = listPublisher
|
self.listPublisher = listPublisher
|
||||||
self.items = listPublisher.snapshot
|
self._items = listPublisher.snapshot
|
||||||
|
|
||||||
listPublisher.addObserver(self) { [weak self] (listPublisher) in
|
listPublisher.addObserver(self) { [weak self] (listPublisher) in
|
||||||
|
|
||||||
@@ -365,7 +391,7 @@ public struct ListState<Object: DynamicObject>: DynamicProperty {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
isolated deinit {
|
||||||
|
|
||||||
self.listPublisher.removeObserver(self)
|
self.listPublisher.removeObserver(self)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ import Foundation
|
|||||||
/**
|
/**
|
||||||
The `MigrationType` specifies the type of migration required for a store.
|
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
|
Indicates that the persistent store matches the latest model version and no migration is needed
|
||||||
|
|||||||
@@ -56,6 +56,6 @@ extension NSManagedObject {
|
|||||||
|
|
||||||
private struct PropertyKeys {
|
private struct PropertyKeys {
|
||||||
|
|
||||||
static var coreStoreObject: Void?
|
static nonisolated(unsafe) var coreStoreObject: Void?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -192,8 +192,8 @@ extension NSManagedObjectContext {
|
|||||||
|
|
||||||
private struct PropertyKeys {
|
private struct PropertyKeys {
|
||||||
|
|
||||||
static var observerForWillSaveNotification: Void?
|
static nonisolated(unsafe) var observerForWillSaveNotification: Void?
|
||||||
static var shouldCascadeSavesToParent: Void?
|
static nonisolated(unsafe) var shouldCascadeSavesToParent: Void?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -293,30 +293,23 @@ extension NSManagedObjectContext: FetchableSource, QueryableSource {
|
|||||||
_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObjectID>
|
_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObjectID>
|
||||||
) throws(CoreStoreError) -> [NSManagedObjectID] {
|
) throws(CoreStoreError) -> [NSManagedObjectID] {
|
||||||
|
|
||||||
var fetchResults: [NSManagedObjectID]?
|
|
||||||
var fetchError: Error?
|
|
||||||
self.performAndWait {
|
|
||||||
|
|
||||||
do {
|
do {
|
||||||
|
|
||||||
fetchResults = try self.fetch(fetchRequest.dynamicCast())
|
return try self.performAndWait {
|
||||||
|
|
||||||
|
return try self.fetch(fetchRequest.staticCast())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
|
|
||||||
fetchError = error
|
let coreStoreError = CoreStoreError(error)
|
||||||
}
|
|
||||||
}
|
|
||||||
if let fetchResults = fetchResults {
|
|
||||||
|
|
||||||
return fetchResults
|
|
||||||
}
|
|
||||||
let coreStoreError = CoreStoreError(fetchError)
|
|
||||||
Internals.log(
|
Internals.log(
|
||||||
coreStoreError,
|
coreStoreError,
|
||||||
"Failed executing fetch request."
|
"Failed executing query request."
|
||||||
)
|
)
|
||||||
throw coreStoreError
|
throw coreStoreError
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// MARK: QueryableSource
|
// MARK: QueryableSource
|
||||||
@@ -443,89 +436,74 @@ extension NSManagedObjectContext {
|
|||||||
_ fetchRequest: Internals.CoreStoreFetchRequest<O>
|
_ fetchRequest: Internals.CoreStoreFetchRequest<O>
|
||||||
) throws(CoreStoreError) -> O? {
|
) throws(CoreStoreError) -> O? {
|
||||||
|
|
||||||
var fetchResults: [O]?
|
|
||||||
var fetchError: (any Swift.Error)?
|
|
||||||
self.performAndWait {
|
|
||||||
|
|
||||||
do {
|
do {
|
||||||
|
|
||||||
fetchResults = try self.fetch(fetchRequest.staticCast())
|
let fetchResults = try self.performAndWait {
|
||||||
|
|
||||||
|
return try self.fetch(fetchRequest.staticCast())
|
||||||
|
}
|
||||||
|
return fetchResults.first
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
|
|
||||||
fetchError = error
|
let coreStoreError = CoreStoreError(error)
|
||||||
}
|
|
||||||
}
|
|
||||||
if let fetchResults = fetchResults {
|
|
||||||
|
|
||||||
return fetchResults.first
|
|
||||||
}
|
|
||||||
let coreStoreError = CoreStoreError(fetchError)
|
|
||||||
Internals.log(
|
Internals.log(
|
||||||
coreStoreError,
|
coreStoreError,
|
||||||
"Failed executing fetch request."
|
"Failed executing fetch request."
|
||||||
)
|
)
|
||||||
throw coreStoreError
|
throw coreStoreError
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@nonobjc
|
@nonobjc
|
||||||
internal func fetchAll<O: NSManagedObject>(
|
internal func fetchAll<O: NSManagedObject>(
|
||||||
_ fetchRequest: Internals.CoreStoreFetchRequest<O>
|
_ fetchRequest: Internals.CoreStoreFetchRequest<O>
|
||||||
) throws(CoreStoreError) -> [O] {
|
) throws(CoreStoreError) -> [O] {
|
||||||
|
|
||||||
var fetchResults: [O]?
|
|
||||||
var fetchError: (any Swift.Error)?
|
|
||||||
self.performAndWait {
|
|
||||||
|
|
||||||
do {
|
do {
|
||||||
|
|
||||||
fetchResults = try self.fetch(fetchRequest.staticCast())
|
return try self.performAndWait {
|
||||||
|
|
||||||
|
return try self.fetch(fetchRequest.staticCast())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
|
|
||||||
fetchError = error
|
let coreStoreError = CoreStoreError(error)
|
||||||
}
|
|
||||||
}
|
|
||||||
if let fetchResults = fetchResults {
|
|
||||||
|
|
||||||
return fetchResults
|
|
||||||
}
|
|
||||||
let coreStoreError = CoreStoreError(fetchError)
|
|
||||||
Internals.log(
|
Internals.log(
|
||||||
coreStoreError,
|
coreStoreError,
|
||||||
"Failed executing fetch request."
|
"Failed executing fetch request."
|
||||||
)
|
)
|
||||||
throw coreStoreError
|
throw coreStoreError
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@nonobjc
|
@nonobjc
|
||||||
internal func fetchCount(
|
internal func fetchCount(
|
||||||
_ fetchRequest: Internals.CoreStoreFetchRequest<NSNumber>
|
_ fetchRequest: Internals.CoreStoreFetchRequest<NSNumber>
|
||||||
) throws(CoreStoreError) -> Int {
|
) throws(CoreStoreError) -> Int {
|
||||||
|
|
||||||
var count = 0
|
|
||||||
var countError: (any Swift.Error)?
|
|
||||||
self.performAndWait {
|
|
||||||
|
|
||||||
do {
|
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 {
|
catch {
|
||||||
|
|
||||||
countError = error
|
let coreStoreError = CoreStoreError(error)
|
||||||
}
|
|
||||||
}
|
|
||||||
if count == NSNotFound {
|
|
||||||
|
|
||||||
let coreStoreError = CoreStoreError(countError)
|
|
||||||
Internals.log(
|
Internals.log(
|
||||||
coreStoreError,
|
coreStoreError,
|
||||||
"Failed executing count request."
|
"Failed executing count request."
|
||||||
)
|
)
|
||||||
throw coreStoreError
|
throw coreStoreError
|
||||||
}
|
}
|
||||||
return count
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@nonobjc
|
@nonobjc
|
||||||
@@ -533,30 +511,24 @@ extension NSManagedObjectContext {
|
|||||||
_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObjectID>
|
_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObjectID>
|
||||||
) throws(CoreStoreError) -> NSManagedObjectID? {
|
) throws(CoreStoreError) -> NSManagedObjectID? {
|
||||||
|
|
||||||
var fetchResults: [NSManagedObjectID]?
|
|
||||||
var fetchError: (any Swift.Error)?
|
|
||||||
self.performAndWait {
|
|
||||||
|
|
||||||
do {
|
do {
|
||||||
|
|
||||||
fetchResults = try self.fetch(fetchRequest.staticCast())
|
let fetchResults = try self.performAndWait {
|
||||||
|
|
||||||
|
return try self.fetch(fetchRequest.staticCast())
|
||||||
|
}
|
||||||
|
return fetchResults.first
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
|
|
||||||
fetchError = error
|
let coreStoreError = CoreStoreError(error)
|
||||||
}
|
|
||||||
}
|
|
||||||
if let fetchResults = fetchResults {
|
|
||||||
|
|
||||||
return fetchResults.first
|
|
||||||
}
|
|
||||||
let coreStoreError = CoreStoreError(fetchError)
|
|
||||||
Internals.log(
|
Internals.log(
|
||||||
coreStoreError,
|
coreStoreError,
|
||||||
"Failed executing fetch request."
|
"Failed executing fetch request."
|
||||||
)
|
)
|
||||||
throw coreStoreError
|
throw coreStoreError
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// MARK: Querying
|
// MARK: Querying
|
||||||
@@ -567,35 +539,29 @@ extension NSManagedObjectContext {
|
|||||||
fetchRequest: Internals.CoreStoreFetchRequest<NSDictionary>
|
fetchRequest: Internals.CoreStoreFetchRequest<NSDictionary>
|
||||||
) throws(CoreStoreError) -> U? {
|
) throws(CoreStoreError) -> U? {
|
||||||
|
|
||||||
var fetchResults: [Any]?
|
|
||||||
var fetchError: (any Swift.Error)?
|
|
||||||
self.performAndWait {
|
|
||||||
|
|
||||||
do {
|
do {
|
||||||
|
|
||||||
fetchResults = try self.fetch(fetchRequest.staticCast())
|
let fetchResults = try self.performAndWait {
|
||||||
}
|
|
||||||
catch {
|
|
||||||
|
|
||||||
fetchError = error
|
return try self.fetch(fetchRequest.staticCast())
|
||||||
}
|
}
|
||||||
}
|
if let rawResult = fetchResults.first,
|
||||||
if let fetchResults = fetchResults {
|
|
||||||
|
|
||||||
if let rawResult = fetchResults.first as? NSDictionary,
|
|
||||||
let rawObject = rawResult[selectTerms.first!.keyPathString] as? U.QueryableNativeType {
|
let rawObject = rawResult[selectTerms.first!.keyPathString] as? U.QueryableNativeType {
|
||||||
|
|
||||||
return Select<O, U>.ReturnType.cs_fromQueryableNativeType(rawObject)
|
return Select<O, U>.ReturnType.cs_fromQueryableNativeType(rawObject)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
let coreStoreError = CoreStoreError(fetchError)
|
catch {
|
||||||
|
|
||||||
|
let coreStoreError = CoreStoreError(error)
|
||||||
Internals.log(
|
Internals.log(
|
||||||
coreStoreError,
|
coreStoreError,
|
||||||
"Failed executing fetch request."
|
"Failed executing query request."
|
||||||
)
|
)
|
||||||
throw coreStoreError
|
throw coreStoreError
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@nonobjc
|
@nonobjc
|
||||||
internal func queryValue<O>(
|
internal func queryValue<O>(
|
||||||
@@ -603,65 +569,53 @@ extension NSManagedObjectContext {
|
|||||||
fetchRequest: Internals.CoreStoreFetchRequest<NSDictionary>
|
fetchRequest: Internals.CoreStoreFetchRequest<NSDictionary>
|
||||||
) throws(CoreStoreError) -> Any? {
|
) throws(CoreStoreError) -> Any? {
|
||||||
|
|
||||||
var fetchResults: [Any]?
|
|
||||||
var fetchError: (any Swift.Error)?
|
|
||||||
self.performAndWait {
|
|
||||||
|
|
||||||
do {
|
do {
|
||||||
|
|
||||||
fetchResults = try self.fetch(fetchRequest.staticCast())
|
let fetchResults = try self.performAndWait {
|
||||||
}
|
|
||||||
catch {
|
|
||||||
|
|
||||||
fetchError = error
|
return try self.fetch(fetchRequest.staticCast())
|
||||||
}
|
}
|
||||||
}
|
if let rawResult = fetchResults.first,
|
||||||
if let fetchResults = fetchResults {
|
|
||||||
|
|
||||||
if let rawResult = fetchResults.first as? NSDictionary,
|
|
||||||
let rawObject = rawResult[selectTerms.first!.keyPathString] {
|
let rawObject = rawResult[selectTerms.first!.keyPathString] {
|
||||||
|
|
||||||
return rawObject
|
return rawObject
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
let coreStoreError = CoreStoreError(fetchError)
|
catch {
|
||||||
|
|
||||||
|
let coreStoreError = CoreStoreError(error)
|
||||||
Internals.log(
|
Internals.log(
|
||||||
coreStoreError,
|
coreStoreError,
|
||||||
"Failed executing fetch request."
|
"Failed executing query request."
|
||||||
)
|
)
|
||||||
throw coreStoreError
|
throw coreStoreError
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@nonobjc
|
@nonobjc
|
||||||
internal func queryAttributes(
|
internal func queryAttributes(
|
||||||
_ fetchRequest: Internals.CoreStoreFetchRequest<NSDictionary>
|
_ fetchRequest: Internals.CoreStoreFetchRequest<NSDictionary>
|
||||||
) throws(CoreStoreError) -> [[String: Any]] {
|
) throws(CoreStoreError) -> [[String: Any]] {
|
||||||
|
|
||||||
var fetchResults: [Any]?
|
|
||||||
var fetchError: (any Swift.Error)?
|
|
||||||
self.performAndWait {
|
|
||||||
|
|
||||||
do {
|
do {
|
||||||
|
|
||||||
fetchResults = try self.fetch(fetchRequest.staticCast())
|
let fetchResults = try self.performAndWait {
|
||||||
|
|
||||||
|
return try self.fetch(fetchRequest.staticCast())
|
||||||
|
}
|
||||||
|
return NSDictionary.cs_fromQueryResultsNativeType(fetchResults)
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
|
|
||||||
fetchError = error
|
let coreStoreError = CoreStoreError(error)
|
||||||
}
|
|
||||||
}
|
|
||||||
if let fetchResults = fetchResults {
|
|
||||||
|
|
||||||
return NSDictionary.cs_fromQueryResultsNativeType(fetchResults)
|
|
||||||
}
|
|
||||||
let coreStoreError = CoreStoreError(fetchError)
|
|
||||||
Internals.log(
|
Internals.log(
|
||||||
coreStoreError,
|
coreStoreError,
|
||||||
"Failed executing fetch request."
|
"Failed executing query request."
|
||||||
)
|
)
|
||||||
throw coreStoreError
|
throw coreStoreError
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// MARK: Deleting
|
// MARK: Deleting
|
||||||
@@ -671,36 +625,29 @@ extension NSManagedObjectContext {
|
|||||||
_ fetchRequest: Internals.CoreStoreFetchRequest<O>
|
_ fetchRequest: Internals.CoreStoreFetchRequest<O>
|
||||||
) throws(CoreStoreError) -> Int {
|
) throws(CoreStoreError) -> Int {
|
||||||
|
|
||||||
var numberOfDeletedObjects: Int?
|
|
||||||
var fetchError: (any Swift.Error)?
|
|
||||||
self.performAndWait {
|
|
||||||
|
|
||||||
Internals.autoreleasepool {
|
|
||||||
|
|
||||||
do {
|
do {
|
||||||
|
|
||||||
|
return try self.performAndWait {
|
||||||
|
|
||||||
|
return try Internals.autoreleasepool {
|
||||||
|
|
||||||
let fetchResults = try self.fetch(fetchRequest.staticCast())
|
let fetchResults = try self.fetch(fetchRequest.staticCast())
|
||||||
for object in fetchResults {
|
for object in fetchResults {
|
||||||
|
|
||||||
self.delete(object)
|
self.delete(object)
|
||||||
}
|
}
|
||||||
numberOfDeletedObjects = fetchResults.count
|
return fetchResults.count
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
|
|
||||||
fetchError = error
|
let coreStoreError = CoreStoreError(error)
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let numberOfDeletedObjects = numberOfDeletedObjects {
|
|
||||||
|
|
||||||
return numberOfDeletedObjects
|
|
||||||
}
|
|
||||||
let coreStoreError = CoreStoreError(fetchError)
|
|
||||||
Internals.log(
|
Internals.log(
|
||||||
coreStoreError,
|
coreStoreError,
|
||||||
"Failed executing delete request."
|
"Failed executing delete request."
|
||||||
)
|
)
|
||||||
throw coreStoreError
|
throw coreStoreError
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ extension NSManagedObjectContext {
|
|||||||
|
|
||||||
let context = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
|
let context = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
|
||||||
context.persistentStoreCoordinator = coordinator
|
context.persistentStoreCoordinator = coordinator
|
||||||
context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
|
context.mergePolicy = NSMergePolicy(merge: .mergeByPropertyObjectTrumpMergePolicyType)
|
||||||
context.undoManager = nil
|
context.undoManager = nil
|
||||||
context.setupForCoreStoreWithContextName("com.corestore.rootcontext")
|
context.setupForCoreStoreWithContextName("com.corestore.rootcontext")
|
||||||
|
|
||||||
@@ -76,16 +76,21 @@ extension NSManagedObjectContext {
|
|||||||
object: coordinator,
|
object: coordinator,
|
||||||
closure: { [weak context] (note) -> Void in
|
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>) {
|
if let updatedObjectIDs = (note.userInfo?[NSUpdatedObjectsKey] as? Set<NSManagedObjectID>) {
|
||||||
|
|
||||||
for objectID in updatedObjectIDs {
|
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)
|
let context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
|
||||||
context.parent = rootContext
|
context.parent = rootContext
|
||||||
context.mergePolicy = NSRollbackMergePolicy
|
context.mergePolicy = NSMergePolicy(merge: .rollbackMergePolicyType)
|
||||||
context.undoManager = nil
|
context.undoManager = nil
|
||||||
context.setupForCoreStoreWithContextName("com.corestore.maincontext")
|
context.setupForCoreStoreWithContextName("com.corestore.maincontext")
|
||||||
context.observerForDidSaveNotification = Internals.NotificationObserver(
|
context.observerForDidSaveNotification = Internals.NotificationObserver(
|
||||||
@@ -108,8 +113,10 @@ extension NSManagedObjectContext {
|
|||||||
object: rootContext,
|
object: rootContext,
|
||||||
closure: { [weak context] (note) -> Void in
|
closure: { [weak context] (note) -> Void in
|
||||||
|
|
||||||
guard let rootContext = note.object as? NSManagedObjectContext,
|
guard
|
||||||
let context = context else {
|
let rootContext = note.object as? NSManagedObjectContext,
|
||||||
|
let context = context
|
||||||
|
else {
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -117,7 +124,8 @@ extension NSManagedObjectContext {
|
|||||||
let saveMetadata = rootContext.saveMetadata
|
let saveMetadata = rootContext.saveMetadata
|
||||||
context.saveMetadata = 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>) {
|
if let updatedObjects = (note.userInfo?[NSUpdatedObjectsKey] as? Set<NSManagedObject>) {
|
||||||
|
|
||||||
@@ -147,9 +155,9 @@ extension NSManagedObjectContext {
|
|||||||
|
|
||||||
private struct PropertyKeys {
|
private struct PropertyKeys {
|
||||||
|
|
||||||
static var parentStack: Void?
|
static nonisolated(unsafe) var parentStack: Void?
|
||||||
static var observerForDidSaveNotification: Void?
|
static nonisolated(unsafe) var observerForDidSaveNotification: Void?
|
||||||
static var observerForDidImportUbiquitousContentChangesNotification: Void?
|
static nonisolated(unsafe) var observerForDidImportUbiquitousContentChangesNotification: Void?
|
||||||
}
|
}
|
||||||
|
|
||||||
@nonobjc
|
@nonobjc
|
||||||
|
|||||||
@@ -142,15 +142,14 @@ extension NSManagedObjectContext {
|
|||||||
@nonobjc
|
@nonobjc
|
||||||
internal func saveSynchronously(
|
internal func saveSynchronously(
|
||||||
waitForMerge: Bool,
|
waitForMerge: Bool,
|
||||||
sourceIdentifier: Any?
|
sourceIdentifier: (any Sendable)?
|
||||||
) -> (hasChanges: Bool, error: CoreStoreError?) {
|
) -> (hasChanges: Bool, error: CoreStoreError?) {
|
||||||
|
|
||||||
var result: (hasChanges: Bool, error: CoreStoreError?) = (false, nil)
|
return self.performAndWait {
|
||||||
self.performAndWait {
|
|
||||||
|
|
||||||
guard self.hasChanges else {
|
guard self.hasChanges else {
|
||||||
|
|
||||||
return
|
return (false, nil)
|
||||||
}
|
}
|
||||||
do {
|
do {
|
||||||
|
|
||||||
@@ -168,8 +167,7 @@ extension NSManagedObjectContext {
|
|||||||
saveError,
|
saveError,
|
||||||
"Failed to save \(Internals.typeName(NSManagedObjectContext.self))."
|
"Failed to save \(Internals.typeName(NSManagedObjectContext.self))."
|
||||||
)
|
)
|
||||||
result = (true, saveError)
|
return (true, saveError)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
if let parentContext = self.parent, self.shouldCascadeSavesToParent {
|
if let parentContext = self.parent, self.shouldCascadeSavesToParent {
|
||||||
|
|
||||||
@@ -177,20 +175,19 @@ extension NSManagedObjectContext {
|
|||||||
waitForMerge: waitForMerge,
|
waitForMerge: waitForMerge,
|
||||||
sourceIdentifier: sourceIdentifier
|
sourceIdentifier: sourceIdentifier
|
||||||
)
|
)
|
||||||
result = (true, error)
|
return (true, error)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
||||||
result = (true, nil)
|
return (true, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@nonobjc
|
@nonobjc
|
||||||
internal func saveAsynchronously(
|
internal func saveAsynchronously(
|
||||||
sourceIdentifier: Any?,
|
sourceIdentifier: (any Sendable)?,
|
||||||
completion: @escaping (_ hasChanges: Bool, _ error: CoreStoreError?) -> Void = { (_, _) in }
|
completion: @escaping @MainActor (_ hasChanges: Bool, _ error: CoreStoreError?) -> Void = { (_, _) in }
|
||||||
) {
|
) {
|
||||||
|
|
||||||
self.perform {
|
self.perform {
|
||||||
@@ -259,11 +256,11 @@ extension NSManagedObjectContext {
|
|||||||
// MARK: Internal
|
// MARK: Internal
|
||||||
|
|
||||||
internal let isSavingSynchronously: Bool
|
internal let isSavingSynchronously: Bool
|
||||||
internal let sourceIdentifier: Any?
|
internal let sourceIdentifier: (any Sendable)?
|
||||||
|
|
||||||
internal init(
|
internal init(
|
||||||
isSavingSynchronously: Bool,
|
isSavingSynchronously: Bool,
|
||||||
sourceIdentifier: Any?
|
sourceIdentifier: (any Sendable)?
|
||||||
) {
|
) {
|
||||||
|
|
||||||
self.isSavingSynchronously = isSavingSynchronously
|
self.isSavingSynchronously = isSavingSynchronously
|
||||||
@@ -276,9 +273,9 @@ extension NSManagedObjectContext {
|
|||||||
|
|
||||||
private struct PropertyKeys {
|
private struct PropertyKeys {
|
||||||
|
|
||||||
static var parentTransaction: Void?
|
static nonisolated(unsafe) var parentTransaction: Void?
|
||||||
static var saveMetadata: Void?
|
static nonisolated(unsafe) var saveMetadata: Void?
|
||||||
static var isTransactionContext: Void?
|
static nonisolated(unsafe) var isTransactionContext: Void?
|
||||||
static var isDataStackContext: Void?
|
static nonisolated(unsafe) var isDataStackContext: Void?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ extension NSPersistentStore {
|
|||||||
|
|
||||||
private struct PropertyKeys {
|
private struct PropertyKeys {
|
||||||
|
|
||||||
static var storageInterface: Void?
|
static nonisolated(unsafe) var storageInterface: Void?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ extension NSPersistentStoreCoordinator {
|
|||||||
|
|
||||||
@nonobjc
|
@nonobjc
|
||||||
internal func performAsynchronously(
|
internal func performAsynchronously(
|
||||||
_ closure: @escaping () -> Void
|
_ closure: @escaping @Sendable () -> Void
|
||||||
) {
|
) {
|
||||||
|
|
||||||
self.perform(closure)
|
self.perform(closure)
|
||||||
@@ -41,41 +41,29 @@ extension NSPersistentStoreCoordinator {
|
|||||||
|
|
||||||
@nonobjc
|
@nonobjc
|
||||||
internal func performSynchronously<T>(
|
internal func performSynchronously<T>(
|
||||||
_ closure: @escaping () -> T
|
_ closure: @Sendable () -> T
|
||||||
) -> T {
|
) -> T {
|
||||||
|
|
||||||
var result: T?
|
return self.performAndWait(closure)
|
||||||
self.performAndWait {
|
|
||||||
|
|
||||||
result = closure()
|
|
||||||
}
|
|
||||||
return result!
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@nonobjc
|
@nonobjc
|
||||||
internal func performSynchronously<T>(
|
internal func performSynchronously<T>(
|
||||||
_ closure: @escaping () throws(any Swift.Error) -> T
|
_ closure: @Sendable () throws(any Swift.Error) -> T
|
||||||
) throws(CoreStoreError) -> T {
|
) throws(CoreStoreError) -> T {
|
||||||
|
|
||||||
var closureError: (any Swift.Error)?
|
|
||||||
var result: T?
|
|
||||||
self.performAndWait {
|
|
||||||
|
|
||||||
do {
|
do {
|
||||||
|
|
||||||
result = try closure()
|
return try self.performAndWait {
|
||||||
|
|
||||||
|
return try closure()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
|
|
||||||
closureError = error
|
throw CoreStoreError(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let closureError = closureError {
|
|
||||||
|
|
||||||
throw CoreStoreError(closureError)
|
|
||||||
}
|
|
||||||
return result!
|
|
||||||
}
|
|
||||||
|
|
||||||
@nonobjc
|
@nonobjc
|
||||||
internal func addPersistentStoreSynchronously(
|
internal func addPersistentStoreSynchronously(
|
||||||
@@ -85,6 +73,7 @@ extension NSPersistentStoreCoordinator {
|
|||||||
options: [AnyHashable: Any]?
|
options: [AnyHashable: Any]?
|
||||||
) throws(CoreStoreError) -> NSPersistentStore {
|
) throws(CoreStoreError) -> NSPersistentStore {
|
||||||
|
|
||||||
|
nonisolated(unsafe) let options = options
|
||||||
return try self.performSynchronously {
|
return try self.performSynchronously {
|
||||||
|
|
||||||
return try self.addPersistentStore(
|
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.
|
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.
|
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
|
- 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.unregisterObserver(observer)
|
||||||
self.registerObserver(
|
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]) ?? [:]
|
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,
|
_ observer: U,
|
||||||
willChangeObject: @escaping (
|
willChangeObject: @escaping @Sendable (
|
||||||
_ observer: U,
|
_ observer: U,
|
||||||
_ monitor: ObjectMonitor<O>,
|
_ monitor: ObjectMonitor<O>,
|
||||||
_ object: O
|
_ object: O
|
||||||
) -> Void,
|
) -> Void,
|
||||||
didDeleteObject: @escaping (
|
didDeleteObject: @escaping @Sendable (
|
||||||
_ observer: U,
|
_ observer: U,
|
||||||
_ monitor: ObjectMonitor<O>,
|
_ monitor: ObjectMonitor<O>,
|
||||||
_ object: O
|
_ object: O
|
||||||
) -> Void,
|
) -> Void,
|
||||||
didUpdateObject: @escaping (
|
didUpdateObject: @escaping @Sendable (
|
||||||
_ observer: U,
|
_ observer: U,
|
||||||
_ monitor: ObjectMonitor<O>,
|
_ monitor: ObjectMonitor<O>,
|
||||||
_ object: O,
|
_ object: O,
|
||||||
@@ -361,7 +361,7 @@ public final class ObjectMonitor<O: DynamicObject>: Hashable, ObjectRepresentati
|
|||||||
_ notificationKey: UnsafeRawPointer,
|
_ notificationKey: UnsafeRawPointer,
|
||||||
name: Notification.Name,
|
name: Notification.Name,
|
||||||
toObserver observer: AnyObject,
|
toObserver observer: AnyObject,
|
||||||
callback: @escaping (_ monitor: ObjectMonitor<O>) -> Void
|
callback: @escaping @Sendable (_ monitor: ObjectMonitor<O>) -> Void
|
||||||
) {
|
) {
|
||||||
|
|
||||||
Internals.setAssociatedRetainedObject(
|
Internals.setAssociatedRetainedObject(
|
||||||
@@ -386,7 +386,7 @@ public final class ObjectMonitor<O: DynamicObject>: Hashable, ObjectRepresentati
|
|||||||
_ notificationKey: UnsafeRawPointer,
|
_ notificationKey: UnsafeRawPointer,
|
||||||
name: Notification.Name,
|
name: Notification.Name,
|
||||||
toObserver observer: AnyObject,
|
toObserver observer: AnyObject,
|
||||||
callback: @escaping (_ monitor: ObjectMonitor<O>, _ object: O) -> Void
|
callback: @escaping @Sendable (_ monitor: ObjectMonitor<O>, _ object: O) -> Void
|
||||||
) {
|
) {
|
||||||
|
|
||||||
Internals.setAssociatedRetainedObject(
|
Internals.setAssociatedRetainedObject(
|
||||||
|
|||||||
@@ -36,12 +36,12 @@ import CoreData
|
|||||||
monitor.addObserver(self)
|
monitor.addObserver(self)
|
||||||
```
|
```
|
||||||
*/
|
*/
|
||||||
public protocol ObjectObserver: AnyObject {
|
public protocol ObjectObserver: AnyObject & Sendable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
The `DynamicObject` type for the observed object
|
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)
|
Handles processing just before a change to the observed `object` occurs. (Optional)
|
||||||
|
|||||||
@@ -90,7 +90,8 @@ extension ObjectPublisher {
|
|||||||
|
|
||||||
// MARK: - ObjectSnapshotSubscription
|
// 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
|
// MARK: FilePrivate
|
||||||
|
|
||||||
@@ -114,6 +115,8 @@ extension ObjectPublisher {
|
|||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
Internals.mainActorImmediate { [self] in
|
||||||
|
|
||||||
self.publisher.addObserver(
|
self.publisher.addObserver(
|
||||||
self,
|
self,
|
||||||
notifyInitial: self.emitInitialValue,
|
notifyInitial: self.emitInitialValue,
|
||||||
@@ -130,6 +133,7 @@ extension ObjectPublisher {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// MARK: Cancellable
|
// MARK: Cancellable
|
||||||
@@ -138,17 +142,10 @@ extension ObjectPublisher {
|
|||||||
|
|
||||||
self.subscriber = nil
|
self.subscriber = nil
|
||||||
|
|
||||||
if Thread.isMainThread {
|
Internals.mainActorImmediate {
|
||||||
|
|
||||||
self.publisher.removeObserver(self)
|
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 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
|
- parameter callback: the closure to execute when changes occur
|
||||||
*/
|
*/
|
||||||
|
@MainActor
|
||||||
public func addObserver<T: AnyObject>(
|
public func addObserver<T: AnyObject>(
|
||||||
_ observer: T,
|
_ observer: T,
|
||||||
notifyInitial: Bool = false,
|
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 initialSourceIdentifier: an optional value that identifies the initial callback invocation if `notifyInitial` is `true`.
|
||||||
- parameter callback: the closure to execute when changes occur
|
- parameter callback: the closure to execute when changes occur
|
||||||
*/
|
*/
|
||||||
|
@MainActor
|
||||||
public func addObserver<T: AnyObject>(
|
public func addObserver<T: AnyObject>(
|
||||||
_ observer: T,
|
_ observer: T,
|
||||||
notifyInitial: Bool = false,
|
notifyInitial: Bool = false,
|
||||||
@@ -145,6 +147,7 @@ public final class ObjectPublisher<O: DynamicObject>: ObjectRepresentation, Hash
|
|||||||
|
|
||||||
- parameter observer: the object whose notifications will be unregistered
|
- parameter observer: the object whose notifications will be unregistered
|
||||||
*/
|
*/
|
||||||
|
@MainActor
|
||||||
public func removeObserver<T: AnyObject>(_ observer: T) {
|
public func removeObserver<T: AnyObject>(_ observer: T) {
|
||||||
|
|
||||||
Internals.assert(
|
Internals.assert(
|
||||||
|
|||||||
+30
-10
@@ -23,9 +23,9 @@
|
|||||||
// SOFTWARE.
|
// SOFTWARE.
|
||||||
//
|
//
|
||||||
|
|
||||||
#if canImport(Combine) && canImport(SwiftUI)
|
#if canImport(Observation) && canImport(SwiftUI)
|
||||||
|
|
||||||
import Combine
|
import Observation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
|
|
||||||
@@ -62,19 +62,22 @@ public struct ObjectState<O: DynamicObject>: DynamicProperty {
|
|||||||
|
|
||||||
- parameter objectPublisher: The `ObjectPublisher` that the `ObjectState` will observe changes for
|
- parameter objectPublisher: The `ObjectPublisher` that the `ObjectState` will observe changes for
|
||||||
*/
|
*/
|
||||||
|
@MainActor
|
||||||
public init(_ objectPublisher: ObjectPublisher<O>?) {
|
public init(_ objectPublisher: ObjectPublisher<O>?) {
|
||||||
|
|
||||||
self.observer = .init(objectPublisher: objectPublisher)
|
self._observer = .init(wrappedValue: .init(objectPublisher: objectPublisher))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// MARK: @propertyWrapper
|
// MARK: @propertyWrapper
|
||||||
|
|
||||||
|
@MainActor
|
||||||
public var wrappedValue: ObjectSnapshot<O>? {
|
public var wrappedValue: ObjectSnapshot<O>? {
|
||||||
|
|
||||||
return self.observer.item
|
return self.observer.item
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
public var projectedValue: ObjectPublisher<O>? {
|
public var projectedValue: ObjectPublisher<O>? {
|
||||||
|
|
||||||
return self.observer.objectPublisher
|
return self.observer.objectPublisher
|
||||||
@@ -91,19 +94,36 @@ public struct ObjectState<O: DynamicObject>: DynamicProperty {
|
|||||||
|
|
||||||
// MARK: Private
|
// MARK: Private
|
||||||
|
|
||||||
@ObservedObject
|
@State
|
||||||
private var observer: Observer
|
private var observer: Observer
|
||||||
|
|
||||||
|
|
||||||
// MARK: - Observer
|
// MARK: - Observer
|
||||||
|
|
||||||
private final class Observer: ObservableObject {
|
@MainActor
|
||||||
|
private final class Observer: Observation.Observable {
|
||||||
|
|
||||||
@Published
|
private let registrar = ObservationRegistrar()
|
||||||
var item: ObjectSnapshot<O>?
|
private var _item: ObjectSnapshot<O>?
|
||||||
|
|
||||||
let objectPublisher: ObjectPublisher<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>?) {
|
init(objectPublisher: ObjectPublisher<O>?) {
|
||||||
|
|
||||||
guard
|
guard
|
||||||
@@ -112,12 +132,12 @@ public struct ObjectState<O: DynamicObject>: DynamicProperty {
|
|||||||
else {
|
else {
|
||||||
|
|
||||||
self.objectPublisher = nil
|
self.objectPublisher = nil
|
||||||
self.item = nil
|
self._item = nil
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
self.objectPublisher = objectPublisher
|
self.objectPublisher = objectPublisher
|
||||||
self.item = objectPublisher.snapshot
|
self._item = objectPublisher.snapshot
|
||||||
|
|
||||||
objectPublisher.addObserver(self) { [weak self] (objectPublisher) in
|
objectPublisher.addObserver(self) { [weak self] (objectPublisher) in
|
||||||
|
|
||||||
@@ -129,7 +149,7 @@ public struct ObjectState<O: DynamicObject>: DynamicProperty {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
isolated deinit {
|
||||||
|
|
||||||
self.objectPublisher?.removeObserver(self)
|
self.objectPublisher?.removeObserver(self)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,8 @@ extension Progress {
|
|||||||
- parameter closure: the closure to execute on progress change
|
- parameter closure: the closure to execute on progress change
|
||||||
*/
|
*/
|
||||||
@nonobjc
|
@nonobjc
|
||||||
public func setProgressHandler(_ closure: ((_ progress: Progress) -> Void)?) {
|
@MainActor
|
||||||
|
public func setProgressHandler(_ closure: (@MainActor (_ progress: Progress) -> Void)?) {
|
||||||
|
|
||||||
self.progressObserver.progressHandler = closure
|
self.progressObserver.progressHandler = closure
|
||||||
}
|
}
|
||||||
@@ -46,15 +47,19 @@ extension Progress {
|
|||||||
|
|
||||||
private struct PropertyKeys {
|
private struct PropertyKeys {
|
||||||
|
|
||||||
static var progressObserver: Void?
|
static nonisolated(unsafe) var progressObserver: Void?
|
||||||
}
|
}
|
||||||
|
|
||||||
@nonobjc
|
@nonobjc
|
||||||
|
@MainActor
|
||||||
private var progressObserver: ProgressObserver {
|
private var progressObserver: ProgressObserver {
|
||||||
|
|
||||||
get {
|
get {
|
||||||
|
|
||||||
let object: ProgressObserver? = Internals.getAssociatedObjectForKey(&PropertyKeys.progressObserver, inObject: self)
|
let object: ProgressObserver? = Internals.getAssociatedObjectForKey(
|
||||||
|
&PropertyKeys.progressObserver,
|
||||||
|
inObject: self
|
||||||
|
)
|
||||||
if let observer = object {
|
if let observer = object {
|
||||||
|
|
||||||
return observer
|
return observer
|
||||||
@@ -76,11 +81,12 @@ extension Progress {
|
|||||||
// MARK: - ProgressObserver
|
// MARK: - ProgressObserver
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
private final class ProgressObserver: NSObject {
|
private final class ProgressObserver: NSObject, @unchecked Sendable {
|
||||||
|
|
||||||
private unowned let progress: Progress
|
private unowned let progress: Progress
|
||||||
|
|
||||||
fileprivate var progressHandler: ((_ progress: Progress) -> Void)? {
|
@MainActor
|
||||||
|
fileprivate var progressHandler: (@MainActor (_ progress: Progress) -> Void)? {
|
||||||
|
|
||||||
didSet {
|
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.
|
- 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.
|
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"]]
|
[NSSQLitePragmasOption: ["journal_mode": "WAL"]]
|
||||||
```
|
```
|
||||||
*/
|
*/
|
||||||
public let storeOptions: [AnyHashable: Any]? = [
|
public var storeOptions: [AnyHashable: Any]? {
|
||||||
|
|
||||||
|
return [
|
||||||
NSSQLitePragmasOption: ["journal_mode": "WAL"],
|
NSSQLitePragmasOption: ["journal_mode": "WAL"],
|
||||||
NSBinaryStoreInsecureDecodingCompatibilityOption: true
|
NSBinaryStoreInsecureDecodingCompatibilityOption: true
|
||||||
]
|
]
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Do not call directly. Used by the `DataStack` internally.
|
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.
|
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.
|
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.
|
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
|
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(
|
internal init(
|
||||||
mainContext: NSManagedObjectContext,
|
mainContext: NSManagedObjectContext,
|
||||||
queue: DispatchQueue,
|
queue: DispatchQueue,
|
||||||
sourceIdentifier: Any?
|
sourceIdentifier: (any Sendable)?
|
||||||
) {
|
) {
|
||||||
|
|
||||||
super.init(
|
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.
|
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||||
*/
|
*/
|
||||||
public func monitorList<O>(
|
public func monitorList<O>(
|
||||||
createAsynchronously: @escaping (ListMonitor<O>) -> Void,
|
createAsynchronously: @escaping @Sendable (ListMonitor<O>) -> Void,
|
||||||
_ from: From<O>,
|
_ from: From<O>,
|
||||||
_ fetchClauses: FetchClause...
|
_ 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.
|
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||||
*/
|
*/
|
||||||
public func monitorList<O>(
|
public func monitorList<O>(
|
||||||
createAsynchronously: @escaping (ListMonitor<O>) -> Void,
|
createAsynchronously: @escaping @Sendable (ListMonitor<O>) -> Void,
|
||||||
_ from: From<O>,
|
_ from: From<O>,
|
||||||
_ fetchClauses: [FetchClause]
|
_ fetchClauses: [FetchClause]
|
||||||
) {
|
) {
|
||||||
@@ -174,7 +174,7 @@ extension UnsafeDataTransaction {
|
|||||||
- parameter clauseChain: a `FetchChainableBuilderType` built from a chain of clauses
|
- parameter clauseChain: a `FetchChainableBuilderType` built from a chain of clauses
|
||||||
*/
|
*/
|
||||||
public func monitorList<B: FetchChainableBuilderType>(
|
public func monitorList<B: FetchChainableBuilderType>(
|
||||||
createAsynchronously: @escaping (ListMonitor<B.ObjectType>) -> Void,
|
createAsynchronously: @escaping @Sendable (ListMonitor<B.ObjectType>) -> Void,
|
||||||
_ clauseChain: B
|
_ 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.
|
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||||
*/
|
*/
|
||||||
public func monitorSectionedList<O>(
|
public func monitorSectionedList<O>(
|
||||||
createAsynchronously: @escaping (ListMonitor<O>) -> Void,
|
createAsynchronously: @escaping @Sendable (ListMonitor<O>) -> Void,
|
||||||
_ from: From<O>,
|
_ from: From<O>,
|
||||||
_ sectionBy: SectionBy<O>,
|
_ sectionBy: SectionBy<O>,
|
||||||
_ fetchClauses: FetchClause...
|
_ 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.
|
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||||
*/
|
*/
|
||||||
public func monitorSectionedList<O>(
|
public func monitorSectionedList<O>(
|
||||||
createAsynchronously: @escaping (ListMonitor<O>) -> Void,
|
createAsynchronously: @escaping @Sendable (ListMonitor<O>) -> Void,
|
||||||
_ from: From<O>,
|
_ from: From<O>,
|
||||||
_ sectionBy: SectionBy<O>,
|
_ sectionBy: SectionBy<O>,
|
||||||
_ fetchClauses: [FetchClause]
|
_ fetchClauses: [FetchClause]
|
||||||
@@ -323,7 +323,7 @@ extension UnsafeDataTransaction {
|
|||||||
- parameter clauseChain: a `SectionMonitorBuilderType` built from a chain of clauses
|
- parameter clauseChain: a `SectionMonitorBuilderType` built from a chain of clauses
|
||||||
*/
|
*/
|
||||||
public func monitorSectionedList<B: SectionMonitorBuilderType>(
|
public func monitorSectionedList<B: SectionMonitorBuilderType>(
|
||||||
createAsynchronously: @escaping (ListMonitor<B.ObjectType>) -> Void,
|
createAsynchronously: @escaping @Sendable (ListMonitor<B.ObjectType>) -> Void,
|
||||||
_ clauseChain: B
|
_ 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.
|
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: -
|
// 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.
|
- 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(
|
public func commit(
|
||||||
_ completion: @escaping (_ error: CoreStoreError?) -> Void
|
_ completion: @escaping @Sendable (_ error: CoreStoreError?) -> Void
|
||||||
) {
|
) {
|
||||||
|
|
||||||
self.context.saveAsynchronously(
|
self.context.saveAsynchronously(
|
||||||
@@ -142,7 +142,7 @@ public final class UnsafeDataTransaction: BaseDataTransaction {
|
|||||||
*/
|
*/
|
||||||
public func beginUnsafe(
|
public func beginUnsafe(
|
||||||
supportsUndo: Bool = false,
|
supportsUndo: Bool = false,
|
||||||
sourceIdentifier: Any? = nil
|
sourceIdentifier: (any Sendable)? = nil
|
||||||
) -> UnsafeDataTransaction {
|
) -> UnsafeDataTransaction {
|
||||||
|
|
||||||
return UnsafeDataTransaction(
|
return UnsafeDataTransaction(
|
||||||
@@ -160,7 +160,7 @@ public final class UnsafeDataTransaction: BaseDataTransaction {
|
|||||||
mainContext: NSManagedObjectContext,
|
mainContext: NSManagedObjectContext,
|
||||||
queue: DispatchQueue,
|
queue: DispatchQueue,
|
||||||
supportsUndo: Bool,
|
supportsUndo: Bool,
|
||||||
sourceIdentifier: Any?
|
sourceIdentifier: (any Sendable)?
|
||||||
) {
|
) {
|
||||||
|
|
||||||
super.init(
|
super.init(
|
||||||
|
|||||||
Reference in New Issue
Block a user