Get transaction that wraps a managed context #29

Closed
opened 2025-12-29 15:22:34 +01:00 by adam · 18 comments
Owner

Originally created by @mantas on GitHub (Jan 6, 2016).

I frequently bump into situation when I'd like to do smth in the context of a specific object. In pure CoreData, I'd run those operation on object.managedObjectContext and all is good. Now've to keep track of both the object and transaction it's in. This adds quite a a bit of unnecessary complexity to the code.

I see there's NSManagedObjectContext#parentTransaction method that does exactly what I was looking for. But it's marked as internal. Is there a reason why I shouldn't touch it? Is there some common pattern how to work around this issue?

Originally created by @mantas on GitHub (Jan 6, 2016). I frequently bump into situation when I'd like to do smth in the context of a specific object. In pure CoreData, I'd run those operation on object.managedObjectContext and all is good. Now've to keep track of both the object and transaction it's in. This adds quite a a bit of unnecessary complexity to the code. I see there's NSManagedObjectContext#parentTransaction method that does exactly what I was looking for. But it's marked as internal. Is there a reason why I shouldn't touch it? Is there some common pattern how to work around this issue?
adam added the enhancementfixedquestion labels 2025-12-29 15:22:34 +01:00
adam closed this issue 2025-12-29 15:22:34 +01:00
Author
Owner

@JohnEstropia commented on GitHub (Jan 6, 2016):

@mantas Part of the safety offered by CoreStore is that transactions guarantee that your NSManagedObjectContext operations always run on queues managed by CoreStore.

But you can use UnsafeDataTransaction for exceptional needs:

let transaction: UnsafeDataTransaction = CoreStore.beginUnsafe()
let context: NSManagedObjectContext = transaction.internalContext // This is what you want

As the name Unsafe suggests though, this basically lets you trade safety for flexibility. And you should still use transaction.commit() to save your changes (instead of context.save())

@JohnEstropia commented on GitHub (Jan 6, 2016): @mantas Part of the safety offered by CoreStore is that transactions guarantee that your NSManagedObjectContext operations always run on queues managed by CoreStore. But you can use `UnsafeDataTransaction` for exceptional needs: ``` swift let transaction: UnsafeDataTransaction = CoreStore.beginUnsafe() let context: NSManagedObjectContext = transaction.internalContext // This is what you want ``` As the name `Unsafe` suggests though, this basically lets you trade safety for flexibility. And you should still use `transaction.commit()` to save your changes (instead of `context.save()`)
Author
Owner

@JohnEstropia commented on GitHub (Jan 6, 2016):

@mantas I'm curious though, what tasks do you need to do where you need the context instance directly? If you can tell me your use cases I might be able to give you an alternative way to do with CoreStore (or even add a new feature for it)

@JohnEstropia commented on GitHub (Jan 6, 2016): @mantas I'm curious though, what tasks do you need to do where you need the context instance directly? If you can tell me your use cases I might be able to give you an alternative way to do with CoreStore (or even add a new feature for it)
Author
Owner

@mantas commented on GitHub (Jan 8, 2016):

@JohnEstropia I'm looking for a reverse operation. In fact, I see that it exists, but it's marked as internal. I wonder why it was marked for internal use only

let transaction: BaseDataTransaction = object.managedObjectContext.parentTransaction

I sometimes pass around an object and later needs to fetch different object in the same context. Lets say, I've a blog post and I may want to edit tags later down. I'd pass around the BlogPost object and get it's transaction whenever I need to fetch the tags. That works very well with CoreData - I can always do BlogPost#managedObjectContext. But since I can't get it's CoreStore transaction, I've to pass around both BlogPost and it's transaction.

@mantas commented on GitHub (Jan 8, 2016): @JohnEstropia I'm looking for a reverse operation. In fact, I see that it exists, but it's marked as internal. I wonder why it was marked for internal use only ``` swift let transaction: BaseDataTransaction = object.managedObjectContext.parentTransaction ``` I sometimes pass around an object and later needs to fetch different object in the same context. Lets say, I've a blog post and I may want to edit tags later down. I'd pass around the BlogPost object and get it's transaction whenever I need to fetch the tags. That works very well with CoreData - I can always do BlogPost#managedObjectContext. But since I can't get it's CoreStore transaction, I've to pass around both BlogPost and it's transaction.
Author
Owner

@JohnEstropia commented on GitHub (Jan 8, 2016):

@mantas Okay, I think you're asking the same thing as here: https://github.com/JohnEstropia/CoreStore/issues/34

Basically instead of passing around an object, you have to think of managing who owns that object instead. It's just either one of these:

  • The DataStack (where you fetch objects with CoreStore.fetch...(), yourDataStack.fetch...() or with ListMonitor and ObjectMonitor)
  • Asynchronous transactions (via CoreStore.beginAsynchronous(...)), but you can't bring this and its objects outside the transaction's closure
  • Synchronous transactions (via CoreStore.beginSynchronous(...)), same with above, you can't pass this outside its scope
  • Unsafe transactions (via CoreStore.beginUnsafe()). This is probably what you want and you should hold a strong reference to this transaction instead of the object/MOC. It's called "unsafe" because of the very nature that you can pass it across scopes, threads, and queues.
@JohnEstropia commented on GitHub (Jan 8, 2016): @mantas Okay, I think you're asking the same thing as here: https://github.com/JohnEstropia/CoreStore/issues/34 Basically instead of passing around an object, you have to think of managing **who owns** that object instead. It's just either one of these: - The `DataStack` (where you fetch objects with `CoreStore.fetch...()`, `yourDataStack.fetch...()` or with `ListMonitor` and `ObjectMonitor`) - Asynchronous transactions (via `CoreStore.beginAsynchronous(...)`), but you can't bring this and its objects outside the transaction's closure - Synchronous transactions (via `CoreStore.beginSynchronous(...)`), same with above, you can't pass this outside its scope - Unsafe transactions (via `CoreStore.beginUnsafe()`). This is probably what you want and you should hold a strong reference to this transaction instead of the object/MOC. It's called "unsafe" because of the very nature that you can pass it across scopes, threads, and queues.
Author
Owner

@markkrenek commented on GitHub (Jan 8, 2016):

This is similar to my question in #34. I was used to passing an object around, and then, any code that needed to modify that object would do something like this:

object.managedObjectContext.performBlock(...)

to guarantee thread/context safety.

I think the suggestion with CoreStore is to stop thinking about MOCs and use a transaction instead. AsynchronousDataTransaction and SynchronousDataTransaction are short-lived, available only during the closures of beginAsynchronous(_:) and beginSynchronous(_:). Longer-lived scenarios (which I think I want) need to use UnsafeDataTransaction, but it's still a bit unclear if I'm doing those completely safely.

@markkrenek commented on GitHub (Jan 8, 2016): This is similar to my question in #34. I was used to passing an object around, and then, any code that needed to modify that object would do something like this: ``` object.managedObjectContext.performBlock(...) ``` to guarantee thread/context safety. I think the suggestion with CoreStore is to stop thinking about MOCs and use a transaction instead. AsynchronousDataTransaction and SynchronousDataTransaction are short-lived, available only during the closures of `beginAsynchronous(_:)` and `beginSynchronous(_:)`. Longer-lived scenarios (which I **think** I want) need to use UnsafeDataTransaction, but it's still a bit unclear if I'm doing those completely safely.
Author
Owner

@mantas commented on GitHub (Jan 8, 2016):

@markkrenek looks like we're looking for the same bit :)

@JohnEstropia I'm aware of the CoreStore-y way to solve this. But, wether to my bad habits or app design, I still need to pass around the object itself. At the moment, I work around it by passing around both the object and the transaction. This is quite messy and easy to mess up.

Anyhow, my question is wether there're any purely technical reasons to keep NSManagedObjectContext#parentTransaction method as internal. I'd love to have it as a public method.

@mantas commented on GitHub (Jan 8, 2016): @markkrenek looks like we're looking for the same bit :) @JohnEstropia I'm aware of the CoreStore-y way to solve this. But, wether to my bad habits or app design, I still need to pass around the object itself. At the moment, I work around it by passing around both the object and the transaction. This is quite messy and easy to mess up. Anyhow, my question is wether there're any purely technical reasons to keep NSManagedObjectContext#parentTransaction method as internal. I'd love to have it as a public method.
Author
Owner

@JohnEstropia commented on GitHub (Jan 8, 2016):

@mantas The transactions own the MOCs, not the other way around. MOCs only keep a weak reference to their parent for things like asserting; it's not meant for any functional purpose. So you will still need to keep a strong reference to the transaction anyway.

Notice that if you use raw Core Data, this is still the exact same status quo with NSManagedObject and NSManagedObjectContext: the former only keeps an unowned(unsafe) reference to the latter, and you'll need to have a strong reference of the MOC somewhere else.

@JohnEstropia commented on GitHub (Jan 8, 2016): @mantas The transactions own the MOCs, not the other way around. MOCs only keep a `weak` reference to their parent for things like asserting; it's not meant for any functional purpose. So you will still need to keep a strong reference to the transaction anyway. Notice that if you use raw Core Data, this is still the exact same status quo with NSManagedObject and NSManagedObjectContext: the former only keeps an `unowned(unsafe)` reference to the latter, and you'll need to have a strong reference of the MOC somewhere else.
Author
Owner

@mantas commented on GitHub (Jan 8, 2016):

@JohnEstropia I'm aware of that. But I prefer to have the strong reference somewhere up in the parent and pass down the object only, instead of passing down both transaction and object. That works pretty well with pure CoreData.

Anyhow, would you be interested in making that parentTransaction method public? That's cool if you prefer to keep it internal for one reason or another.

@mantas commented on GitHub (Jan 8, 2016): @JohnEstropia I'm aware of that. But I prefer to have the strong reference somewhere up in the parent and pass down the object only, instead of passing down both transaction and object. That works pretty well with pure CoreData. Anyhow, would you be interested in making that parentTransaction method public? That's cool if you prefer to keep it internal for one reason or another.
Author
Owner

@JohnEstropia commented on GitHub (Jan 8, 2016):

@mantas Would it work for your use-case if I expose a var parentTransaction: BaseDataTransaction? property from the NSManagedObject instead? (I really don't prefer doing things via MOCs directly)

And another thing, BaseDataTransaction does not have its own commit() method. You'll either have to cast to the specific transaction type or to do commits from the original transaction reference.

@JohnEstropia commented on GitHub (Jan 8, 2016): @mantas Would it work for your use-case if I expose a `var parentTransaction: BaseDataTransaction?` property from the NSManagedObject instead? (I really don't prefer doing things via MOCs directly) And another thing, `BaseDataTransaction` does not have its own `commit()` method. You'll either have to cast to the specific transaction type or to do commits from the original transaction reference.
Author
Owner

@mantas commented on GitHub (Jan 8, 2016):

@JohnEstropia that'd be great. Exposing it on NSManagedObjectContext might be more flexible though.

@mantas commented on GitHub (Jan 8, 2016): @JohnEstropia that'd be great. Exposing it on NSManagedObjectContext might be more flexible though.
Author
Owner

@JohnEstropia commented on GitHub (Jan 10, 2016):

@mantas Can you elaborate how it can be more flexible? Using MOCs is just trying to fight CoreStore's framework design, which tries to prevent MOC access. If there's a functionality that MOCs can do that transactions can't, please tell so I can add methods to the transactions.

@JohnEstropia commented on GitHub (Jan 10, 2016): @mantas Can you elaborate how it can be more flexible? Using MOCs is just trying to fight CoreStore's framework design, which tries to prevent MOC access. If there's a functionality that MOCs can do that transactions can't, please tell so I can add methods to the transactions.
Author
Owner

@mantas commented on GitHub (Jan 10, 2016):

@JohnEstropia I'm talking about purely hypothetical case there :)

Let's say someone needs to create/fetch object without CoreStore. Wether pure CoreData or some other library. I assume CoreStore's transaction wouldn't be possible on that NSManagedObject. But someone might create a transaction and then do what he needs using context wrapped in that transaction. NSManagedObject still wouldn't have the transaction, but NSManagedObjectContext would.

let transaction = UnsafeDataTransaction()
let object = SomeWeirdStuff(transaction.managedObjectContext)
object.parentTransaction // nil
object.managedObjectContext?.parentTransaction // Some
@mantas commented on GitHub (Jan 10, 2016): @JohnEstropia I'm talking about purely hypothetical case there :) Let's say someone needs to create/fetch object without CoreStore. Wether pure CoreData or some other library. I assume CoreStore's transaction wouldn't be possible on that NSManagedObject. But someone might create a transaction and then do what he needs using context wrapped in that transaction. NSManagedObject still wouldn't have the transaction, but NSManagedObjectContext would. ``` swift let transaction = UnsafeDataTransaction() let object = SomeWeirdStuff(transaction.managedObjectContext) object.parentTransaction // nil object.managedObjectContext?.parentTransaction // Some ```
Author
Owner

@JohnEstropia commented on GitHub (Jan 14, 2016):

@mantas I pushed an update, but do tell if you hit a use case where the solution provided was not enough.

public extension NSManagedObject {
    /**
     Returns this object's parent `UnsafeDataTransaction` instance if it was created from one. Returns `nil` if the parent transaction is either an `AsynchronousDataTransaction` or a `SynchronousDataTransaction`, or if the object is not managed by CoreStore.

     When using an `UnsafeDataTransaction` and passing around a temporary object, you can use this property to execute fetches and updates to the transaction without having to pass around both the object and the transaction instances.

     Note that the internal reference to the transaction is `weak`, and it is still the developer's responsibility to retain a strong reference to the `UnsafeDataTransaction`.
     */
    public var unsafeDataTransaction: UnsafeDataTransaction? {

        return self.managedObjectContext?.parentTransaction as? UnsafeDataTransaction
    }

As long as an object was created from an NSManagedObjectContext managed by an UnsafeDataTransaction and that the transaction is not yet released, you are guaranteed that object.unsafeDataTransaction will not return nil.

@JohnEstropia commented on GitHub (Jan 14, 2016): @mantas I pushed an update, but do tell if you hit a use case where the solution provided was not enough. ``` swift public extension NSManagedObject { /** Returns this object's parent `UnsafeDataTransaction` instance if it was created from one. Returns `nil` if the parent transaction is either an `AsynchronousDataTransaction` or a `SynchronousDataTransaction`, or if the object is not managed by CoreStore. When using an `UnsafeDataTransaction` and passing around a temporary object, you can use this property to execute fetches and updates to the transaction without having to pass around both the object and the transaction instances. Note that the internal reference to the transaction is `weak`, and it is still the developer's responsibility to retain a strong reference to the `UnsafeDataTransaction`. */ public var unsafeDataTransaction: UnsafeDataTransaction? { return self.managedObjectContext?.parentTransaction as? UnsafeDataTransaction } ``` As long as an object was created from an `NSManagedObjectContext` managed by an `UnsafeDataTransaction` and that the transaction is not yet released, you are guaranteed that `object.unsafeDataTransaction` will not return `nil`.
Author
Owner

@mantas commented on GitHub (Jan 14, 2016):

thank you!

@mantas commented on GitHub (Jan 14, 2016): thank you!
Author
Owner

@mantas commented on GitHub (Jan 17, 2016):

@JohnEstropia looks like I bumped into a very similar issue once again.

I've an object "page" and it has has_many relationship to "section". I want to have a methods Page#custom_get_sections. It'd be more or less an enhanced Page#sections with custom ordering/filtering/whatever. With CoreData, I'd get self.managedObjectContext and fetch sections using the same context as the parent page. However, that doesn't work with CoreStore.

@mantas commented on GitHub (Jan 17, 2016): @JohnEstropia looks like I bumped into a very similar issue once again. I've an object "page" and it has has_many relationship to "section". I want to have a methods Page#custom_get_sections. It'd be more or less an enhanced Page#sections with custom ordering/filtering/whatever. With CoreData, I'd get self.managedObjectContext and fetch sections using the same context as the parent page. However, that doesn't work with CoreStore.
Author
Owner

@JohnEstropia commented on GitHub (Jan 18, 2016):

@mantas You should be able to fetch using the transaction:

self.unsafeDataTransaction?.fetchXXXX(...)
@JohnEstropia commented on GitHub (Jan 18, 2016): @mantas You should be able to fetch using the transaction: ``` self.unsafeDataTransaction?.fetchXXXX(...) ```
Author
Owner

@mantas commented on GitHub (Jan 18, 2016):

@JohnEstropia what if I'd do smth like this:

let object = CoreStore.fetchOne(From(Object))
object.custom_children_fetcher()

It looks like unsafeDataTransaction is not available then

@mantas commented on GitHub (Jan 18, 2016): @JohnEstropia what if I'd do smth like this: ``` swift let object = CoreStore.fetchOne(From(Object)) object.custom_children_fetcher() ``` It looks like unsafeDataTransaction is not available then
Author
Owner

@JohnEstropia commented on GitHub (Jan 19, 2016):

@mantas There is no "transaction" in that case. The fetch is done from a DataStack. So I think what you wanted to do is to converge these two operations

dataStack.fetchXXX(...) // exposed for reading
transaction.fetchXXX(...) // exposed for editing

into one

context.fetchXXX(...)

for convenience.

I know this should be easy (just expose the internal MOC methods as public), but I really think this is against what CoreStore is trying to do:

  • abstracting-out MOC use
  • making API consumers always conscious wether objects are being operated for reading or for writing (e.g. require fetches be done through the DataStacks/BaseDataTransactions)

Another thing, exposing fetch() methods on MOC is inconsistent API-wise; the behavior is undefined on MOC's not managed by CoreStore.

My suggestion is to either:

  1. Write custom_children_fetcher() as extension methods on both DataStack and BaseDataTransaction that receives the object as argument. OR
  2. Create your own custom FetchClause (this is how we normally approach similar cases in our projects)
struct ChildrenOf: FetchClause {
    init(parent: NSManagedObject) {
        // ...
    }
    func applyToFetchRequest(fetchRequest: NSFetchRequest) {
        fetchRequest.predicate = NSPredicate(format: "parent == %@", self.parent)
        fetchRequest.sortDescriptors = //...
        // etc
    }
}

Usage:

let object = CoreStore.fetchOne(From(Object))
let children = CoreStore.fetchAll(From(Child), ChildrenOf(object))
// let children = transaction.fetchAll(From(Child), ChildrenOf(object))
@JohnEstropia commented on GitHub (Jan 19, 2016): @mantas There is no "transaction" in that case. The fetch is done from a `DataStack`. So I think what you wanted to do is to converge these two operations ``` swift dataStack.fetchXXX(...) // exposed for reading transaction.fetchXXX(...) // exposed for editing ``` into one ``` swift context.fetchXXX(...) ``` for convenience. I know this should be easy (just expose the internal MOC methods as public), but I really think this is against what CoreStore is trying to do: - abstracting-out MOC use - making API consumers always conscious wether objects are being operated for reading or for writing (e.g. require fetches be done through the DataStacks/BaseDataTransactions) Another thing, exposing fetch() methods on MOC is inconsistent API-wise; the behavior is undefined on MOC's not managed by CoreStore. My suggestion is to either: 1. Write `custom_children_fetcher()` as extension methods on both `DataStack` _and_ `BaseDataTransaction` that receives the `object` as argument. OR 2. Create your own custom `FetchClause` (this is how we normally approach similar cases in our projects) ``` swift struct ChildrenOf: FetchClause { init(parent: NSManagedObject) { // ... } func applyToFetchRequest(fetchRequest: NSFetchRequest) { fetchRequest.predicate = NSPredicate(format: "parent == %@", self.parent) fetchRequest.sortDescriptors = //... // etc } } ``` Usage: ``` swift let object = CoreStore.fetchOne(From(Object)) let children = CoreStore.fetchAll(From(Child), ChildrenOf(object)) // let children = transaction.fetchAll(From(Child), ChildrenOf(object)) ```
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/CoreStore#29