Closed justinmakaila closed 8 years ago
Hi Justin,
This looks really interesting. What were you trying to achieve when you got this bug?
@3lvis So, here's my use case:
In the application I'm building, a user can take a picture, which is transformed into a Photo
entity on the main context. That photo entity is saved. When changes happen in a context (observing the notification that i put in my PR ;)) I kick of a process in a background context, which is managed by a SyncManager
, but it's created from DATAStack
(similar to the example above, where there is a _backgroundContext
on the view controller). That context queries for all Photo
s matching a predicate, and then sends the work off onto a network request. When the request comes back, I merge the JSON into the model, and save the object on the background context.
What I Expected: I expected the changes made in the background context to be merged into the main context, and the updates to be available .
What Happens:
The observer in DATAStack
fires, "merging" the changes into the main context. However, calling save on the main context does not make the changes available in the main context, nor does persisting the stack with persistWithCompletion
. If I query for Photo
s on the main context, only the unchanged version shows up. If the app is killed and relaunched, the changes are available on the main thread.
Any thoughts? I've been poking around the source a little more and playing with some of the NSManagedObjectContext
configuration variables, but there's not much there.
@3lvis I'm putting together a better example. I'll fork and link to it.
https://github.com/justinmakaila/DATAStack.git
^ See the Swift example on master
Two things to try in the example:
Example One:
Example Two:
editUsers
function to use dataStack.mainContext
Making the new background context a child of the parent makes both examples work just fine, regardless of queue.
Thanks for explaining your issue and for taking the time to make an example to show your problem. There was people reporting that mutating data in the main thread is not working correctly, personally I think this is something that shouldn't even be allowed, blocking the main thread is 📛 you could drop a few frames because of it 😱 Anyway, I understand that even though is not recommended, some developers might still want to do it and as you mentioned there are a few fixes that could be applied to DATASource in order to improve mutating data in the main thread but I would prefer to keep the implementation of DATASource optimized for "best practices" and keep it lean instead.
Here's what other users reported: https://github.com/3lvis/DATAStack/issues/28
@3lvis But the issue isn't with the data created on the main context, it's with the data that's mutated in the background, and saved to the persistent store. The main context is never updated about these changes, and it appears as if there's no way to retrieve them.
I see, thanks for clarifying that, I'll have a look.
It works.
import UIKit
import CoreData
import DATASource
class ViewController: UITableViewController {
var dataStack: DATAStack
lazy var dataSource: DATASource = {
let request: NSFetchRequest = NSFetchRequest(entityName: "User")
request.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)]
let dataSource = DATASource(tableView: self.tableView, cellIdentifier: "Cell", fetchRequest: request, mainContext: self.dataStack.mainContext, configuration: { cell, item, indexPath in
if let name = item.valueForKey("name") as? String, createdDate = item.valueForKey("createdDate") as? NSDate, score = item.valueForKey("score") as? NSNumber {
cell.textLabel?.text = name + " - " + score.description
}
})
return dataSource
}()
init(dataStack: DATAStack) {
self.dataStack = dataStack
super.init(style: .Plain)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "Cell")
self.tableView.dataSource = self.dataSource
let backgroundButton = UIBarButtonItem(title: "Background", style: .Done, target: self, action: #selector(ViewController.createBackground))
self.navigationItem.rightBarButtonItem = backgroundButton
let editButton = UIBarButtonItem(title: "Edit", style: .Done, target: self, action: #selector(ViewController.editUsersInContext))
self.navigationItem.leftBarButtonItem = editButton
}
func createBackground() {
self.dataStack.performInNewBackgroundContext { _backgroundContext in
let entity = NSEntityDescription.entityForName("User", inManagedObjectContext: _backgroundContext)!
let object = NSManagedObject(entity: entity, insertIntoManagedObjectContext: _backgroundContext)
object.setValue("Background", forKey: "name")
object.setValue(NSDate(), forKey: "createdDate")
object.setValue(0, forKey: "score")
try! _backgroundContext.save()
self.dataStack.persistWithCompletion()
}
}
func editUsersInContext() {
self.dataStack.performInNewBackgroundContext { backgroundContext in
let fetchRequest = NSFetchRequest(entityName: "User")
let users = try! backgroundContext.executeFetchRequest(fetchRequest)
for user in users {
let score = user.valueForKey("score") as! NSNumber
let newScore = NSNumber(int: score.integerValue + 1)
user.setValue(newScore, forKey: "score")
}
try! backgroundContext.save()
self.dataStack.persistWithCompletion()
}
}
}
Closing since sample above fixes the problem.
I'm not sure what's going on, but I've been blocked by this issue for >= 48 hours. It appears as if none of the insertions on any context are making it to the persistent store coordinator. I've tried everything from changing the
mergePolicy
, to manually observing contexts. For whatever reason, changes made in a background context, even after invokingpersistWithCompletion
, never update the main context, even after explicit fetches until the application is killed and restarted.I've tweaked the
DemoSwift.ViewController
to demonstrate the issue:In this example, pressing the "Main" button will create a new
User
with ascore
of 0. ThatUser
will not be modified by theeditMainObject
function when the "Edit Main Object" button is pressed.If the app is killed and restarted, the "Edit Main Object" button will cause the objects to be modified and updated.
I'm really not sure how to proceed, or if I'm doing something wrong.