Core Data Concurrency on iOS 9 - ios

If I create a NSManagedObjectContext on the main thread with NSMainQueueConcurrencyType must I use performBlock() method for all the save and performFetch methods. IE is it possible to do the following:
do {
managedObjectContext.save()
} catch let error as NSError {
print(error)
}
or should I always do this:
managedObjectContext.performBlock({
do {
managedObjectContext.save()
} catch let error as NSError {
print(error)
}
})
If I understand the documentation correctly I always have to use performBlock() or performBlockAndWait() but in the template code from XCode 7 they are not using blocks. Any help is appreciated!

If you are already on the main thread and have a NSMainQueueConcurrencyType context, you do not need to use performBlock.

Related

Swift - How to properly get data from REST API to Label in ViewController

I am trying to create an iOS app to get data from API that I want to show the user in a Label.
So far I have this:
func getJoke(completion: #escaping (ChuckNorrisResponse) -> ()) {
URLSession.shared.dataTask(with: URL(string: url)!) { data, response, error in
guard let data = data, error == nil else {
print("Something fucked up")
return
}
var result: ChuckNorrisResponse?
do {
result = try JSONDecoder().decode(ChuckNorrisResponse.self, from: data)
} catch {
print("Fucked up to convert \(error.localizedDescription)")
}
guard let joke = result else {
return
}
completion(joke)
}.resume()
}
And in the ViewController
func setNewJoke() {
jokesProvier.getJoke { joke in
self.JokeLabel.text = joke.value
}
But it doesn't like that I try to edit the text in the Label inside the closure.
It shows error - UILabel.Text must be used from main thread only.
I cannot find anywhere how should I do this properly so it works.
Thanks in advance
Basically, as Aaron stated - you have to pass the closure to the main thread with DispatchQueue.main.async. The reason is that URLSession.shared.dataTask completionHandler runs on the thread different from main and self.JokeLabel.text = joke.value is an UI update - you're changing the text on the label, and UIKit requires you to update the screen on the main thread! That's why you have to pass label update to the main thread. Trying to update it on the thread different from main will result in undefined behaviour - it may work, it may freeze, it may crash.So, whenever you're doing something on the background thread and at some point want to update the UI always pass this job to the main thread Hope this helps
I'd recommend calling the completion handler on the main thread. So instead of
completion(joke)
do
DispatchQueue.main.async { completion(joke) }

CoreData multithreading fetch requires small delay before accessing attributes

I use coreData as persistent store.
To read data, I use (only essential parts are shown):
func fetchShoppingItems(completion: #escaping (Set<ShoppingItem>?, Error?) -> Void) {
persistentContainer.performBackgroundTask { (backgroundManagedContext) in
let fetchRequest: NSFetchRequest<CDShoppingItem> = CDShoppingItem.fetchRequest()
var shoppingItems: Set<ShoppingItem> = []
do {
let cdShoppingItems: [CDShoppingItem] = try backgroundManagedContext.fetch(fetchRequest)
for nextCdShoppingItem in cdShoppingItems {
nextCdShoppingItem.managedObjectContext!.performAndWait {
Thread.sleep(forTimeInterval: 0.1) // This seems to be required
let nextShoppingItem = ShoppingItem.init(name: nextCdShoppingItem.name!)
shoppingItems.insert(nextShoppingItem)
} // performAndWait
} // for all cdShoppingItems
completion(shoppingItems, nil)
return
} catch let error as NSError {
completion(nil, error)
return
} // fetch error
} // performBackgroundTask
} // fetchShoppingItems
To test the coreData implementation, I wrote a unit test that creates multiple threads that write to and read from coreData concurrently.
This test runs only successfully, if the instruction
Thread.sleep(forTimeInterval: 0.1) // This seems to be required
is inserted in the performAndWait closure.
If it is commented out, nextCdShoppingItem is often read back with nil attributes, and the function crashes due to the forced unwrap.
I am not sure, if nextCdShoppingItem.managedObjectContext!.performAndWait is correct or if I had to use backgroundManagedContext.performAndWait, but with backgroundManagedContext the effect is the same.
I do not understand why inserting a small delay before accessing an attribute of a managed object is necessary to avoid the problem.
Any hint is welcome!
EDIT:
I investigated the issue further, and found the following:
Every time nextCdShoppingItem is read back by the background thread (called read thread below) as nil, there is also another background thread that tries to save its own managedContext after all records in its managedContext have been deleted (called write thread below).
Apparently the read thread tries to fetch a record that has just been deleted by the write thread.
So the problem is definitively a multithreading issue, and I found a solution (see my answer below).
performAndWait will add the block to the queue and schedule it to run, just like perform, but performAndWait will not return until the block is complete. Since you are inside a loop of cdShoppingItems, the loop does not stop and wait for the block to return. By adding the thread sleep, you are essentially slowing down the loop and giving core data enough time to complete its fetch. The forced unwrap crash is probably an indication that it's lost its nextCdShoppingItem reference.
I would consider refactoring where you do not need to query core data inside a loop. If it's possible, add the name attribute to CDShoppingItem so you don't have to fetch it to build a ShoppingItem object.
Edit: took a stab at a refactor although I don't know your exact use case:
func fetchShoppingItems(completion: #escaping (Set<ShoppingItem>?, Error?) -> Void) {
persistentContainer.performBackgroundTask { (backgroundManagedContext) in
let fetchRequest: NSFetchRequest<CDShoppingItem> = CDShoppingItem.fetchRequest()
do {
var shoppingItems: Set<ShoppingItem> = []
let cdShoppingItems: [CDShoppingItem] = try backgroundManagedContext.fetch(fetchRequest)
for nextCdShoppingItem in cdShoppingItems {
if let name = nextCdShoppingItem.name {
let nextShoppingItem = ShoppingItem.init(name: name)
shoppingItems.insert(nextShoppingItem)
}
}
completion(shoppingItems, nil)
} catch let error as NSError {
print("Error fetching CDShoppingItem: \(error)")
completion(nil, error)
} // fetch error
return
} // performBackgroundTask
} // fetchShoppingItems
To prevent the multithreading issue, I tried 2 things:
1) Since iOS10, a persistentStore of SQL type maintains a connection pool for concurrent access to the pool, and it is possible to set a maximum pool size, see the WWDC video. I did so using
private lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: modelName)
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// ...
} else {
storeDescription.setOption(NSNumber(1), forKey: NSPersistentStoreConnectionPoolMaxSizeKey)
}
})
return container
}()
to prevent concurrent access to the persistentStore. Unfortunately, this did not solve the problem for an unknown reason.
2) I then tried to serialize read and write operations by setting up a serial queue:
private let coreDataSerialQueue = DispatchQueue(label: "com.xxx.ShopEasy.coreDataManager") // serial by default
It is used for the read and write accesses in the following way:
coreDataSerialQueue.async {
let backgroundManagedContext = self.persistentContainer.newBackgroundContext()
backgroundManagedContext.performAndWait {
// …
} // performAndWait
} // coreDataSerialQueue.async
This did solve the problem.
Please note that it would be wrong to use
coreDataSerialQueue.async {
self.persistentContainer.performBackgroundTask { (backgroundManagedContext) in
// …
} // performBackgroundTask
} // coreDataSerialQueue.async
because performBackgroundTask would fork another asynchronous thread and thus break the serialization.

How can i save objects to core data without leaks

I have almost the same core data stack #2 : http://floriankugler.com/2013/04/29/concurrent-core-data-stack-performance-shootout/ . (mine is singleton)
After i save on worker moc (last moc) i get leaks (leaks app_name in terminal).
This is my save function:
func saveToLast(moc: NSManagedObjectContext?) throws{
if let moc = moc {
moc.performAndWait({ _ in
if moc.hasChanges {
do {
try moc.save()
if let parentMoc = moc.parent {
try self.saveToLast(moc: parentMoc)
}
}
catch {
}
}
})
}
}
if i keep performAndWait i know sooner or later i will create a deadlock and if i put perform theres no guarantee the background thread saves on worker before main thread (in order).
Can anyone explain me how can i save with serial queues or any good alternative to what i wrote?

NSManagedObjectContext.performBlockAndWait() runs the block on main thread/queue

print("queue1: \(NSOperationQueue.currentQueue())")
managedObjectContext.performBlockAndWait({
print("queue2: \(NSOperationQueue.currentQueue())")
})
and output is:
queue1: Optional(<NSOperationQueue: 0x7fa31c030cf0>{name = 'NSOperationQueue 0x7fa31c030cf0'})
queue2: Optional(<NSOperationQueue: 0x7fa319d114d0>{name = 'NSOperationQueue Main Queue'})
So, performBlockAndWait runs the block in main thread. Don't know why?
Is this the expected behaviour?
So, any solutions for the problems:
I need to run the code of the block in background, else it freezes UI. So, how can I?, or
If I don't use performBlockAndWait the code in the block crash with the description Terminating app due to uncaught exception NSInvalidArgumentException, reason _referenceData64 only defined for abstract class. Any alternative?
PerformBlock() and performBlockAndWait() runs on the queue in which your context is created. So if your managedObjectContext is created in main queue it will run in the main queue.
If you want background execution you need to create a background context :
let privateContext = NSManagedObjectContext(
concurrencyType: .PrivateQueueConcurrencyType)
privateContext.persistentStoreCoordinator = mainQueueContext.persistentStoreCoordinator
privateContext.performBlock {
...
}
Perform your task you want to do in background in a separate function with a completion handler.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
func performFuncWithCompletion() { (Success) in
if (Success)
NSOperationQueue.mainQueue().addOperationWithBlock {
// update UI here
}
else {
// Show error message?
}
}
}
func performFuncWithCompletion(completion : (SuccessStatus: Bool)) {
// perform your back ground function here.
// Decide if everything went fine
if everythingWentFine {
completion(true)
}
else {
completion(false)
}
}
Ask if any questions

Coredata concurrency issues

I have a NSOperation subclass that has its private context, and a singleton data manager class that has a context on main queue. All the UI and crud operation are done by this singleton class and a background fetch from cloud kit is done by the NSOperation subclass. Had few doubts as below.
Following is the code i have in NSoperation subclass.Can below code create deadlock?
self.localStoreMOC?.performBlockAndWait({ () -> Void in
//Long process of fetching data from cloud and pushing changes to cloud happens here.
var error:NSErrorPointer = nil
if self.localStoreMOC!.hasChanges
{
do
{
try self.localStoreMOC!.save()
}
catch let error1 as NSError
{
error.memory = error1
}
if error == nil
{
self.localStoreMOC!.parentContext!.performBlockAndWait({
do
{
try self.localStoreMOC!.parentContext!.save()
}
catch let error1 as NSError
{
print("wasSuccessful error1 \(error1)")
}
})
}
}
}
If i have a another singleton class using this class NSManagedOBject do i need to pass them though ID ?
First, you need to turn on -com.apple.CoreData.ConcurrencyDebug 1 in your run time arguments. That will help insure you are calling everything on the proper thread/queue.
Second, you are doing a lot of forced unwrapping of optionals, that is a very bad habit to be in. Best to unwrap them properly or use the optional unwrapping.
Third, what happens when you pause the debugger? Where is the line of code that it is pausing on and what queues are you on?
Just turning on the concurrency debug will most likely show you your issue.
Item 2
If you are wanting to pass a reference to a NSManagedObject from one context to another then yes, you need to use the NSManagedObjectID as the NSManagedObject is not safe to pass between contexts.
Code Formatting
Was playing with the formatting a bit, the results may be of interest to you:
guard let local = localStoreMOC else { fatalError("Local store is nil") }
guard let parent = local.parentContext else { fatalError("Parent store is nil") }
local.performBlockAndWait {
//Long process of fetching data from cloud and pushing changes to cloud happens here.
if !local.hasChanges { return }
do {
try local.save()
parent.performBlockAndWait {
do {
try parent.save()
} catch {
print("wasSuccessful error1 \(error)")
}
}
} catch {
print("Failed to save local: \(error)")
}
}
This removes the forced unwrapping of optionals and prints both errors out if you get one in either case.
Update
Also, some developers, say that nested performblockandwait like above will cause deadlock.
performBlockAndWait will never cause a deadlock. It is more intelligent than that.
If you are going from one queue to another, then you want each queue to block and wait like the code describes.
If you were on the right queue and called performBlockAndWait then the call would effectively be a no-op and will NOT deadlock

Resources