new auto-commit transaction methods

This commit is contained in:
John Estropia
2017-03-31 19:44:18 +09:00
parent d72d1afe8b
commit 97d7a276fe
39 changed files with 3739 additions and 3232 deletions

View File

@@ -398,10 +398,6 @@
B59FA0B11CCBACA7007C9BCA /* ICloudStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B59FA0AD1CCBAC95007C9BCA /* ICloudStore.swift */; };
B59FA0B21CCBACA8007C9BCA /* ICloudStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B59FA0AD1CCBAC95007C9BCA /* ICloudStore.swift */; };
B5A261211B64BFDB006EB6D3 /* MigrationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A261201B64BFDB006EB6D3 /* MigrationType.swift */; };
B5A27F861E857C5300203C3E /* TransactionResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A27F851E857C5300203C3E /* TransactionResult.swift */; };
B5A27F871E857C5300203C3E /* TransactionResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A27F851E857C5300203C3E /* TransactionResult.swift */; };
B5A27F881E857C5300203C3E /* TransactionResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A27F851E857C5300203C3E /* TransactionResult.swift */; };
B5A27F891E857C5300203C3E /* TransactionResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A27F851E857C5300203C3E /* TransactionResult.swift */; };
B5A5F2661CAEC50F004AB9AF /* CSSelect.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A5F2651CAEC50F004AB9AF /* CSSelect.swift */; };
B5A5F2681CAEC50F004AB9AF /* CSSelect.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A5F2651CAEC50F004AB9AF /* CSSelect.swift */; };
B5A5F2691CAEC50F004AB9AF /* CSSelect.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A5F2651CAEC50F004AB9AF /* CSSelect.swift */; };
@@ -689,7 +685,6 @@
B59AFF401C6593E400C0ABE2 /* NSPersistentStoreCoordinator+Setup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSPersistentStoreCoordinator+Setup.swift"; sourceTree = "<group>"; };
B59FA0AD1CCBAC95007C9BCA /* ICloudStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ICloudStore.swift; sourceTree = "<group>"; };
B5A261201B64BFDB006EB6D3 /* MigrationType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MigrationType.swift; sourceTree = "<group>"; };
B5A27F851E857C5300203C3E /* TransactionResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionResult.swift; sourceTree = "<group>"; };
B5A5F2651CAEC50F004AB9AF /* CSSelect.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSSelect.swift; sourceTree = "<group>"; };
B5AD60CD1C90141E00F2B2E8 /* Package.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = SOURCE_ROOT; };
B5AEFAB41C9962AE00AD137F /* CoreStoreBridge.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreStoreBridge.swift; sourceTree = "<group>"; };
@@ -1165,7 +1160,6 @@
B5E84EEE1AFF846E0064E85B /* CoreStore+Transaction.swift */,
B50392F81C478FF3009900CA /* NSManagedObject+Transaction.swift */,
B5E84EF21AFF846E0064E85B /* SaveResult.swift */,
B5A27F851E857C5300203C3E /* TransactionResult.swift */,
);
path = Transactions;
sourceTree = "<group>";
@@ -1609,7 +1603,6 @@
B549F6731E56A92800FBAB2D /* CoreDataNativeType.swift in Sources */,
B509C7F41E54511B0061C547 /* ImportableAttributeType.swift in Sources */,
B5E84F0E1AFF847B0064E85B /* Tweak.swift in Sources */,
B5A27F861E857C5300203C3E /* TransactionResult.swift in Sources */,
B5E1B5931CAA0C15007FD580 /* CSObjectMonitor.swift in Sources */,
B5ECDC291CA81CC700C7F112 /* CSDataStack+Transaction.swift in Sources */,
B5E84F121AFF847B0064E85B /* OrderBy.swift in Sources */,
@@ -1766,7 +1759,6 @@
B549F6741E56A92800FBAB2D /* CoreDataNativeType.swift in Sources */,
B509C7F51E54511B0061C547 /* ImportableAttributeType.swift in Sources */,
82BA18B31C4BBD3900A0916E /* ImportableUniqueObject.swift in Sources */,
B5A27F871E857C5300203C3E /* TransactionResult.swift in Sources */,
B5E1B5951CAA0C15007FD580 /* CSObjectMonitor.swift in Sources */,
B5ECDC2B1CA81CC700C7F112 /* CSDataStack+Transaction.swift in Sources */,
82BA18A11C4BBD1D00A0916E /* CoreStore.swift in Sources */,
@@ -1923,7 +1915,6 @@
B549F6761E56A92800FBAB2D /* CoreDataNativeType.swift in Sources */,
B509C7F71E54511B0061C547 /* ImportableAttributeType.swift in Sources */,
B5220E1F1D130810009BC71E /* CSListObserver.swift in Sources */,
B5A27F891E857C5300203C3E /* TransactionResult.swift in Sources */,
B52DD1941BE1F92500949AFE /* CoreStore.swift in Sources */,
B52DD1A61BE1F92F00949AFE /* BaseDataTransaction+Importing.swift in Sources */,
B5220E1D1D13080A009BC71E /* CSDataStack+Observing.swift in Sources */,
@@ -2080,7 +2071,6 @@
B549F6751E56A92800FBAB2D /* CoreDataNativeType.swift in Sources */,
B509C7F61E54511B0061C547 /* ImportableAttributeType.swift in Sources */,
B5E1B5961CAA0C15007FD580 /* CSObjectMonitor.swift in Sources */,
B5A27F881E857C5300203C3E /* TransactionResult.swift in Sources */,
B5ECDC2C1CA81CC700C7F112 /* CSDataStack+Transaction.swift in Sources */,
B56321911BD65216006C9394 /* BaseDataTransaction+Importing.swift in Sources */,
B546F95A1C99B17400D5AC55 /* CSCoreStore+Setup.swift in Sources */,

View File

@@ -30,47 +30,48 @@ class BaseTestDataTestCase: BaseTestCase {
@nonobjc
func prepareTestDataForStack(_ stack: DataStack, configurations: [String?] = [nil]) {
stack.beginSynchronous { (transaction) in
for (configurationIndex, configuration) in configurations.enumerated() {
try! stack.perform(
synchronous: { (transaction) in
let configurationOrdinal = configurationIndex + 1
if configuration == nil || configuration == "Config1" {
for (configurationIndex, configuration) in configurations.enumerated() {
for idIndex in 1 ... 5 {
let configurationOrdinal = configurationIndex + 1
if configuration == nil || configuration == "Config1" {
let object = transaction.create(Into<TestEntity1>(configuration))
object.testEntityID = NSNumber(value: (configurationOrdinal * 100) + idIndex)
object.testNumber = NSNumber(value: idIndex)
object.testDate = self.dateFormatter.date(from: "2000-\(configurationOrdinal)-\(idIndex)T00:00:00Z")
object.testBoolean = NSNumber(value: (idIndex % 2) == 1)
object.testDecimal = NSDecimalNumber(string: "\(idIndex)")
let string = "\(configuration ?? "nil"):TestEntity1:\(idIndex)"
object.testString = string
object.testData = (string as NSString).data(using: String.Encoding.utf8.rawValue)
for idIndex in 1 ... 5 {
let object = transaction.create(Into<TestEntity1>(configuration))
object.testEntityID = NSNumber(value: (configurationOrdinal * 100) + idIndex)
object.testNumber = NSNumber(value: idIndex)
object.testDate = self.dateFormatter.date(from: "2000-\(configurationOrdinal)-\(idIndex)T00:00:00Z")
object.testBoolean = NSNumber(value: (idIndex % 2) == 1)
object.testDecimal = NSDecimalNumber(string: "\(idIndex)")
let string = "\(configuration ?? "nil"):TestEntity1:\(idIndex)"
object.testString = string
object.testData = (string as NSString).data(using: String.Encoding.utf8.rawValue)
}
}
}
if configuration == nil || configuration == "Config2" {
for idIndex in 1 ... 5 {
if configuration == nil || configuration == "Config2" {
let object = transaction.create(Into<TestEntity2>(configuration))
object.testEntityID = NSNumber(value: (configurationOrdinal * 200) + idIndex)
object.testNumber = NSNumber(value: idIndex)
object.testDate = self.dateFormatter.date(from: "2000-\(configurationOrdinal)-\(idIndex)T00:00:00Z")
object.testBoolean = NSNumber(value: (idIndex % 2) == 1)
object.testDecimal = NSDecimalNumber(string: "\(idIndex)")
let string = "\(configuration ?? "nil"):TestEntity2:\(idIndex)"
object.testString = string
object.testData = (string as NSString).data(using: String.Encoding.utf8.rawValue)
for idIndex in 1 ... 5 {
let object = transaction.create(Into<TestEntity2>(configuration))
object.testEntityID = NSNumber(value: (configurationOrdinal * 200) + idIndex)
object.testNumber = NSNumber(value: idIndex)
object.testDate = self.dateFormatter.date(from: "2000-\(configurationOrdinal)-\(idIndex)T00:00:00Z")
object.testBoolean = NSNumber(value: (idIndex % 2) == 1)
object.testDecimal = NSDecimalNumber(string: "\(idIndex)")
let string = "\(configuration ?? "nil"):TestEntity2:\(idIndex)"
object.testString = string
object.testData = (string as NSString).data(using: String.Encoding.utf8.rawValue)
}
}
}
}
_ = transaction.commitAndWait()
}
)
}
}

View File

@@ -212,6 +212,9 @@
CSUnsafeDataTransaction *transaction = [CSCoreStore beginUnsafe];
XCTAssertNotNil(transaction);
XCTAssert([transaction isKindOfClass:[CSUnsafeDataTransaction class]]);
NSError *error;
XCTAssertTrue([transaction commitAndWaitWithError:&error]);
XCTAssertNil(error);
}
{
XCTestExpectation *expectation = [self expectationWithDescription:@"sync"];
@@ -219,6 +222,9 @@
XCTAssertNotNil(transaction);
XCTAssert([transaction isKindOfClass:[CSSynchronousDataTransaction class]]);
NSError *error;
XCTAssertTrue([transaction commitAndWaitWithError:&error]);
XCTAssertNil(error);
[expectation fulfill];
}];
}
@@ -228,7 +234,15 @@
XCTAssertNotNil(transaction);
XCTAssert([transaction isKindOfClass:[CSAsynchronousDataTransaction class]]);
[expectation fulfill];
[transaction
commitWithSuccess:^{
[expectation fulfill];
}
failure:^(CSError *error){
XCTFail();
}];
}];
}
[self waitForExpectationsWithTimeout:10 handler:nil];

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -149,29 +149,29 @@ class ListObserverTests: BaseTestDataTestCase {
}
)
let saveExpectation = self.expectation(description: "save")
stack.beginAsynchronous { (transaction) in
let object = transaction.create(Into<TestEntity1>())
object.testBoolean = NSNumber(value: true)
object.testNumber = NSNumber(value: 1)
object.testDecimal = NSDecimalNumber(string: "1")
object.testString = "nil:TestEntity1:1"
object.testData = ("nil:TestEntity1:1" as NSString).data(using: String.Encoding.utf8.rawValue)!
object.testDate = self.dateFormatter.date(from: "2000-01-01T00:00:00Z")!
transaction.commit { (result) in
stack.perform(
asynchronous: { (transaction) -> Bool in
switch result {
case .success(let hasChanges):
XCTAssertTrue(hasChanges)
saveExpectation.fulfill()
case .failure:
XCTFail()
}
let object = transaction.create(Into<TestEntity1>())
object.testBoolean = NSNumber(value: true)
object.testNumber = NSNumber(value: 1)
object.testDecimal = NSDecimalNumber(string: "1")
object.testString = "nil:TestEntity1:1"
object.testData = ("nil:TestEntity1:1" as NSString).data(using: String.Encoding.utf8.rawValue)!
object.testDate = self.dateFormatter.date(from: "2000-01-01T00:00:00Z")!
return transaction.hasChanges
},
success: { (hasChanges) in
XCTAssertTrue(hasChanges)
saveExpectation.fulfill()
},
failure: { _ in
XCTFail()
}
}
)
self.waitAndCheckExpectations()
}
}
@@ -283,49 +283,49 @@ class ListObserverTests: BaseTestDataTestCase {
}
)
let saveExpectation = self.expectation(description: "save")
stack.beginAsynchronous { (transaction) in
if let object = transaction.fetchOne(
From<TestEntity1>(),
Where(#keyPath(TestEntity1.testEntityID), isEqualTo: 101)) {
stack.perform(
asynchronous: { (transaction) -> Bool in
object.testNumber = NSNumber(value: 11)
object.testDecimal = NSDecimalNumber(string: "11")
object.testString = "nil:TestEntity1:11"
object.testData = ("nil:TestEntity1:11" as NSString).data(using: String.Encoding.utf8.rawValue)!
object.testDate = self.dateFormatter.date(from: "2000-01-11T00:00:00Z")!
}
else {
XCTFail()
}
if let object = transaction.fetchOne(
From<TestEntity1>(),
Where(#keyPath(TestEntity1.testEntityID), isEqualTo: 102)) {
object.testNumber = NSNumber(value: 22)
object.testDecimal = NSDecimalNumber(string: "22")
object.testString = "nil:TestEntity1:22"
object.testData = ("nil:TestEntity1:22" as NSString).data(using: String.Encoding.utf8.rawValue)!
object.testDate = self.dateFormatter.date(from: "2000-01-22T00:00:00Z")!
}
else {
XCTFail()
}
transaction.commit { (result) in
switch result {
if let object = transaction.fetchOne(
From<TestEntity1>(),
Where(#keyPath(TestEntity1.testEntityID), isEqualTo: 101)) {
case .success(let hasChanges):
XCTAssertTrue(hasChanges)
saveExpectation.fulfill()
object.testNumber = NSNumber(value: 11)
object.testDecimal = NSDecimalNumber(string: "11")
object.testString = "nil:TestEntity1:11"
object.testData = ("nil:TestEntity1:11" as NSString).data(using: String.Encoding.utf8.rawValue)!
object.testDate = self.dateFormatter.date(from: "2000-01-11T00:00:00Z")!
}
else {
case .failure:
XCTFail()
}
if let object = transaction.fetchOne(
From<TestEntity1>(),
Where(#keyPath(TestEntity1.testEntityID), isEqualTo: 102)) {
object.testNumber = NSNumber(value: 22)
object.testDecimal = NSDecimalNumber(string: "22")
object.testString = "nil:TestEntity1:22"
object.testData = ("nil:TestEntity1:22" as NSString).data(using: String.Encoding.utf8.rawValue)!
object.testDate = self.dateFormatter.date(from: "2000-01-22T00:00:00Z")!
}
else {
XCTFail()
}
return transaction.hasChanges
},
success: { (hasChanges) in
XCTAssertTrue(hasChanges)
saveExpectation.fulfill()
},
failure: { _ in
XCTFail()
}
}
)
self.waitAndCheckExpectations()
}
}
@@ -409,31 +409,31 @@ class ListObserverTests: BaseTestDataTestCase {
}
)
let saveExpectation = self.expectation(description: "save")
stack.beginAsynchronous { (transaction) in
if let object = transaction.fetchOne(
From<TestEntity1>(),
Where(#keyPath(TestEntity1.testEntityID), isEqualTo: 102)) {
stack.perform(
asynchronous: { (transaction) -> Bool in
object.testBoolean = NSNumber(value: true)
}
else {
if let object = transaction.fetchOne(
From<TestEntity1>(),
Where(#keyPath(TestEntity1.testEntityID), isEqualTo: 102)) {
object.testBoolean = NSNumber(value: true)
}
else {
XCTFail()
}
return transaction.hasChanges
},
success: { (hasChanges) in
XCTAssertTrue(hasChanges)
saveExpectation.fulfill()
},
failure: { _ in
XCTFail()
}
transaction.commit { (result) in
switch result {
case .success(let hasChanges):
XCTAssertTrue(hasChanges)
saveExpectation.fulfill()
case .failure:
XCTFail()
}
}
}
)
self.waitAndCheckExpectations()
}
}
@@ -544,25 +544,25 @@ class ListObserverTests: BaseTestDataTestCase {
}
)
let saveExpectation = self.expectation(description: "save")
stack.beginAsynchronous { (transaction) in
transaction.deleteAll(
From<TestEntity1>(),
Where(#keyPath(TestEntity1.testBoolean), isEqualTo: false)
)
transaction.commit { (result) in
stack.perform(
asynchronous: { (transaction) -> Bool in
switch result {
case .success(let hasChanges):
XCTAssertTrue(hasChanges)
saveExpectation.fulfill()
case .failure:
XCTFail()
}
transaction.deleteAll(
From<TestEntity1>(),
Where(#keyPath(TestEntity1.testBoolean), isEqualTo: false)
)
return transaction.hasChanges
},
success: { (hasChanges) in
XCTAssertTrue(hasChanges)
saveExpectation.fulfill()
},
failure: { _ in
XCTFail()
}
}
)
self.waitAndCheckExpectations()
}
}

View File

@@ -125,29 +125,29 @@ import CoreStore
}
)
let saveExpectation = self.expectation(description: "save")
stack.beginAsynchronous { (transaction) in
guard let object = transaction.edit(object) else {
stack.perform(
asynchronous: { (transaction) -> Bool in
guard let object = transaction.edit(object) else {
XCTFail()
try transaction.cancel()
}
object.testNumber = NSNumber(value: 10)
object.testString = "nil:TestEntity1:10"
return transaction.hasChanges
},
success: { (hasChanges) in
XCTAssertTrue(hasChanges)
saveExpectation.fulfill()
},
failure: { _ in
XCTFail()
return
}
object.testNumber = NSNumber(value: 10)
object.testString = "nil:TestEntity1:10"
transaction.commit { (result) in
switch result {
case .success(let hasChanges):
XCTAssertTrue(hasChanges)
saveExpectation.fulfill()
case .failure:
XCTFail()
}
}
}
)
self.waitAndCheckExpectations()
}
}
@@ -193,29 +193,29 @@ import CoreStore
}
)
let saveExpectation = self.expectation(description: "save")
stack.beginAsynchronous { (transaction) in
guard let object = transaction.edit(object) else {
stack.perform(
asynchronous: { (transaction) -> Bool in
guard let object = transaction.edit(object) else {
XCTFail()
try transaction.cancel()
}
transaction.delete(object)
return transaction.hasChanges
},
success: { (hasChanges) in
XCTAssertTrue(hasChanges)
XCTAssertTrue(monitor.isObjectDeleted)
saveExpectation.fulfill()
},
failure: { _ in
XCTFail()
return
}
transaction.delete(object)
transaction.commit { (result) in
switch result {
case .success(let hasChanges):
XCTAssertTrue(hasChanges)
XCTAssertTrue(monitor.isObjectDeleted)
saveExpectation.fulfill()
case .failure:
XCTFail()
}
}
}
)
self.waitAndCheckExpectations()
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -33,7 +33,7 @@ public extension NSManagedObject {
/**
Exposes a `FetchableSource` that can fetch sibling objects of this `NSManagedObject` instance. This may be the `DataStack`, a `BaseDataTransaction`, the `NSManagedObjectContext` itself, or `nil` if the obejct's parent is already deallocated.
- Warning: Future implementations may change the instance returned by this method depending on the timing or condition that `fetchSource()` was called. Do not make assumptions that the instance will be a specific instance. If the `NSManagedObjectContext` instance is desired, use the `FetchableSource.internalContext()` method to get the correct instance. Also, do not assume that the `fetchSource()` and `querySource()` return the same instance all the time.
- Warning: Future implementations may change the instance returned by this method depending on the timing or condition that `fetchSource()` was called. Do not make assumptions that the instance will be a specific instance. If the `NSManagedObjectContext` instance is desired, use the `FetchableSource.unsafeContext()` method to get the correct instance. Also, do not assume that the `fetchSource()` and `querySource()` return the same instance all the time.
- returns: a `FetchableSource` that can fetch sibling objects of this `NSManagedObject` instance. This may be the `DataStack`, a `BaseDataTransaction`, the `NSManagedObjectContext` itself, or `nil` if the object's parent is already deallocated.
*/
@nonobjc
@@ -56,7 +56,7 @@ public extension NSManagedObject {
/**
Exposes a `QueryableSource` that can query attributes and aggregate values. This may be the `DataStack`, a `BaseDataTransaction`, the `NSManagedObjectContext` itself, or `nil` if the obejct's parent is already deallocated.
- Warning: Future implementations may change the instance returned by this method depending on the timing or condition that `querySource()` was called. Do not make assumptions that the instance will be a specific instance. If the `NSManagedObjectContext` instance is desired, use the `QueryableSource.internalContext()` method to get the correct instance. Also, do not assume that the `fetchSource()` and `querySource()` return the same instance all the time.
- Warning: Future implementations may change the instance returned by this method depending on the timing or condition that `querySource()` was called. Do not make assumptions that the instance will be a specific instance. If the `NSManagedObjectContext` instance is desired, use the `QueryableSource.unsafeContext()` method to get the correct instance. Also, do not assume that the `fetchSource()` and `querySource()` return the same instance all the time.
- returns: a `QueryableSource` that can query attributes and aggregate values. This may be the `DataStack`, a `BaseDataTransaction`, the `NSManagedObjectContext` itself, or `nil` if the object's parent is already deallocated.
*/
@nonobjc

View File

@@ -288,10 +288,19 @@ public extension NSError {
internal var isCoreDataMigrationError: Bool {
let code = self.code
return (code == NSPersistentStoreIncompatibleVersionHashError
|| code == NSMigrationMissingSourceModelError
|| code == NSMigrationError)
&& self.domain == NSCocoaErrorDomain
guard self.domain == CocoaError.errorDomain else {
return false
}
switch CocoaError.Code(rawValue: self.code) {
case CocoaError.Code.persistentStoreIncompatibleVersionHash,
CocoaError.Code.migrationMissingSourceModel,
CocoaError.Code.migration:
return true
default:
return false
}
}
}

View File

@@ -359,8 +359,17 @@ extension BaseDataTransaction: FetchableSource, QueryableSource {
/**
The internal `NSManagedObjectContext` managed by this instance. Using this context directly should typically be avoided, and is provided by CoreStore only for extremely specialized cases.
*/
public func internalContext() -> NSManagedObjectContext {
public func unsafeContext() -> NSManagedObjectContext {
return self.context
}
// MARK: Deprecated
@available(*, deprecated: 4.0.0, renamed: "unsafeContext()")
public func internalContext() -> NSManagedObjectContext {
return self.unsafeContext()
}
}

View File

@@ -322,8 +322,17 @@ extension DataStack: FetchableSource, QueryableSource {
/**
The internal `NSManagedObjectContext` managed by this instance. Using this context directly should typically be avoided, and is provided by CoreStore only for extremely specialized cases.
*/
public func internalContext() -> NSManagedObjectContext {
public func unsafeContext() -> NSManagedObjectContext {
return self.mainContext
}
// MARK: Deprecated
@available(*, deprecated: 4.0.0, renamed: "unsafeContext()")
public func internalContext() -> NSManagedObjectContext {
return self.unsafeContext()
}
}

View File

@@ -159,5 +159,11 @@ public protocol FetchableSource: class {
/**
The internal `NSManagedObjectContext` managed by this `FetchableSource`. Using this context directly should typically be avoided, and is provided by CoreStore only for extremely specialized cases.
*/
func unsafeContext() -> NSManagedObjectContext
// MARK: Deprecated
@available(*, deprecated: 4.0.0, renamed: "unsafeContext()")
func internalContext() -> NSManagedObjectContext
}

View File

@@ -85,5 +85,11 @@ public protocol QueryableSource: class {
/**
The internal `NSManagedObjectContext` managed by this `QueryableSource`. Using this context directly should typically be avoided, and is provided by CoreStore only for extremely specialized cases.
*/
func unsafeContext() -> NSManagedObjectContext
// MARK: Deprecated
@available(*, deprecated: 4.0.0, renamed: "unsafeContext()")
func internalContext() -> NSManagedObjectContext
}

View File

@@ -73,7 +73,7 @@ public protocol ImportableObject: class, NSObjectProtocol, AnyObject {
func didInsert(from source: ImportSource, in transaction: BaseDataTransaction) throws
// MARK: Deprecated
// MARK: Obsolete (`deprecated` only for reference, please use new methods)
@available(*, deprecated: 3.0.0, renamed: "shouldInsert(from:in:)")
static func shouldInsertFromImportSource(_ source: ImportSource, inTransaction transaction: BaseDataTransaction) -> Bool
@@ -93,13 +93,15 @@ public extension ImportableObject {
}
// MARK: Deprecated
// MARK: Obsolete
@available(*, obsoleted: 4.0.0, renamed: "shouldInsert(from:in:)")
static func shouldInsertFromImportSource(_ source: ImportSource, inTransaction transaction: BaseDataTransaction) -> Bool {
return Self.shouldInsert(from: source, in: transaction)
}
@available(*, obsoleted: 4.0.0, renamed: "didInsert(from:in:)")
func didInsertFromImportSource(_ source: ImportSource, inTransaction transaction: BaseDataTransaction) throws {
try self.didInsert(from: source, in: transaction)

View File

@@ -116,7 +116,7 @@ public protocol ImportableUniqueObject: ImportableObject {
func update(from source: ImportSource, in transaction: BaseDataTransaction) throws
// MARK: Deprecated
// MARK: Obsolete (`deprecated` only for reference, please use new methods)
@available(*, deprecated: 3.0.0, renamed: "shouldInsert(from:in:)")
static func shouldInsertFromImportSource(_ source: ImportSource, inTransaction transaction: BaseDataTransaction) -> Bool
@@ -155,28 +155,33 @@ public extension ImportableUniqueObject {
}
// MARK: Deprecated
// MARK: Obsolete
@available(*, obsoleted: 4.0.0, renamed: "shouldInsert(from:in:)")
static func shouldInsertFromImportSource(_ source: ImportSource, inTransaction transaction: BaseDataTransaction) -> Bool {
return Self.shouldInsert(from: source, in: transaction)
}
@available(*, obsoleted: 4.0.0, renamed: "shouldUpdate(from:in:)")
static func shouldUpdateFromImportSource(_ source: ImportSource, inTransaction transaction: BaseDataTransaction) -> Bool {
return Self.shouldUpdate(from: source, in: transaction)
}
@available(*, obsoleted: 4.0.0, renamed: "uniqueID(from:in:)")
static func uniqueIDFromImportSource(_ source: ImportSource, inTransaction transaction: BaseDataTransaction) throws -> UniqueIDType? {
return try Self.uniqueID(from: source, in: transaction)
}
@available(*, obsoleted: 4.0.0, renamed: "didInsert(from:in:)")
func didInsertFromImportSource(_ source: ImportSource, inTransaction transaction: BaseDataTransaction) throws {
try self.didInsert(from: source, in: transaction)
}
@available(*, obsoleted: 4.0.0, renamed: "update(from:in:)")
func updateFromImportSource(_ source: ImportSource, inTransaction transaction: BaseDataTransaction) throws {
try self.update(from: source, in: transaction)

View File

@@ -54,9 +54,14 @@ internal extension DispatchQueue {
)
}
@nonobjc @inline(__always)
@nonobjc
internal func cs_isCurrentExecutionContext() -> Bool {
enum Static {
static let specificKey = DispatchSpecificKey<ObjectIdentifier>()
}
let specific = ObjectIdentifier(self)
self.setSpecific(key: Static.specificKey, value: specific)
@@ -89,9 +94,4 @@ internal extension DispatchQueue {
// MARK: Private
private enum Static {
static let specificKey = DispatchSpecificKey<ObjectIdentifier>()
}
}

View File

@@ -290,10 +290,19 @@ extension NSManagedObjectContext: FetchableSource, QueryableSource {
// MARK: FetchableSource, QueryableSource
@nonobjc
public func internalContext() -> NSManagedObjectContext {
public func unsafeContext() -> NSManagedObjectContext {
return self
}
// MARK: Deprecated
@available(*, deprecated: 4.0.0, renamed: "unsafeContext()")
public func internalContext() -> NSManagedObjectContext {
return self.unsafeContext()
}
}

View File

@@ -82,7 +82,7 @@ internal extension NSManagedObjectContext {
for objectID in updatedObjectIDs {
context?.object(with: objectID).willAccessValue(forKey: nil)
context?.registeredObject(for: objectID)?.willAccessValue(forKey: nil)
}
}
context?.mergeChanges(fromContextDidSave: note)

View File

@@ -140,17 +140,15 @@ internal extension NSManagedObjectContext {
}
@nonobjc
internal func saveSynchronously(waitForMerge: Bool) -> SaveResult {
internal func saveSynchronously(waitForMerge: Bool) -> (hasChanges: Bool, error: CoreStoreError?) {
var result = SaveResult(hasChanges: false)
var result: (hasChanges: Bool, error: CoreStoreError?) = (false, nil)
self.performAndWait {
guard self.hasChanges else {
return
}
do {
self.isSavingSynchronously = waitForMerge
@@ -164,32 +162,24 @@ internal extension NSManagedObjectContext {
saveError,
"Failed to save \(cs_typeName(NSManagedObjectContext.self))."
)
result = SaveResult(saveError)
result = (true, saveError)
return
}
if let parentContext = self.parent, self.shouldCascadeSavesToParent {
switch parentContext.saveSynchronously(waitForMerge: waitForMerge) {
case .success:
result = SaveResult(hasChanges: true)
case .failure(let error):
result = SaveResult(error)
}
let (_, error) = parentContext.saveSynchronously(waitForMerge: waitForMerge)
result = (true, error)
}
else {
result = SaveResult(hasChanges: true)
result = (true, nil)
}
}
return result
}
@nonobjc
internal func saveAsynchronouslyWithCompletion(_ completion: @escaping ((_ result: SaveResult) -> Void) = { _ in }) {
internal func saveAsynchronouslyWithCompletion(_ completion: @escaping (_ hasChanges: Bool, _ error: CoreStoreError?) -> Void = { _ in }) {
self.perform {
@@ -197,11 +187,10 @@ internal extension NSManagedObjectContext {
DispatchQueue.main.async {
completion(SaveResult(hasChanges: false))
completion(false, nil)
}
return
}
do {
self.isSavingSynchronously = false
@@ -216,21 +205,23 @@ internal extension NSManagedObjectContext {
"Failed to save \(cs_typeName(NSManagedObjectContext.self))."
)
DispatchQueue.main.async {
completion(SaveResult(saveError))
completion(true, saveError)
}
return
}
if self.shouldCascadeSavesToParent, let parentContext = self.parent {
parentContext.saveAsynchronouslyWithCompletion(completion)
parentContext.saveAsynchronouslyWithCompletion { (_, error) in
completion(true, error)
}
}
else {
DispatchQueue.main.async {
completion(SaveResult(hasChanges: true))
completion(true, nil)
}
}
}

View File

@@ -48,8 +48,7 @@ extension AsynchronousDataTransaction: CustomDebugStringConvertible, CoreStoreDe
("context", self.context),
("supportsUndo", self.supportsUndo),
("bypassesQueueing", self.bypassesQueueing),
("isCommitted", self.isCommitted),
("result", self.result as Any)
("isCommitted", self.isCommitted)
)
}
}
@@ -614,6 +613,7 @@ extension OrderBy: CustomDebugStringConvertible, CoreStoreDebugStringConvertible
// MARK: - SaveResult
@available(*, deprecated: 4.0.0, message: "Use the new DataStack.perform(asynchronous:...) and DataStack.perform(synchronous:...) family of APIs")
extension SaveResult: CustomDebugStringConvertible, CoreStoreDebugStringConvertible {
// MARK: CustomDebugStringConvertible
@@ -824,8 +824,7 @@ extension SynchronousDataTransaction: CustomDebugStringConvertible, CoreStoreDeb
("context", self.context),
("supportsUndo", self.supportsUndo),
("bypassesQueueing", self.bypassesQueueing),
("isCommitted", self.isCommitted),
("result", self.result as Any)
("isCommitted", self.isCommitted)
)
}
}

View File

@@ -38,6 +38,7 @@ public extension CoreStore {
// MARK: Internal
@inline(__always)
internal static func log(_ level: LogLevel, message: String, fileName: StaticString = #file, lineNumber: Int = #line, functionName: StaticString = #function) {
self.logger.log(
@@ -49,6 +50,7 @@ public extension CoreStore {
)
}
@inline(__always)
internal static func log(_ error: CoreStoreError, _ message: String, fileName: StaticString = #file, lineNumber: Int = #line, functionName: StaticString = #function) {
self.logger.log(
@@ -60,6 +62,7 @@ public extension CoreStore {
)
}
@inline(__always)
internal static func assert( _ condition: @autoclosure () -> Bool, _ message: String, fileName: StaticString = #file, lineNumber: Int = #line, functionName: StaticString = #function) {
self.logger.assert(
@@ -71,6 +74,7 @@ public extension CoreStore {
)
}
@inline(__always)
internal static func abort(_ message: String, fileName: StaticString = #file, lineNumber: Int = #line, functionName: StaticString = #function) -> Never {
self.logger.abort(

View File

@@ -43,29 +43,22 @@ public final class CSAsynchronousDataTransaction: CSBaseDataTransaction {
- parameter completion: the block executed after the save completes. Success or failure is reported by the `CSSaveResult` argument of the block.
*/
@objc
public func commitWithCompletion(_ completion: ((_ result: CSSaveResult) -> Void)?) {
public func commitWithSuccess(_ success: (() -> Void)?, failure: ((CSError) -> Void)?) {
self.bridgeToSwift.commit { (result) in
CoreStore.assert(
self.bridgeToSwift.transactionQueue.cs_isCurrentExecutionContext(),
"Attempted to commit a \(cs_typeName(self)) outside its designated queue."
)
CoreStore.assert(
!self.bridgeToSwift.isCommitted,
"Attempted to commit a \(cs_typeName(self)) more than once."
)
self.bridgeToSwift.autoCommit { (result) in
completion?(result.bridgeToObjectiveC)
}
}
/**
Begins a child transaction synchronously where `NSManagedObject` creates, updates, and deletes can be made. This method should not be used after the `-commitWithCompletion:` method was already called once.
- parameter closure: the block 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 `CSSaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously
*/
@objc
@discardableResult
public func beginSynchronous(_ closure: @escaping (_ transaction: CSSynchronousDataTransaction) -> Void) -> CSSaveResult? {
return bridge {
self.bridgeToSwift.beginSynchronous { (transaction) in
switch result {
closure(transaction.bridgeToObjectiveC)
case (_, nil): success?()
case (_, let error?): failure?(error.bridgeToObjectiveC)
}
}
}
@@ -159,6 +152,52 @@ public final class CSAsynchronousDataTransaction: CSBaseDataTransaction {
super.init(swiftValue as! AsynchronousDataTransaction)
}
// MARK: Deprecated
/**
Saves the transaction changes. This method should not be used after the `-commitWithCompletion:` method was already called once.
- parameter completion: the block executed after the save completes. Success or failure is reported by the `CSSaveResult` argument of the block.
*/
@available(*, deprecated: 4.0.0, message: "Use the new -[CSAsynchronousDataTransaction commitWithSuccess:failure:] method.")
@objc
public func commitWithCompletion(_ completion: ((_ result: CSSaveResult) -> Void)?) {
CoreStore.assert(
self.bridgeToSwift.transactionQueue.cs_isCurrentExecutionContext(),
"Attempted to commit a \(cs_typeName(self)) outside its designated queue."
)
CoreStore.assert(
!self.bridgeToSwift.isCommitted,
"Attempted to commit a \(cs_typeName(self)) more than once."
)
self.bridgeToSwift.commit { (result) in
completion?(result.bridgeToObjectiveC)
}
}
/**
Begins a child transaction synchronously where `NSManagedObject` creates, updates, and deletes can be made. This method should not be used after the `-commitWithCompletion:` method was already called once.
- parameter closure: the block 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 `CSSaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously
*/
@available(*, deprecated: 4.0.0, message: "Secondary tasks spawned from CSAsynchronousDataTransactions and CSSynchronousDataTransactions are no longer supported. ")
@objc
@discardableResult
public func beginSynchronous(_ closure: @escaping (_ transaction: CSSynchronousDataTransaction) -> Void) -> CSSaveResult? {
return bridge {
self.bridgeToSwift.beginSynchronous { (transaction) in
closure(transaction.bridgeToObjectiveC)
}
}
}
}

View File

@@ -38,10 +38,7 @@ public extension CSCoreStore {
@objc
public static func beginAsynchronous(_ closure: @escaping (_ transaction: CSAsynchronousDataTransaction) -> Void) {
return CoreStore.beginAsynchronous { (transaction) in
closure(transaction.bridgeToObjectiveC)
}
self.defaultStack.beginAsynchronous(closure)
}
/**
@@ -51,16 +48,9 @@ public extension CSCoreStore {
- returns: a `CSSaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously
*/
@objc
@discardableResult
public static func beginSynchronous(_ closure: @escaping (_ transaction: CSSynchronousDataTransaction) -> Void) -> CSSaveResult? {
public static func beginSynchronous(_ closure: @escaping (_ transaction: CSSynchronousDataTransaction) -> Void, error: NSErrorPointer) -> Bool {
return bridge {
CoreStore.beginSynchronous { (transaction) in
closure(transaction.bridgeToObjectiveC)
}
}
return self.defaultStack.beginSynchronous(closure, error: error)
}
/**
@@ -101,4 +91,21 @@ public extension CSCoreStore {
CoreStore.refreshAndMergeAllObjects()
}
// MARK: Deprecated
/**
Using the `defaultStack`, begins a transaction synchronously where `NSManagedObject` creates, updates, and deletes can be made.
- parameter closure: the block 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 `CSSaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously
*/
@available(*, deprecated: 4.0.0, message: "Use the new +[CSCoreStore beginSynchronous:error:] API that reports failure using an error instance.")
@objc
@discardableResult
public static func beginSynchronous(_ closure: @escaping (_ transaction: CSSynchronousDataTransaction) -> Void) -> CSSaveResult? {
return self.defaultStack.beginSynchronous(closure)
}
}

View File

@@ -38,27 +38,57 @@ public extension CSDataStack {
@objc
public func beginAsynchronous(_ closure: @escaping (_ transaction: CSAsynchronousDataTransaction) -> Void) {
return self.bridgeToSwift.beginAsynchronous { (transaction) in
closure(transaction.bridgeToObjectiveC)
}
self.bridgeToSwift.perform(
asynchronous: { (transaction) in
let csTransaction = transaction.bridgeToObjectiveC
closure(csTransaction)
if !transaction.isCommitted && transaction.hasChanges {
CoreStore.log(
.warning,
message: "The closure for the \(cs_typeName(csTransaction)) completed without being committed. All changes made within the transaction were discarded."
)
}
try transaction.cancel()
},
completion: { _ in }
)
}
/**
Begins a transaction synchronously where `NSManagedObject` creates, updates, and deletes can be made.
- parameter closure: the block 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 `CSSaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously
- parameter error: the `CSError` pointer that indicates the reason in case of an failure
- returns: `YES` if the commit succeeded, `NO` if the commit failed. If `NO`, the `error` argument will hold error information.
*/
@objc
@discardableResult
public func beginSynchronous(_ closure: @escaping (_ transaction: CSSynchronousDataTransaction) -> Void) -> CSSaveResult? {
public func beginSynchronous(_ closure: @escaping (_ transaction: CSSynchronousDataTransaction) -> Void, error: NSErrorPointer) -> Bool {
return bridge {
return bridge(error) {
self.bridgeToSwift.beginSynchronous { (transaction) in
do {
closure(transaction.bridgeToObjectiveC)
try self.bridgeToSwift.perform(
synchronous: { (transaction) in
let csTransaction = transaction.bridgeToObjectiveC
closure(csTransaction)
if !transaction.isCommitted && transaction.hasChanges {
CoreStore.log(
.warning,
message: "The closure for the \(cs_typeName(csTransaction)) completed without being committed. All changes made within the transaction were discarded."
)
}
try transaction.cancel()
}
)
}
catch CoreStoreError.userCancelled {
return
}
}
}
@@ -101,4 +131,27 @@ public extension CSDataStack {
self.bridgeToSwift.refreshAndMergeAllObjects()
}
// MARK: Deprecated
/**
Begins a transaction synchronously where `NSManagedObject` creates, updates, and deletes can be made.
- parameter closure: the block 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 `CSSaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously
*/
@available(*, deprecated: 4.0.0, message: "Use the new -[CSDataStack beginSynchronous:error:] API that reports failure using an error instance.")
@objc
@discardableResult
public func beginSynchronous(_ closure: @escaping (_ transaction: CSSynchronousDataTransaction) -> Void) -> CSSaveResult? {
return bridge {
self.bridgeToSwift.beginSynchronous { (transaction) in
closure(transaction.bridgeToObjectiveC)
}
}
}
}

View File

@@ -34,6 +34,7 @@ import CoreData
- SeeAlso: `SaveResult`
*/
@available(*, deprecated: 4.0.0, message: "Use APIs that report failures with `CSError`s instead.")
@objc
public final class CSSaveResult: NSObject, CoreStoreObjectiveCType {
@@ -173,6 +174,7 @@ public final class CSSaveResult: NSObject, CoreStoreObjectiveCType {
// MARK: - SaveResult
@available(*, deprecated: 4.0.0, message: "Use the new DataStack.perform(asynchronous:...) and DataStack.perform(synchronous:...) family of APIs")
extension SaveResult: CoreStoreSwiftType {
// MARK: CoreStoreSwiftType

View File

@@ -38,34 +38,19 @@ import CoreData
public final class CSSynchronousDataTransaction: CSBaseDataTransaction {
/**
Saves the transaction changes and waits for completion synchronously. This method should not be used after the `-commitAndWait` method was already called once.
Saves the transaction changes and waits for completion synchronously. This method should not be used after the `-commitAndWaitWithError:` method was already called once.
- returns: a `CSSaveResult` containing the success or failure information
- parameter error: the `CSError` pointer that indicates the reason in case of an failure
- returns: `YES` if the commit succeeded, `NO` if the commit failed. If `NO`, the `error` argument will hold error information.
*/
@objc
public func commitAndWait() -> CSSaveResult {
public func commitAndWait(error: NSErrorPointer) -> Bool {
return bridge {
return bridge(error) {
self.bridgeToSwift.commitAndWait()
}
}
/**
Begins a child transaction synchronously where `NSManagedObject` creates, updates, and deletes can be made. This method should not be used after the `-commitAndWait` method was already called once.
- parameter closure: the block 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 `CSSaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously
*/
@objc
@discardableResult
public func beginSynchronous(_ closure: @escaping (_ transaction: CSSynchronousDataTransaction) -> Void) -> CSSaveResult? {
return bridge {
self.bridgeToSwift.beginSynchronous { (transaction) in
if case (_, let error?) = self.bridgeToSwift.context.saveSynchronously(waitForMerge: true) {
closure(transaction.bridgeToObjectiveC)
throw error
}
}
}
@@ -158,6 +143,44 @@ public final class CSSynchronousDataTransaction: CSBaseDataTransaction {
super.init(swiftValue as! SynchronousDataTransaction)
}
// MARK: Deprecated
/**
Saves the transaction changes and waits for completion synchronously. This method should not be used after the `-commitAndWait` method was already called once.
- returns: a `CSSaveResult` containing the success or failure information
*/
@available(*, deprecated: 4.0.0, message: "Use the new -[CSSynchronousDataTransaction commitAndWaitWithError:] method")
@objc
public func commitAndWait() -> CSSaveResult {
return bridge {
self.bridgeToSwift.commitAndWait()
}
}
/**
Begins a child transaction synchronously where `NSManagedObject` creates, updates, and deletes can be made. This method should not be used after the `-commitAndWait` method was already called once.
- parameter closure: the block 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 `CSSaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously
*/
@available(*, deprecated: 4.0.0, message: "Secondary tasks spawned from CSAsynchronousDataTransactions and CSSynchronousDataTransactions are no longer supported. ")
@objc
@discardableResult
public func beginSynchronous(_ closure: @escaping (_ transaction: CSSynchronousDataTransaction) -> Void) -> CSSaveResult? {
return bridge {
self.bridgeToSwift.beginSynchronous { (transaction) in
closure(transaction.bridgeToObjectiveC)
}
}
}
}

View File

@@ -36,32 +36,43 @@ import CoreData
*/
@objc
public final class CSUnsafeDataTransaction: CSBaseDataTransaction {
/**
Saves the transaction changes asynchronously. For a `CSUnsafeDataTransaction`, multiple commits are allowed, although it is the developer's responsibility to ensure a reasonable leeway to prevent blocking the main thread.
- parameter completion: the block executed after the save completes. Success or failure is reported by the `CSSaveResult` argument of the block.
- parameter completion: the block executed after the save completes. Success or failure is reported by the `error` argument of the block.
*/
@objc
public func commit(_ completion: ((_ result: CSSaveResult) -> Void)?) {
public func commitWithSuccess(_ success: (() -> Void)?, _ failure: ((CSError) -> Void)?) {
self.bridgeToSwift.commit { (result) in
self.bridgeToSwift.context.saveAsynchronouslyWithCompletion { (_, error) in
completion?(result.bridgeToObjectiveC)
if let error = error {
failure?(error.bridgeToObjectiveC)
}
else {
success?()
}
withExtendedLifetime(self, {})
}
}
/**
Saves the transaction changes and waits for completion synchronously. For a `CSUnsafeDataTransaction`, multiple commits are allowed, although it is the developer's responsibility to ensure a reasonable leeway to prevent blocking the main thread.
- returns: a `CSSaveResult` containing the success or failure information
- parameter error: the `CSError` pointer that indicates the reason in case of an failure
- returns: `YES` if the commit succeeded, `NO` if the commit failed. If `NO`, the `error` argument will hold error information.
*/
@objc
public func commitAndWait() -> CSSaveResult {
public func commitAndWait(error: NSErrorPointer) -> Bool {
return bridge {
return bridge(error) {
self.bridgeToSwift.commitAndWait()
if case (_, let error?) = self.bridgeToSwift.context.saveSynchronously(waitForMerge: true) {
throw error
}
}
}
@@ -156,7 +167,7 @@ public final class CSUnsafeDataTransaction: CSBaseDataTransaction {
- that all saves will be done either through the `CSUnsafeDataTransaction`'s `-commit:` or `-commitAndWait` method, or by calling `-save:` manually on the context, its parent, and all other ancestor contexts if there are any.
*/
@objc
public var internalContext: NSManagedObjectContext {
public func unsafeContext() -> NSManagedObjectContext {
return self.bridgeToSwift.context
}
@@ -188,6 +199,58 @@ public final class CSUnsafeDataTransaction: CSBaseDataTransaction {
super.init(swiftValue as! UnsafeDataTransaction)
}
// MARK: Deprecated
@available(*, deprecated: 4.0.0, renamed: "unsafeContext()")
@objc
public var internalContext: NSManagedObjectContext {
return self.bridgeToSwift.context
}
/**
Saves the transaction changes asynchronously. For a `CSUnsafeDataTransaction`, multiple commits are allowed, although it is the developer's responsibility to ensure a reasonable leeway to prevent blocking the main thread.
- parameter completion: the block executed after the save completes. Success or failure is reported by the `CSSaveResult` argument of the block.
*/
@available(*, deprecated: 4.0.0, message: "Use the new -[CSUnsafeDataTransaction commitWithSuccess:failure:] method")
@objc
public func commit(_ completion: ((_ result: CSSaveResult) -> Void)?) {
self.bridgeToSwift.context.saveAsynchronouslyWithCompletion { (hasChanges, error) in
if let error = error {
completion?(SaveResult(error).bridgeToObjectiveC)
}
else {
completion?(SaveResult(hasChanges: hasChanges).bridgeToObjectiveC)
}
withExtendedLifetime(self, {})
}
}
/**
Saves the transaction changes and waits for completion synchronously. For a `CSUnsafeDataTransaction`, multiple commits are allowed, although it is the developer's responsibility to ensure a reasonable leeway to prevent blocking the main thread.
- returns: a `CSSaveResult` containing the success or failure information
*/
@available(*, deprecated: 4.0.0, message: "Use the new -[CSUnsafeDataTransaction commitAndWaitWithError:] method")
@objc
public func commitAndWait() -> CSSaveResult {
return bridge { () -> SaveResult in
switch self.bridgeToSwift.context.saveSynchronously(waitForMerge: true) {
case (let hasChanges, nil): return SaveResult(hasChanges: hasChanges)
case (_, let error?): return SaveResult(error)
}
}
}
}

View File

@@ -202,7 +202,7 @@ public final class ListMonitor<T: NSManagedObject>: Hashable {
!self.isPendingRefetch || Thread.isMainThread,
"Attempted to access a \(cs_typeName(self)) outside the main thread while a refetch is in progress."
)
return self.fetchedResultsController.dynamicCast().fetchedObjects ?? []
return (self.fetchedResultsController.dynamicCast() as NSFetchedResultsController<T>).fetchedObjects ?? []
}
/**
@@ -213,7 +213,7 @@ public final class ListMonitor<T: NSManagedObject>: Hashable {
*/
public func objectsInSection(_ section: Int) -> [T] {
return (self.sectionInfoAtIndex(section).objects as? [T]) ?? []
return (self.sectionInfoAtIndex(section).objects as! [T]?) ?? []
}
/**
@@ -224,7 +224,7 @@ public final class ListMonitor<T: NSManagedObject>: Hashable {
*/
public func objectsInSection(safeSectionIndex section: Int) -> [T]? {
return (self.sectionInfoAtIndex(safeSectionIndex: section)?.objects as? [T]) ?? []
return self.sectionInfoAtIndex(safeSectionIndex: section)?.objects as! [T]?
}
/**
@@ -371,7 +371,7 @@ public final class ListMonitor<T: NSManagedObject>: Hashable {
!self.isPendingRefetch || Thread.isMainThread,
"Attempted to access a \(cs_typeName(self)) outside the main thread while a refetch is in progress."
)
return (self.fetchedResultsController.dynamicCast().fetchedObjects ?? []).index(of: object)
return (self.fetchedResultsController.dynamicCast() as NSFetchedResultsController<T>).fetchedObjects?.index(of: object)
}
/**

View File

@@ -155,12 +155,9 @@ public extension CoreStore {
}
// MARK: Deprecated
// MARK: Obsolete
/**
Returns the `NSEntityDescription` for the specified `NSManagedObject` subclass from `defaultStack`'s model.
*/
@available(*, deprecated: 3.0.0, renamed: "entityDescription(for:)")
@available(*, obsoleted: 4.0.0, renamed: "entityDescription(for:)")
public static func entityDescriptionForType(_ type: NSManagedObject.Type) -> NSEntityDescription? {
return self.entityDescription(for: type)

View File

@@ -529,15 +529,15 @@ public final class DataStack: Equatable {
}
// MARK: Deprecated
// MARK: Obsolete
@available(*, deprecated: 3.0.0, renamed: "entityDescription(for:)")
@available(*, obsoleted: 4.0.0, renamed: "entityDescription(for:)")
public func entityDescriptionForType(_ type: NSManagedObject.Type) -> NSEntityDescription? {
return self.entityDescription(for: type)
}
@available(*, deprecated: 3.0.0, renamed: "objectID(forURIRepresentation:)")
@available(*, obsoleted: 4.0.0, renamed: "objectID(forURIRepresentation:)")
public func objectIDForURIRepresentation(_ url: URL) -> NSManagedObjectID? {
return self.objectID(forURIRepresentation: url)

View File

@@ -34,63 +34,51 @@ import CoreData
*/
public final class AsynchronousDataTransaction: BaseDataTransaction {
// MARK: - Result
public enum Result<T> {
case success(userInfo: T)
case failure(error: CoreStoreError)
public var boolValue: Bool {
switch self {
case .success: return true
case .failure: return false
}
}
// MARK: Internal
internal init(userInfo: T) {
self = .success(userInfo: userInfo)
}
internal init(error: CoreStoreError) {
self = .failure(error: error)
}
}
// MARK: -
/**
Cancels a transaction by throwing `CoreStoreError.userCancelled`.
```
try transaction.cancel()
```
- Important: Never use `try?` or `try!` on a `cancel()` call. Always use `try`. Using `try?` will swallow the cancellation and the transaction will proceed to commit as normal. Using `try!` will crash the app as `cancel()` will *always* throw an error.
*/
public func cancel() throws -> Never {
throw CoreStoreError.userCancelled
}
/**
Saves the transaction changes. This method should not be used after the `commit()` method was already called once.
- parameter completion: the block executed after the save completes. Success or failure is reported by the `SaveResult` argument of the block.
*/
public func commit(_ completion: @escaping (_ result: SaveResult) -> Void = { _ in }) {
CoreStore.assert(
self.transactionQueue.cs_isCurrentExecutionContext(),
"Attempted to commit a \(cs_typeName(self)) outside its designated queue."
)
CoreStore.assert(
!self.isCommitted,
"Attempted to commit a \(cs_typeName(self)) more than once."
)
self.isCommitted = true
let group = DispatchGroup()
group.enter()
self.context.saveAsynchronouslyWithCompletion { (result) -> Void in
self.result = result
completion(result)
group.leave()
}
group.wait()
}
/**
Begins a child transaction synchronously where NSManagedObject creates, updates, and deletes can be made. This method should not be used after the `commit()` method was already called once.
- parameter closure: the block 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 `SaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously
*/
@discardableResult
public func beginSynchronous(_ closure: @escaping (_ transaction: SynchronousDataTransaction) -> Void) -> SaveResult? {
CoreStore.assert(
self.transactionQueue.cs_isCurrentExecutionContext(),
"Attempted to begin a child transaction from a \(cs_typeName(self)) outside its designated queue."
)
CoreStore.assert(
!self.isCommitted,
"Attempted to begin a child transaction from an already committed \(cs_typeName(self))."
)
return SynchronousDataTransaction(
mainContext: self.context,
queue: self.childTransactionQueue,
closure: closure).performAndWait()
}
// MARK: BaseDataTransaction
@@ -193,47 +181,93 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
// MARK: Internal
internal init(mainContext: NSManagedObjectContext, queue: DispatchQueue, closure: @escaping (_ transaction: AsynchronousDataTransaction) -> Void) {
self.closure = closure
internal init(mainContext: NSManagedObjectContext, queue: DispatchQueue) {
super.init(mainContext: mainContext, queue: queue, supportsUndo: false, bypassesQueueing: false)
}
internal func perform() {
internal func autoCommit(_ completion: @escaping (_ hasChanges: Bool, _ error: CoreStoreError?) -> Void) {
self.transactionQueue.async {
self.isCommitted = true
let group = DispatchGroup()
group.enter()
self.context.saveAsynchronouslyWithCompletion { (result) -> Void in
self.closure(self)
if !self.isCommitted && self.hasChanges {
completion(result.0, result.1)
self.result = result
group.leave()
}
group.wait()
}
// MARK: Deprecated
/**
Saves the transaction changes. This method should not be used after the `commit()` method was already called once.
- parameter completion: the block executed after the save completes. Success or failure is reported by the `SaveResult` argument of the block.
*/
@available(*, deprecated: 4.0.0, message: "Use the new auto-commiting methods `DataStack.perform(asynchronous:completion:)` or `DataStack.perform(asynchronous:success:failure:)`. Please read the documentation on the behavior of the new methods.")
public func commit(_ completion: @escaping (_ result: SaveResult) -> Void = { _ in }) {
CoreStore.assert(
self.transactionQueue.cs_isCurrentExecutionContext(),
"Attempted to commit a \(cs_typeName(self)) outside its designated queue."
)
CoreStore.assert(
!self.isCommitted,
"Attempted to commit a \(cs_typeName(self)) more than once."
)
self.autoCommit { (result) in
switch result {
CoreStore.log(
.warning,
message: "The closure for the \(cs_typeName(self)) completed without being committed. All changes made within the transaction were discarded."
)
case (let hasChanges, nil): completion(SaveResult(hasChanges: hasChanges))
case (_, let error?): completion(SaveResult(error))
}
}
}
internal func performAndWait() -> SaveResult? {
/**
Begins a child transaction synchronously where NSManagedObject creates, updates, and deletes can be made. This method should not be used after the `commit()` method was already called once.
- parameter closure: the block 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 `SaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously
*/
@available(*, deprecated: 4.0.0, message: "Secondary tasks spawned from AsynchronousDataTransactions and SynchronousDataTransactions are no longer supported. ")
@discardableResult
public func beginSynchronous(_ closure: @escaping (_ transaction: SynchronousDataTransaction) -> Void) -> SaveResult? {
self.transactionQueue.sync {
CoreStore.assert(
self.transactionQueue.cs_isCurrentExecutionContext(),
"Attempted to begin a child transaction from a \(cs_typeName(self)) outside its designated queue."
)
CoreStore.assert(
!self.isCommitted,
"Attempted to begin a child transaction from an already committed \(cs_typeName(self))."
)
let childTransaction = SynchronousDataTransaction(
mainContext: self.context,
queue: self.childTransactionQueue
)
childTransaction.transactionQueue.cs_sync {
self.closure(self)
closure(childTransaction)
if !self.isCommitted && self.hasChanges {
if !childTransaction.isCommitted && childTransaction.hasChanges {
CoreStore.log(
.warning,
message: "The closure for the \(cs_typeName(self)) completed without being committed. All changes made within the transaction were discarded."
message: "The closure for the \(cs_typeName(childTransaction)) completed without being committed. All changes made within the transaction were discarded."
)
}
}
return self.result
switch childTransaction.result {
case nil: return nil
case (let hasChanges, nil)?: return SaveResult(hasChanges: hasChanges)
case (_, let error?)?: return SaveResult(error)
}
}
// MARK: Private
private let closure: (_ transaction: AsynchronousDataTransaction) -> Void
}

View File

@@ -455,7 +455,7 @@ public /*abstract*/ class BaseDataTransaction {
internal let supportsUndo: Bool
internal let bypassesQueueing: Bool
internal var isCommitted = false
internal var result: SaveResult?
internal var result: (hasChanges: Bool, error: CoreStoreError?)?
internal init(mainContext: NSManagedObjectContext, queue: DispatchQueue, supportsUndo: Bool, bypassesQueueing: Bool) {

View File

@@ -31,25 +31,39 @@ import Foundation
public extension CoreStore {
/**
Using the `defaultStack`, begins a transaction asynchronously where `NSManagedObject` creates, updates, and deletes can be made.
Using the `defaultStack`, performs a transaction asynchronously where `NSManagedObject` creates, updates, and deletes can be made. The changes are commited automatically after the `task` closure returns. On success, the value returned from closure will be the wrapped as `TransactionResult.success(userInfo: T)` in the `completion`'s `TransactionResult<T>`. Any errors thrown from inside the `task` will be reported as `TransactionResult.failure(error: Error)`. To cancel/rollback changes, call `transaction.cancel()`, which throws a `CoreStoreError.userCancelled`.
- parameter closure: the block 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`.
- parameter completion: the closure executed after the save completes. The `TransactionResult` 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 `CoreStoreError.userCancelled`. Custom errors thrown by the user will be wrapped in `CoreStoreError.userError(error: Error)`.
*/
public static func beginAsynchronous(_ closure: @escaping (_ transaction: AsynchronousDataTransaction) -> Void) {
public static func perform<T>(asynchronous task: @escaping (_ transaction: AsynchronousDataTransaction) throws -> T, completion: @escaping (AsynchronousDataTransaction.Result<T>) -> Void) {
self.defaultStack.beginAsynchronous(closure)
self.defaultStack.perform(asynchronous: task, completion: completion)
}
/**
Using the `defaultStack`, begins a transaction asynchronously where `NSManagedObject` creates, updates, and deletes can be made.
Using the `defaultStack`, performs a transaction asynchronously where `NSManagedObject` creates, updates, and deletes can be made. The changes are commited automatically after the `task` closure returns. On success, the value returned from closure will be the argument of the `success` closure. Any errors thrown from inside the `task` will be wrapped in a `CoreStoreError` and reported in the `failure` closure. To cancel/rollback changes, call `transaction.cancel()`, which throws a `CoreStoreError.userCancelled`.
- parameter closure: the block 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 `SaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously
- 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 success: the closure executed after the save succeeds. The `T` argument of the closure will be the value returned from `task`.
- 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)`.
*/
@discardableResult
public static func beginSynchronous(_ closure: @escaping (_ transaction: SynchronousDataTransaction) -> Void) -> SaveResult? {
public static func perform<T>(asynchronous task: @escaping (_ transaction: AsynchronousDataTransaction) throws -> T, success: @escaping (T) -> Void, failure: @escaping (CoreStoreError) -> Void) {
return self.defaultStack.beginSynchronous(closure)
self.defaultStack.perform(asynchronous: task, success: success, failure: failure)
}
/**
Using the `defaultStack`, performs a transaction synchronously where `NSManagedObject` creates, updates, and deletes can be made. The changes are commited automatically after the `task` closure returns. On success, the value returned from closure will be the return value of `perform(synchronous:)`. Any errors thrown from inside the `task` will be rethrown from `perform(synchronous:)`. To cancel/rollback changes, call `transaction.cancel()`, which throws a `CoreStoreError.userCancelled`.
- parameter task: the synchronous non-escaping 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 waitForAllObservers: When `true`, this method waits for all observers to be notified of the changes before returning. This results in more predictable data update order, but may risk triggering deadlocks. When `false`, this method does not wait for observers to be notified of the changes before returning. This results in lower risk for deadlocks, but the updated data may not have been propagated to the `DataStack` after returning. Defaults to `true`.
- throws: a `CoreStoreError` value indicating the failure. Cancelled `task`s will be indicated by `CoreStoreError.userCancelled`. Custom errors thrown by the user will be wrapped in `CoreStoreError.userError(error: Error)`.
- returns: the value returned from `task`
*/
public static func perform<T>(synchronous task: ((_ transaction: SynchronousDataTransaction) throws -> T), waitForAllObservers: Bool = true) throws -> T {
return try self.defaultStack.perform(synchronous: task, waitForAllObservers: waitForAllObservers)
}
/**
@@ -70,4 +84,31 @@ public extension CoreStore {
self.defaultStack.refreshAndMergeAllObjects()
}
// MARK: Deprecated
/**
Using the `defaultStack`, begins a transaction asynchronously where `NSManagedObject` creates, updates, and deletes can be made.
- parameter closure: the block 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`.
*/
@available(*, deprecated: 4.0.0, message: "Use the new auto-commiting methods `perform(asynchronous:completion:)` or `perform(asynchronous:success:failure:)`. Please read the documentation on the behavior of the new methods.")
public static func beginAsynchronous(_ closure: @escaping (_ transaction: AsynchronousDataTransaction) -> Void) {
self.defaultStack.beginAsynchronous(closure)
}
/**
Using the `defaultStack`, begins a transaction asynchronously where `NSManagedObject` creates, updates, and deletes can be made.
- parameter closure: the block 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 `SaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously
*/
@available(*, deprecated: 4.0.0, message: "Use the new auto-commiting method `perform(synchronous:)`. Please read the documentation on the behavior of the new methods.")
@discardableResult
public static func beginSynchronous(_ closure: @escaping (_ transaction: SynchronousDataTransaction) -> Void) -> SaveResult? {
return self.defaultStack.beginSynchronous(closure)
}
}

View File

@@ -31,22 +31,33 @@ import CoreData
public extension DataStack {
public func perform<T>(asynchronous task: @escaping (_ transaction: AsynchronousDataTransaction) throws -> T, completion: @escaping (TransactionResult<T>) -> Void) {
/**
Performs a transaction asynchronously where `NSManagedObject` creates, updates, and deletes can be made. The changes are commited automatically after the `task` closure returns. On success, the value returned from closure will be the wrapped as `TransactionResult.success(userInfo: T)` in the `completion`'s `TransactionResult<T>`. Any errors thrown from inside the `task` will be reported as `TransactionResult.failure(error: Error)`. To cancel/rollback changes, call `transaction.cancel()`, which throws a `CoreStoreError.userCancelled`.
- 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 completion: the closure executed after the save completes. The `TransactionResult` 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 `CoreStoreError.userCancelled`. Custom errors thrown by the user will be wrapped in `CoreStoreError.userError(error: Error)`.
*/
public func perform<T>(asynchronous task: @escaping (_ transaction: AsynchronousDataTransaction) throws -> T, completion: @escaping (AsynchronousDataTransaction.Result<T>) -> Void) {
self.perform(
asynchronous: task,
success: { completion(TransactionResult(userInfo: $0)) },
failure: { completion(TransactionResult(error: $0)) }
success: { completion(.init(userInfo: $0)) },
failure: { completion(.init(error: $0)) }
)
}
/**
Performs a transaction asynchronously where `NSManagedObject` creates, updates, and deletes can be made. The changes are commited automatically after the `task` closure returns. On success, the value returned from closure will be the argument of the `success` closure. Any errors thrown from inside the `task` will be wrapped in a `CoreStoreError` and reported in the `failure` closure. To cancel/rollback changes, call `transaction.cancel()`, which throws a `CoreStoreError.userCancelled`.
- 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 success: the closure executed after the save succeeds. The `T` argument of the closure will be the value returned from `task`.
- parameter failure: the closure executed if the save fails or if any errors are thrown within `task`. Cancelled `task`s will be indicated by `CoreStoreError.userCancelled`. Custom errors thrown by the user will be wrapped in `CoreStoreError.userError(error: Error)`.
*/
public func perform<T>(asynchronous task: @escaping (_ transaction: AsynchronousDataTransaction) throws -> T, success: @escaping (T) -> Void, failure: @escaping (CoreStoreError) -> Void) {
let transaction = AsynchronousDataTransaction(
mainContext: self.rootSavingContext,
queue: self.childTransactionQueue,
closure: { _ in }
queue: self.childTransactionQueue
)
transaction.transactionQueue.cs_async {
@@ -65,30 +76,40 @@ public extension DataStack {
DispatchQueue.main.async { failure(.userError(error: error)) }
return
}
transaction.commit { (result) in
transaction.autoCommit { (_, error) in
switch result {
if let error = error {
case .success: success(userInfo)
case .failure(let error): failure(error)
failure(error)
}
else {
success(userInfo)
}
}
}
}
public func perform<T>(synchronous task: ((_ transaction: SynchronousDataTransaction) throws -> T), waitForObserverNotifications: Bool = true) throws -> T {
/**
Performs a transaction synchronously where `NSManagedObject` creates, updates, and deletes can be made. The changes are commited automatically after the `task` closure returns. On success, the value returned from closure will be the return value of `perform(synchronous:)`. Any errors thrown from inside the `task` will be rethrown from `perform(synchronous:)`. To cancel/rollback changes, call `transaction.cancel()`, which throws a `CoreStoreError.userCancelled`.
- parameter task: the synchronous non-escaping 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 waitForAllObservers: When `true`, this method waits for all observers to be notified of the changes before returning. This results in more predictable data update order, but may risk triggering deadlocks. When `false`, this method does not wait for observers to be notified of the changes before returning. This results in lower risk for deadlocks, but the updated data may not have been propagated to the `DataStack` after returning. Defaults to `true`.
- throws: a `CoreStoreError` value indicating the failure. Cancelled `task`s will be indicated by `CoreStoreError.userCancelled`. Custom errors thrown by the user will be wrapped in `CoreStoreError.userError(error: Error)`.
- returns: the value returned from `task`
*/
public func perform<T>(synchronous task: ((_ transaction: SynchronousDataTransaction) throws -> T), waitForAllObservers: Bool = true) throws -> T {
let transaction = SynchronousDataTransaction(
mainContext: self.rootSavingContext,
queue: self.childTransactionQueue,
closure: { _ in }
queue: self.childTransactionQueue
)
return try transaction.transactionQueue.cs_sync {
let userInfo: T
do {
userInfo = try task(transaction)
userInfo = try withoutActuallyEscaping(task, do: { try $0(transaction) })
}
catch let error as CoreStoreError {
@@ -98,46 +119,17 @@ public extension DataStack {
throw CoreStoreError.userError(error: error)
}
let result = waitForObserverNotifications
? transaction.commitAndWait()
: transaction.commit()
switch result {
if case (_, let error?) = transaction.autoCommit(waitForMerge: waitForAllObservers) {
case .success: return userInfo
case .failure(let error): throw error
throw error
}
else {
return userInfo
}
}
}
/**
Begins a transaction asynchronously where `NSManagedObject` creates, updates, and deletes can be made.
- parameter closure: the block 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`.
*/
public func beginAsynchronous(_ closure: @escaping (_ transaction: AsynchronousDataTransaction) -> Void) {
AsynchronousDataTransaction(
mainContext: self.rootSavingContext,
queue: self.childTransactionQueue,
closure: closure).perform()
}
/**
Begins a transaction synchronously where `NSManagedObject` creates, updates, and deletes can be made.
- parameter closure: the block 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 `SaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously
*/
@discardableResult
public func beginSynchronous(_ closure: @escaping (_ transaction: SynchronousDataTransaction) -> Void) -> SaveResult? {
return SynchronousDataTransaction(
mainContext: self.rootSavingContext,
queue: self.childTransactionQueue,
closure: closure).performAndWait()
}
/**
Begins a non-contiguous transaction where `NSManagedObject` creates, updates, and deletes can be made. This is useful for making temporary changes, such as partially filled forms.
@@ -164,4 +156,67 @@ public extension DataStack {
)
self.mainContext.refreshAndMergeAllObjects()
}
// MARK: Deprecated
/**
Begins a transaction asynchronously where `NSManagedObject` creates, updates, and deletes can be made.
- parameter closure: the block 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`.
*/
@available(*, deprecated: 4.0.0, message: "Use the new auto-commiting methods `perform(asynchronous:completion:)` or `perform(asynchronous:success:failure:)`. Please read the documentation on the behavior of the new methods.")
public func beginAsynchronous(_ closure: @escaping (_ transaction: AsynchronousDataTransaction) -> Void) {
let transaction = AsynchronousDataTransaction(
mainContext: self.rootSavingContext,
queue: self.childTransactionQueue
)
transaction.transactionQueue.cs_async {
closure(transaction)
if !transaction.isCommitted && transaction.hasChanges {
CoreStore.log(
.warning,
message: "The closure for the \(cs_typeName(transaction)) completed without being committed. All changes made within the transaction were discarded."
)
}
}
}
/**
Begins a transaction synchronously where `NSManagedObject` creates, updates, and deletes can be made.
- parameter closure: the block 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 `SaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously
*/
@available(*, deprecated: 4.0.0, message: "Use the new auto-commiting method `perform(synchronous:)`. Please read the documentation on the behavior of the new methods.")
@discardableResult
public func beginSynchronous(_ closure: @escaping (_ transaction: SynchronousDataTransaction) -> Void) -> SaveResult? {
let transaction = SynchronousDataTransaction(
mainContext: self.rootSavingContext,
queue: self.childTransactionQueue
)
transaction.transactionQueue.cs_sync {
closure(transaction)
if !transaction.isCommitted && transaction.hasChanges {
CoreStore.log(
.warning,
message: "The closure for the \(cs_typeName(transaction)) completed without being committed. All changes made within the transaction were discarded."
)
}
}
switch transaction.result {
case nil: return nil
case (let hasChanges, nil)?: return SaveResult(hasChanges: hasChanges)
case (_, let error?)?: return SaveResult(error)
}
}
}

View File

@@ -57,6 +57,7 @@ import Foundation
}
```
*/
@available(*, deprecated: 4.0.0, message: "Use the new DataStack.perform(asynchronous:...) and DataStack.perform(synchronous:...) family of APIs")
public enum SaveResult: Hashable {
/**

View File

@@ -34,83 +34,18 @@ import CoreData
*/
public final class SynchronousDataTransaction: BaseDataTransaction {
/**
Cancels a transaction by throwing `CoreStoreError.userCancelled`.
```
try transaction.cancel()
```
- Important: Never use `try?` or `try!` on a `cancel()` call. Always use `try`. Using `try?` will swallow the cancellation and the transaction will proceed to commit as normal. Using `try!` will crash the app as `cancel()` will *always* throw an error.
*/
public func cancel() throws -> Never {
throw CoreStoreError.userCancelled
}
/**
Saves the transaction changes and waits for completion synchronously. This method should not be used after the `commit()` or `commitAndWait()` method was already called once.
- Important: Unlike `SynchronousDataTransaction.commit()`, this method waits for all observers to be notified of the changes before returning. This results in more predictable data update order, but may risk triggering deadlocks.
- returns: a `SaveResult` containing the success or failure information
*/
public func commitAndWait() -> SaveResult {
CoreStore.assert(
self.transactionQueue.cs_isCurrentExecutionContext(),
"Attempted to commit a \(cs_typeName(self)) outside its designated queue."
)
CoreStore.assert(
!self.isCommitted,
"Attempted to commit a \(cs_typeName(self)) more than once."
)
self.isCommitted = true
let result = self.context.saveSynchronously(waitForMerge: true)
self.result = result
return result
}
/**
Saves the transaction changes and waits for completion synchronously. This method should not be used after the `commit()` or `commitAndWait()` method was already called once.
- Important: Unlike `SynchronousDataTransaction.commitAndWait()`, this method does not wait for observers to be notified of the changes before returning. This results in lower risk for deadlocks, but the updated data may not have been propagated to the `DataStack` after returning.
- returns: a `SaveResult` containing the success or failure information
*/
public func commit() -> SaveResult {
CoreStore.assert(
self.transactionQueue.cs_isCurrentExecutionContext(),
"Attempted to commit a \(cs_typeName(self)) outside its designated queue."
)
CoreStore.assert(
!self.isCommitted,
"Attempted to commit a \(cs_typeName(self)) more than once."
)
self.isCommitted = true
let result = self.context.saveSynchronously(waitForMerge: false)
self.result = result
return result
}
/**
Begins a child transaction synchronously where `NSManagedObject` creates, updates, and deletes can be made. This method should not be used after the `commit()` method was already called once.
- parameter closure: the block 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 `SaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously
*/
@discardableResult
public func beginSynchronous(_ closure: @escaping (_ transaction: SynchronousDataTransaction) -> Void) -> SaveResult? {
CoreStore.assert(
self.transactionQueue.cs_isCurrentExecutionContext(),
"Attempted to begin a child transaction from a \(cs_typeName(self)) outside its designated queue."
)
CoreStore.assert(
!self.isCommitted,
"Attempted to begin a child transaction from an already committed \(cs_typeName(self))."
)
return SynchronousDataTransaction(
mainContext: self.context,
queue: self.childTransactionQueue,
closure: closure).performAndWait()
}
// MARK: BaseDataTransaction
@@ -213,32 +148,109 @@ public final class SynchronousDataTransaction: BaseDataTransaction {
// MARK: Internal
internal init(mainContext: NSManagedObjectContext, queue: DispatchQueue, closure: @escaping (_ transaction: SynchronousDataTransaction) -> Void) {
self.closure = closure
internal init(mainContext: NSManagedObjectContext, queue: DispatchQueue) {
super.init(mainContext: mainContext, queue: queue, supportsUndo: false, bypassesQueueing: false)
}
internal func performAndWait() -> SaveResult? {
internal func autoCommit(waitForMerge: Bool) -> (hasChanges: Bool, error: CoreStoreError?) {
self.transactionQueue.sync {
self.closure(self)
if !self.isCommitted && self.hasChanges {
CoreStore.log(
.warning,
message: "The closure for the \(cs_typeName(self)) completed without being committed. All changes made within the transaction were discarded."
)
}
}
return self.result
self.isCommitted = true
let result = self.context.saveSynchronously(waitForMerge: waitForMerge)
self.result = result
return result
}
// MARK: Private
// MARK: Deprecated
private let closure: (_ transaction: SynchronousDataTransaction) -> Void
/**
Saves the transaction changes and waits for completion synchronously. This method should not be used after the `commit()` or `commitAndWait()` method was already called once.
- Important: Unlike `SynchronousDataTransaction.commit()`, this method waits for all observers to be notified of the changes before returning. This results in more predictable data update order, but may risk triggering deadlocks.
- returns: a `SaveResult` containing the success or failure information
*/
@available(*, deprecated: 4.0.0, message: "Use the new auto-commit method DataStack.perform(synchronous:waitForAllObservers:)")
public func commitAndWait() -> SaveResult {
CoreStore.assert(
self.transactionQueue.cs_isCurrentExecutionContext(),
"Attempted to commit a \(cs_typeName(self)) outside its designated queue."
)
CoreStore.assert(
!self.isCommitted,
"Attempted to commit a \(cs_typeName(self)) more than once."
)
switch self.autoCommit(waitForMerge: true) {
case (let hasChanges, nil): return SaveResult(hasChanges: hasChanges)
case (_, let error?): return SaveResult(error)
}
}
/**
Saves the transaction changes and waits for completion synchronously. This method should not be used after the `commit()` or `commitAndWait()` method was already called once.
- Important: Unlike `SynchronousDataTransaction.commitAndWait()`, this method does not wait for observers to be notified of the changes before returning. This results in lower risk for deadlocks, but the updated data may not have been propagated to the `DataStack` after returning.
- returns: a `SaveResult` containing the success or failure information
*/
@available(*, deprecated: 4.0.0, message: "Use the new auto-commit method DataStack.perform(synchronous:waitForAllObservers:)")
public func commit() -> SaveResult {
CoreStore.assert(
self.transactionQueue.cs_isCurrentExecutionContext(),
"Attempted to commit a \(cs_typeName(self)) outside its designated queue."
)
CoreStore.assert(
!self.isCommitted,
"Attempted to commit a \(cs_typeName(self)) more than once."
)
switch self.autoCommit(waitForMerge: false) {
case (let hasChanges, nil): return SaveResult(hasChanges: hasChanges)
case (_, let error?): return SaveResult(error)
}
}
/**
Begins a child transaction synchronously where `NSManagedObject` creates, updates, and deletes can be made. This method should not be used after the `commit()` method was already called once.
- parameter closure: the block 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 `SaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously
*/
@available(*, deprecated: 4.0.0, message: "Secondary tasks spawned from AsynchronousDataTransactions and SynchronousDataTransactions are no longer supported. ")
@discardableResult
public func beginSynchronous(_ closure: @escaping (_ transaction: SynchronousDataTransaction) -> Void) -> SaveResult? {
CoreStore.assert(
self.transactionQueue.cs_isCurrentExecutionContext(),
"Attempted to begin a child transaction from a \(cs_typeName(self)) outside its designated queue."
)
CoreStore.assert(
!self.isCommitted,
"Attempted to begin a child transaction from an already committed \(cs_typeName(self))."
)
let childTransaction = SynchronousDataTransaction(
mainContext: self.context,
queue: self.childTransactionQueue
)
childTransaction.transactionQueue.cs_sync {
closure(childTransaction)
if !childTransaction.isCommitted && childTransaction.hasChanges {
CoreStore.log(
.warning,
message: "The closure for the \(cs_typeName(childTransaction)) completed without being committed. All changes made within the transaction were discarded."
)
}
}
switch childTransaction.result {
case nil: return nil
case (let hasChanges, nil)?: return SaveResult(hasChanges: hasChanges)
case (_, let error?)?: return SaveResult(error)
}
}
}

View File

@@ -1,58 +0,0 @@
//
// TransactionResult.swift
// CoreStore
//
// Copyright © 2017 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
// MARK: - TransactionResult
public enum TransactionResult<T> {
case success(T)
case failure(CoreStoreError)
public var boolValue: Bool {
switch self {
case .success: return true
case .failure: return false
}
}
// MARK: Internal
internal init(userInfo: T) {
self = .success(userInfo)
}
internal init(error: CoreStoreError) {
self = .failure(error)
}
}

View File

@@ -34,30 +34,33 @@ import CoreData
*/
public final class UnsafeDataTransaction: BaseDataTransaction {
// MARK: -
/**
Saves the transaction changes asynchronously. For an `UnsafeDataTransaction`, multiple commits are allowed, although it is the developer's responsibility to ensure a reasonable leeway to prevent blocking the main thread.
- parameter completion: the block executed after the save completes. Success or failure is reported by the `SaveResult` 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(_ completion: @escaping (_ result: SaveResult) -> Void) {
public func commit(_ completion: @escaping (_ error: CoreStoreError?) -> Void) {
self.context.saveAsynchronouslyWithCompletion { (result) -> Void in
self.context.saveAsynchronouslyWithCompletion { (_, error) in
self.result = result
completion(result)
completion(error)
withExtendedLifetime(self, {})
}
}
/**
Saves the transaction changes and waits for completion synchronously. For an `UnsafeDataTransaction`, multiple commits are allowed, although it is the developer's responsibility to ensure a reasonable leeway to prevent blocking the main thread.
- returns: a `SaveResult` containing the success or failure information
- throws: a `CoreStoreError` value indicating the failure.
*/
public func commitAndWait() -> SaveResult {
public func commitAndWait() throws {
let result = self.context.saveSynchronously(waitForMerge: true)
self.result = result
return result
if case (_, let error?) = self.context.saveSynchronously(waitForMerge: true) {
throw error
}
}
/**
@@ -142,13 +145,4 @@ public final class UnsafeDataTransaction: BaseDataTransaction {
super.init(mainContext: mainContext, queue: queue, supportsUndo: supportsUndo, bypassesQueueing: true)
}
// MARK: Obsolete
@available(*, obsoleted: 3.0.0, message: "Transaction contexts are now exposed through the FetchableSource and QueryableSource protocols.", renamed: "internalContext()")
public var internalContext: NSManagedObjectContext {
fatalError()
}
}