Pattern for editing a model object #30

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

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

What's the suggested pattern for editing an NSManagedObject in a cancelable view controller? View controller A displays a list of model objects. The user selects one and then view controller B is displayed for editing the object. In pure Core Data, I pass the object to B, it creates its own child MOC, and performs edits on the object in that context. If the user cancels, the MOC is allowed to be released and no changes are saved. If the user presses Save, the child MOC is saved, and recursively saves until committed to the store.

(This is the pattern I've used in the past, where I directly edit an NSManagedObject in a temporary MOC as the user makes changes. The alternative is to maintain the edited values in local vars of the view controller and then push those changes onto the NSManagedObject only when the user presses Save. I find the former to be easier when edits might have ripple effects through relationships or computed values.)

Originally created by @markkrenek on GitHub (Jan 6, 2016). What's the suggested pattern for editing an NSManagedObject in a cancelable view controller? View controller A displays a list of model objects. The user selects one and then view controller B is displayed for editing the object. In pure Core Data, I pass the object to B, it creates its own child MOC, and performs edits on the object in that context. If the user cancels, the MOC is allowed to be released and no changes are saved. If the user presses Save, the child MOC is saved, and recursively saves until committed to the store. (This is the pattern I've used in the past, where I directly edit an NSManagedObject in a temporary MOC as the user makes changes. The alternative is to maintain the edited values in local vars of the view controller and then push those changes onto the NSManagedObject only when the user presses Save. I find the former to be easier when edits might have ripple effects through relationships or computed values.)
adam added the question label 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 7, 2016):

@markkrenek This is exactly the use-case that UnsafeDataTransactions were designed for:

let transaction: UnsafeDataTransaction = CoreStore.beginUnsafe()
self.transaction = transaction // keep a strong reference in your UIViewController

// Later...
self.temporaryObject = self.transaction.create(Into(YourEntity))
// or
self.temporaryObject = self.transaction.fetch(From(YourEntity), Where( /* ... */ ))

 // ... make updates
self.temporaryObject.yourProperty = // ... 

// When done ...
self.transaction.commit { result in
    // ...
}

If you want to cancel changes, just don't call commit() on the transaction.

@JohnEstropia commented on GitHub (Jan 7, 2016): @markkrenek This is exactly the use-case that `UnsafeDataTransaction`s were designed for: ``` swift let transaction: UnsafeDataTransaction = CoreStore.beginUnsafe() self.transaction = transaction // keep a strong reference in your UIViewController // Later... self.temporaryObject = self.transaction.create(Into(YourEntity)) // or self.temporaryObject = self.transaction.fetch(From(YourEntity), Where( /* ... */ )) // ... make updates self.temporaryObject.yourProperty = // ... // When done ... self.transaction.commit { result in // ... } ``` If you want to cancel changes, just don't call `commit()` on the transaction.
Author
Owner

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

Thanks. I'm still wrapping my head around some of the abstraction that CoreStore is providing. I had not thought to use UnsafeDataTransaction simply because of the "Unsafe" name. It sounds like as long as I make changes to the object only from the main thread, I'll be OK.

@markkrenek commented on GitHub (Jan 7, 2016): Thanks. I'm still wrapping my head around some of the abstraction that CoreStore is providing. I had not thought to use UnsafeDataTransaction simply because of the "Unsafe" name. It sounds like as long as I make changes to the object only from the main thread, I'll be OK.
Author
Owner

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

All transactions create their own temporary MOC and UnsafeDataTransaction lets you use it from any thread/queue. It's named "unsafe" because CoreStore cannot track changes made concurrently from other transactions. If your app does not update objects in the background then it should be fine.

Just make sure that you do not interact objects fetched/created from different contexts.
For example, don't do this:

let object = CoreStore.fetchOne(From(SomeEntity), Where(...)) // fetched from the main context

let another = self.transaction.fetchOne(From(AnotherEntity), Where(...)) // fetched from temporary context
another.myRelationship = object // BAD!

You'll need to do this:

another.myRelationship = self.transaction.fetchExisting(object) // OK!
@JohnEstropia commented on GitHub (Jan 7, 2016): All transactions create their own temporary MOC and UnsafeDataTransaction lets you use it from any thread/queue. It's named "unsafe" because CoreStore cannot track changes made concurrently from other transactions. If your app does not update objects in the background then it should be fine. Just make sure that you do not interact objects fetched/created from different contexts. For example, don't do this: ``` swift let object = CoreStore.fetchOne(From(SomeEntity), Where(...)) // fetched from the main context let another = self.transaction.fetchOne(From(AnotherEntity), Where(...)) // fetched from temporary context another.myRelationship = object // BAD! ``` You'll need to do this: ``` swift another.myRelationship = self.transaction.fetchExisting(object) // OK! ```
Author
Owner

@markst commented on GitHub (Oct 25, 2018):

Hey @markkrenek. I hope it's alright to piggyback off your previous question!

I've a similar pattern to you with regard to having a cancelable view controller.
Let's say I reuse a view controller which can both create & edit a 'customer' details.

I'd like to know how you handle logic for creating the Customer model object on creation & if updating a previous object?

Here's what I have currently, any input much appreciated! :

@IBAction func updateCustomerAction(_ sender: LoadingButton) {

    if !self.customerDetailsPanel.isValid(isDeferred: false) {
        // Early return
        return
    }

    self.transaction = CoreStore.beginUnsafe()
    
    guard let customer:Customer = {
        if let existingCustomer = self.customer {
            return self.transaction?.edit(existingCustomer)
        } else {
            return self.transaction?.create(Into<Customer>())
        }
        }() else {
            // Report error
            return
    }

    customer.name  = customerDetailsPanel.customerNameField.text
    customer.email = customerDetailsPanel.emailField.text
    customer.notes = notesPanel.notesTextView.text
    customer.phone = customerDetailsPanel.phoneNumberField.text
    customer.otherNumber = customerDetailsPanel.otherNumberField.text

    sender.showLoadingView()

    self.transaction?.commit { [weak self] result in
        if let error = result {
            // Report error
            print("Error - \(error)")
        } else {
            self?.navigationController?.popViewController(animated: true)
        }
    }
}
@markst commented on GitHub (Oct 25, 2018): Hey @markkrenek. I hope it's alright to piggyback off your previous question! I've a similar pattern to you with regard to having a cancelable view controller. Let's say I reuse a view controller which can both create & edit a 'customer' details. I'd like to know how you handle logic for creating the Customer model object on creation & if updating a previous object? Here's what I have currently, any input much appreciated! : ```swift @IBAction func updateCustomerAction(_ sender: LoadingButton) { if !self.customerDetailsPanel.isValid(isDeferred: false) { // Early return return } self.transaction = CoreStore.beginUnsafe() guard let customer:Customer = { if let existingCustomer = self.customer { return self.transaction?.edit(existingCustomer) } else { return self.transaction?.create(Into<Customer>()) } }() else { // Report error return } customer.name = customerDetailsPanel.customerNameField.text customer.email = customerDetailsPanel.emailField.text customer.notes = notesPanel.notesTextView.text customer.phone = customerDetailsPanel.phoneNumberField.text customer.otherNumber = customerDetailsPanel.otherNumberField.text sender.showLoadingView() self.transaction?.commit { [weak self] result in if let error = result { // Report error print("Error - \(error)") } else { self?.navigationController?.popViewController(animated: true) } } } ```
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/CoreStore#30