mirror of
https://github.com/JohnEstropia/CoreStore.git
synced 2026-01-15 21:53:39 +01:00
WIP: prototyping new transaction structure
This commit is contained in:
@@ -42,33 +42,32 @@ final class TransactionTests: BaseTestCase {
|
||||
do {
|
||||
|
||||
let createExpectation = self.expectation(description: "create")
|
||||
stack.beginSynchronous { (transaction) in
|
||||
let hasChanges: Bool = stack.perform(
|
||||
synchronous: { (transaction) in
|
||||
|
||||
XCTAssertEqual(transaction.context, transaction.internalContext())
|
||||
XCTAssertTrue(transaction.context.isTransactionContext)
|
||||
XCTAssertFalse(transaction.context.isDataStackContext)
|
||||
|
||||
let object = transaction.create(Into<TestEntity1>())
|
||||
XCTAssertEqual(object.fetchSource()?.internalContext(), transaction.context)
|
||||
XCTAssertEqual(object.querySource()?.internalContext(), transaction.context)
|
||||
|
||||
object.testEntityID = NSNumber(value: 1)
|
||||
object.testString = "string1"
|
||||
object.testNumber = 100
|
||||
object.testDate = testDate
|
||||
|
||||
|
||||
switch transaction.commitAndWait() {
|
||||
defer {
|
||||
|
||||
createExpectation.fulfill()
|
||||
}
|
||||
XCTAssertEqual(transaction.context, transaction.internalContext())
|
||||
XCTAssertTrue(transaction.context.isTransactionContext)
|
||||
XCTAssertFalse(transaction.context.isDataStackContext)
|
||||
|
||||
case .success(let hasChanges):
|
||||
XCTAssertTrue(hasChanges)
|
||||
createExpectation.fulfill()
|
||||
let object = transaction.create(Into<TestEntity1>())
|
||||
XCTAssertEqual(object.fetchSource()?.internalContext(), transaction.context)
|
||||
XCTAssertEqual(object.querySource()?.internalContext(), transaction.context)
|
||||
|
||||
default:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
object.testEntityID = NSNumber(value: 1)
|
||||
object.testString = "string1"
|
||||
object.testNumber = 100
|
||||
object.testDate = testDate
|
||||
|
||||
return transaction.hasChanges
|
||||
},
|
||||
waitForObserverNotifications: true
|
||||
)
|
||||
self.checkExpectationsImmediately()
|
||||
XCTAssertTrue(hasChanges)
|
||||
|
||||
XCTAssertEqual(stack.fetchCount(From<TestEntity1>()), 1)
|
||||
|
||||
@@ -85,28 +84,28 @@ final class TransactionTests: BaseTestCase {
|
||||
do {
|
||||
|
||||
let updateExpectation = self.expectation(description: "update")
|
||||
stack.beginSynchronous { (transaction) in
|
||||
|
||||
guard let object = transaction.fetchOne(From<TestEntity1>()) else {
|
||||
let hasChanges: Bool = stack.perform(
|
||||
synchronous: { (transaction) in
|
||||
|
||||
XCTFail()
|
||||
return
|
||||
}
|
||||
object.testString = "string1_edit"
|
||||
object.testNumber = 200
|
||||
object.testDate = Date.distantFuture
|
||||
|
||||
switch transaction.commitAndWait() {
|
||||
defer {
|
||||
|
||||
updateExpectation.fulfill()
|
||||
}
|
||||
guard let object = transaction.fetchOne(From<TestEntity1>()) else {
|
||||
|
||||
XCTFail()
|
||||
return // TODO: convert fetch methods to throwing methods
|
||||
}
|
||||
object.testString = "string1_edit"
|
||||
object.testNumber = 200
|
||||
object.testDate = Date.distantFuture
|
||||
|
||||
case .success(let hasChanges):
|
||||
XCTAssertTrue(hasChanges)
|
||||
updateExpectation.fulfill()
|
||||
|
||||
default:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
return transaction.hasChanges
|
||||
},
|
||||
waitForObserverNotifications: true
|
||||
)
|
||||
self.checkExpectationsImmediately()
|
||||
XCTAssertTrue(hasChanges)
|
||||
|
||||
XCTAssertEqual(stack.fetchCount(From<TestEntity1>()), 1)
|
||||
|
||||
|
||||
@@ -815,7 +815,7 @@ public protocol ImportableUniqueObject: ImportableObject {
|
||||
static var uniqueIDKeyPath: String { get }
|
||||
var uniqueIDValue: UniqueIDType { get set }
|
||||
|
||||
static func shouldInsert(from source: ImportSource, inn transaction: BaseDataTransaction) -> Bool
|
||||
static func shouldInsert(from source: ImportSource, in transaction: BaseDataTransaction) -> Bool
|
||||
static func shouldUpdate(from source: ImportSource, in transaction: BaseDataTransaction) -> Bool
|
||||
static func uniqueID(from source: ImportSource, in transaction: BaseDataTransaction) throws -> UniqueIDType?
|
||||
func didInsert(from source: ImportSource, in transaction: BaseDataTransaction) throws
|
||||
|
||||
@@ -59,6 +59,16 @@ public enum CoreStoreError: Error, CustomNSError, Hashable {
|
||||
*/
|
||||
case internalError(NSError: NSError)
|
||||
|
||||
/**
|
||||
The transaction was terminated by a user-thrown `Error`.
|
||||
*/
|
||||
case userError(error: Error)
|
||||
|
||||
/**
|
||||
The transaction was cancelled by the user.
|
||||
*/
|
||||
case userCancelled
|
||||
|
||||
|
||||
// MARK: CustomNSError
|
||||
|
||||
@@ -85,6 +95,12 @@ public enum CoreStoreError: Error, CustomNSError, Hashable {
|
||||
|
||||
case .internalError:
|
||||
return CoreStoreErrorCode.internalError.rawValue
|
||||
|
||||
case .userError:
|
||||
return CoreStoreErrorCode.userError.rawValue
|
||||
|
||||
case .userCancelled:
|
||||
return CoreStoreErrorCode.userCancelled.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,10 +128,18 @@ public enum CoreStoreError: Error, CustomNSError, Hashable {
|
||||
"localStoreURL": localStoreURL
|
||||
]
|
||||
|
||||
case .internalError(let NSError):
|
||||
case .internalError(let nsError):
|
||||
return [
|
||||
"NSError": NSError
|
||||
"NSError": nsError
|
||||
]
|
||||
|
||||
case .userError(let error):
|
||||
return [
|
||||
"Error": error
|
||||
]
|
||||
|
||||
case .userCancelled:
|
||||
return [:]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,7 +163,23 @@ public enum CoreStoreError: Error, CustomNSError, Hashable {
|
||||
return url1 == url2
|
||||
|
||||
case (.internalError(let NSError1), .internalError(let NSError2)):
|
||||
return NSError1 == NSError2
|
||||
return NSError1.isEqual(NSError2)
|
||||
|
||||
case (.userError(let error1), .userError(let error2)):
|
||||
switch (error1, error2) {
|
||||
|
||||
case (let error1 as AnyHashable, let error2 as AnyHashable):
|
||||
return error1 == error2
|
||||
|
||||
case (let error1 as NSError, let error2 as NSError):
|
||||
return error1.isEqual(error2)
|
||||
|
||||
default:
|
||||
return false // shouldn't happen
|
||||
}
|
||||
|
||||
case (.userCancelled, .userCancelled):
|
||||
return true
|
||||
|
||||
default:
|
||||
return false
|
||||
@@ -166,8 +206,14 @@ public enum CoreStoreError: Error, CustomNSError, Hashable {
|
||||
case .progressiveMigrationRequired(let localStoreURL):
|
||||
return code.hashValue ^ localStoreURL.hashValue
|
||||
|
||||
case .internalError(let NSError):
|
||||
return code.hashValue ^ NSError.hashValue
|
||||
case .internalError(let nsError):
|
||||
return code.hashValue ^ nsError.hashValue
|
||||
|
||||
case .userError(let error):
|
||||
return code.hashValue ^ (error as NSError).hashValue
|
||||
|
||||
case .userCancelled:
|
||||
return code.hashValue
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,6 +267,16 @@ public enum CoreStoreErrorCode: Int {
|
||||
An internal SDK call failed with the specified "NSError" userInfo key.
|
||||
*/
|
||||
case internalError
|
||||
|
||||
/**
|
||||
The transaction was terminated by a user-thrown `Error` specified by "Error" userInfo key.
|
||||
*/
|
||||
case userError
|
||||
|
||||
/**
|
||||
The transaction was cancelled by the user.
|
||||
*/
|
||||
case userCancelled
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -142,6 +142,13 @@ extension CoreStoreError: CustomDebugStringConvertible, CoreStoreDebugStringConv
|
||||
case .internalError(let NSError):
|
||||
firstLine = ".internalError"
|
||||
info.append(("NSError", NSError))
|
||||
|
||||
case .userError(error: let error):
|
||||
firstLine = ".userError"
|
||||
info.append(("Error", error))
|
||||
|
||||
case .userCancelled:
|
||||
firstLine = ".userCancelled"
|
||||
}
|
||||
|
||||
return createFormattedString(
|
||||
|
||||
@@ -137,6 +137,16 @@ public enum CSErrorCode: Int {
|
||||
An internal SDK call failed with the specified "NSError" userInfo key.
|
||||
*/
|
||||
case internalError
|
||||
|
||||
/**
|
||||
The transaction was terminated by a user-thrown error with the specified "Error" userInfo key.
|
||||
*/
|
||||
case userError
|
||||
|
||||
/**
|
||||
The transaction was cancelled by the user.
|
||||
*/
|
||||
case userCancelled
|
||||
}
|
||||
|
||||
|
||||
@@ -209,12 +219,23 @@ extension CoreStoreError: CoreStoreSwiftType, _ObjectiveCBridgeableError {
|
||||
self = .progressiveMigrationRequired(localStoreURL: localStoreURL)
|
||||
|
||||
case .internalError:
|
||||
guard case let NSError as NSError = info["NSError"] else {
|
||||
guard case let nsError as NSError = info["NSError"] else {
|
||||
|
||||
self = .unknown
|
||||
return
|
||||
}
|
||||
self = .internalError(NSError: NSError)
|
||||
self = .internalError(NSError: nsError)
|
||||
|
||||
case .userError:
|
||||
guard case let error as Error = info["Error"] else {
|
||||
|
||||
self = .unknown
|
||||
return
|
||||
}
|
||||
self = .userError(error: error)
|
||||
|
||||
case .userCancelled:
|
||||
self = .userCancelled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,12 @@ public /*abstract*/ class BaseDataTransaction {
|
||||
|
||||
// MARK: Object management
|
||||
|
||||
|
||||
public func cancel() throws -> Never {
|
||||
|
||||
throw CoreStoreError.userCancelled
|
||||
}
|
||||
|
||||
/**
|
||||
Indicates if the transaction has pending changes
|
||||
*/
|
||||
|
||||
@@ -31,6 +31,79 @@ import CoreData
|
||||
|
||||
public extension DataStack {
|
||||
|
||||
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 }
|
||||
)
|
||||
transaction.transactionQueue.cs_async {
|
||||
|
||||
do {
|
||||
|
||||
let extraInfo = try task(transaction)
|
||||
transaction.commit { (result) in
|
||||
|
||||
switch result {
|
||||
|
||||
case .success:
|
||||
success(extraInfo)
|
||||
|
||||
case .failure(let error):
|
||||
failure(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
catch let error as CoreStoreError {
|
||||
|
||||
DispatchQueue.main.async { failure(error) }
|
||||
}
|
||||
catch let error {
|
||||
|
||||
DispatchQueue.main.async { failure(.userError(error: error)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func perform<T>(synchronous task: ((_ transaction: SynchronousDataTransaction) throws -> T), waitForObserverNotifications: Bool = true) throws -> T {
|
||||
|
||||
let transaction = SynchronousDataTransaction(
|
||||
mainContext: self.rootSavingContext,
|
||||
queue: self.childTransactionQueue,
|
||||
closure: { _ in }
|
||||
)
|
||||
return try transaction.transactionQueue.cs_sync {
|
||||
|
||||
let extraInfo: T
|
||||
do {
|
||||
|
||||
extraInfo = try task(transaction)
|
||||
}
|
||||
catch let error as CoreStoreError {
|
||||
|
||||
throw error
|
||||
}
|
||||
catch let error {
|
||||
|
||||
throw CoreStoreError.userError(error: error)
|
||||
}
|
||||
|
||||
let result = waitForObserverNotifications
|
||||
? transaction.commitAndWait()
|
||||
: transaction.commit()
|
||||
switch result {
|
||||
|
||||
case .success:
|
||||
return extraInfo
|
||||
|
||||
case .failure(let error):
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Begins a transaction asynchronously where `NSManagedObject` creates, updates, and deletes can be made.
|
||||
|
||||
@@ -83,7 +156,6 @@ public extension DataStack {
|
||||
Thread.isMainThread,
|
||||
"Attempted to refresh entities outside their designated queue."
|
||||
)
|
||||
|
||||
self.mainContext.refreshAndMergeAllObjects()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user