Saving NSManagedObjectContext without falling into the main thread

What am I trying to do:

  • Synchronize the background with the web API without freezing the user interface. I use MagicalRecord , but it is not very specific to it.
  • make sure i use contexts and so correctly

Actually my question is: do I understand correctly? Plus a couple of questions at the end.

So, the contexts provided by MagicalRecord are as follows:

  • MR_rootSavingContext from PrivateQueueConcurrencyType , which is used to save data to storage, which is a slow process
  • MR_defaultContext MainQueueConcurrencyType
  • and for the background you would like to work with the context created by MR_context () , which is a child of the MR_defaultContext file and has PrivateQueueConcurrencyType

Now for saving in asynchronous mode we have two options:

  • MR_saveToPersistentStoreWithCompletion () , which will save everything up to MR_rootSavingContext and write to disk
  • MR_saveOnlySelfWithCompletion () , which will save only to the parent context (for example, MR_defaultContext for a context created using MR_context)

From there, I thought that I could do the following (let me call it "Attempt No. 1") without freezing the user interface:

let context = NSManagedObjectContext.MR_context() for i in 1...1_000 { let user = User.MR_createInContext(context) as User context.MR_saveOnlySelfWithCompletion(nil) } // I would normally call MR_saveOnlySelfWithCompletion here, but calling it inside the loop makes any UI block easier to spot 

But my guess was wrong. I looked at MR_saveOnlySelfWithCompletion and saw that it relies on

 [self performBlock:saveBlock]; 

which according to apple docs

Asynchronously executes the given block in the queue of receivers.

So, I was a bit puzzled, as I expected this to not block the user interface because of this.

Then I tried (let me call this attempt number 2)

 let context = NSManagedObjectContext.MR_context() for i in 1...1_000 { let user = User.MR_createInContext(context) as User dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { () -> Void in context.MR_saveOnlySelfWithCompletion(nil) } } 

And it does the job, but it doesn't seem right.

Then I found something in the iOS 5.0 release notes

When sending messages to the context created using the association queue, you should use executeBlock: or performBlockAndWait: if your code is not already running on this queue (for the main type of the queue) or within the executeBlock ... invocation action (for the type of the private queue) . Within the blocks passed by these methods, you are free to use the NSManagedObjectContext methods.

So, I assume that:

  • Attempt # 1 freezes the user interface because I actually call it from the main queue, and not within the execution area.
  • Attempt # 2 works, but I create another thread while the context already has its own background thread

So of course , what I have to do is use saveWithBlock:

 MagicalRecord.saveWithBlock { (localContext) -> Void in for i in 1...1_000 { User.MR_createInContext(context) } } 

Performs an operation on the direct child of MR_rootSavingContext , which has the PrivateQueueConcurrencyType property. Thanks to rootContextChanged, any changes that go before MR_rootSavingContext will be available to MR_defaultContext.

So it seems that:

  • MR_defaultContext is the ideal context when it comes to displaying data.
  • editing is preferably done in the MR_context file (a child of the MR_defaultContext file)
  • Long-term tasks, such as server synchronization, are preferably performed using saveWithBlock

What still fails is to work with MR_save [...] WithCompletion (). I would use it in the MR_context file, but since it blocked the main thread in my test cases, I do not see when this becomes relevant (or what I missed ...).

Thank you for your time:)

+6
source share
2 answers

Well, I rarely use magic notations, but since you said that your question is more general, I will try to answer.

Some theory: when creating a context, you pass an indicator of whether you want it to be attached to the main or background thread

 let context = NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.PrivateQueueConcurrencyType) 

By "bound" we mean that the stream refers to the context inside. In the above example, a new thread is created and belongs to the context. This stream is not used automatically, but must be explicitly specified as:

 context.performBlock({ () -> Void in context.save(nil) return }); 

Thus, your code with "dispatch_async" is incorrect, because the thread to which the context is bound can only refer to the context itself (this is a private thread).

What you should do from the above is that if the context is bound to the main thread, calling the executeBlock function from the main thread will not do anything else that directly calls the context methods.

To comment on your markers at the end:

  • MR_defaultContext is the ideal context when it comes to displaying data: NSManagedObject needs to be accessed from the context so this is actually just the context that you can UI from.

  • editing is preferably done in the MR_context file (a child of the MR_defaultContext file): editing is not expensive, and you must follow the rule above. If you call a function that edits NSManagedObject properties from the main thread (for example, by clicking a button), you must update the main context. On the other hand, savings and therefore your main context should not be directly related to the permanent store, but just push its edits to the root context with the concurrency background that owns the permanent store.

  • Long-term tasks, such as server synchronization, are preferably performed using saveWithBlock Yes.

Now, when you try 1

 for i in 1...1_000 { let user = User.MR_createInContext(context) as User } context.MR_saveOnlySelfWithCompletion(nil) 

There is no need to save for each object creation. Even if the user interface has not been blocked, it is wasteful.

About MR_context. I donโ€™t see โ€œMR_contextโ€ in the documentation for magic entries, so I wonder if this is a quick way to access the main context. If so, it is blocked.

+5
source

Read this article, I found it useful. http://martiancraft.com/blog/2015/03/core-data-stack/ ?

0
source

Source: https://habr.com/ru/post/984336/


All Articles