I have a list-based app. To support adding new elements to a list, what I do is that in the action handler, I create a new object, and I pass this to a details view. If the user cancels the details view, I want to delete the object - when I do this, I get an EXC_BAD_INSTRUCTION.
There must be some threading issue going on.
If I delete the note immediately after creating it (as a test), it works:
Button(action: {
self.newNote = NoteDataManager.makeNote(moc: self.moc, folder: self.savingFolder)
// DELETE immediately, as a test
self.moc.delete(self.newNote!)
self.showNewNoteView = true
}) {
Image(systemName: "square.and.pencil")
}
.sheet(isPresented: self.$showNewNoteView) {
CreateNoteView(newNote: self.newNote!, cancelAction: {
// DO NOTHING here (for now)
})
}
However, if I delete it in the handler for cancelAction (which is called when the user taps a cancel button), I get the exception:
Button(action: {
self.newNote = NoteDataManager.makeNote(moc: self.moc, folder: self.savingFolder)
self.showNewNoteView = true
}) {
Image(systemName: "square.and.pencil")
}
.sheet(isPresented: self.$showNewNoteView) {
CreateNoteView(newNote: self.newNote!, cancelAction: {
// MOVING the delete action here causes EXC_BAD_INSTRUCTION
self.moc.delete(self.newNote!)
})
}
My guess is that it's some sort of threading issue or something. Has anyone run into this and know how to work around this issue? It seems like it should work ...
Core Data is designed to work in a multithreaded environment. However, not every object under the Core Data framework is thread safe. To use Core Data in a multithreaded environment, ensure that:
Managed object contexts are bound to the thread (queue) that they are associated with upon initialization.
Managed objects retrieved from a context are bound to the same queue that the context is bound to.
Avoiding Problems
In general, avoid doing data processing on the main queue that is not user-related. Data processing can be CPU-intensive, and if it is performed on the main queue, it can result in unresponsiveness in the user interface. If your application will be processing data, like importing data into Core Data from JSON, create a private queue context and perform the import on the private context.
Do not pass NSManagedObject instances between queues. Doing so can result in corruption of the data and termination of the app. When it is necessary to hand off a managed object reference from one queue to another, use NSManagedObjectID instances.
You retrieve the managed object ID of a managed object by calling the objectID accessor on the NSManagedObject instance.
as per apple documentation
Concurrency is the ability to work with the data on more than one
queue at the same time. If you choose to use concurrency with Core
Data, you also need to consider the application environment. For the
most part, AppKit and UIKit are not thread-safe. In macOS in
particular, Cocoa bindings and controllers are not threadsafe—if you
are using these technologies, multithreading may be complex.
Using a Private Queue to Support Concurrency
In general, avoid doing data processing on the main queue that is not
user-related. Data processing can be CPU-intensive, and if it is
performed on the main queue, it can result in unresponsiveness in the
user interface. If your application will be processing data, such as
importing data into Core Data from JSON, create a private queue
context and perform the import on the private context.
Related
I'm starting using Realm recently, I'm not sure if my use case is valid:
Normally, when reading a lot of data from DB, I want to put it in a background queue so it will async get the data and later use it on main thread.
For example, I want to fetch several results based on city:
private var results: [Results<SomeObject>?] = []
autoreleasepool {
DispatchQueue(label: "background").async {
[unowned self] in
do
{
let realm = try Realm()
for i in 1...City.count
{
self.results.append(realm.objects(SomeObject.self).filter("city=\(i)"))
}
}
catch
{
NSLog("Failed to open Realm instance on background qeueue")
}
}
}
And later use results to update my chart:
cell.setChartData(ChartDataFactory.createCombinedData(from: results[0]))
However if I apply this model for Realm, I'm getting error like
Terminating app due to uncaught exception 'RLMException', reason: 'Realm accessed from incorrect thread.
I understand I must use realm for each thread, and I can do this by reading realm on main thread, but I don't want the realm query block my main thread.
is there any way I can achieve my goal? e.g. reading realm in a background queue and access the results from another thread, while keeping the auto-refresh feature.
Thanks.
Realm has built-in functionality for running a query on a background thread and delivering the results to the main thread by using Results.observe().
If you specifically need to perform expensive filtering logic that can't be expressed as a Realm query, you can manually pass an array of objects between threads using ThreadSafeReference.
As of 5.0, you can now construct the query on a background thread and receive notifications on the main thread using the on: parameter to observe():
DispatchQueue.global().async {
let realm = try! Realm()
let results = realm.objects(ObjectType.self).filter("property in %#", expensiveFunction(realm))
self.token = results.observe(on: .main) { change in
// do stuff with the results on the main thread
}
}
Realm objects are only accessible through the realm from which they are fetched or created. Realm instances cannot be shared between threads (which you are aware of), and sharing an object from a specific realm instance to another thread, implicitly has the same effects as sharing a realm instance between threads. This is due to the tight coupling between the objects and the realm instance.
As mentioned in this GitHub issue https://github.com/realm/realm-cocoa/issues/946, the recommended practice is to share the primary keys (if your realm object overrides the primaryKey method of RealmObject (Objective-C) / Object (Swift)).
You're trying to directly access 'results' property from a different queue and that will crash. You should instead use ThreadSafeReference as indicated on the answer of Thomas.
Make sure to create a ThreadSafeReference for results and call realm.resolve() on your background queue before fetching from your Realm database.
I solved it like this. I see an overall performance improvement, but I couldn't find any implementation example for querying on a background thread. Might be there is a solution with even better performance.
self.results = self.realm.objects(Object.self).filter(predicate).sorted(by: sortProperties)
self.notificationToken = self.results.observe({ (notification) in
self.tableview.reloadData()
})
This is the results on an iPhone X with a database of ~171k items. Durations are in seconds.
Search on UI tread:
UI thread blocked 0.730504035949707
Search with the code from above:
UI thread blocked 0.28138411045074463
background search duration 0.5073530673980713
I've already done research in lots of articles and discussions about how to use NSManagedObjectContext, but still could't find a satisfying architecture for my project.
In my app, there are three sources where the data might be modified from, ordered by their priorities when there is conflict simultaneously (ex. the cloud has the least priority):
User interface,
BLE message,
HTTP response from the cloud
Since I'm still not an expert on iOS development, I tried to avoid using multiple context for each source. However, after weeks of trial and error, I am reluctant but start to think if I really need to go for multi-context approach.
At the very beginning, I tried to use context.perform { } on the main context to do all the data change operations (add/update/delete, except fetch). I kept fetch to be a sync function because I want the data fetch to be instant so that can responsive with the UI. However, under this approach, I occasionally receive "Collection <__NSCFSet: 0x000000000> was mutated while being enumerated" exception (which I suppose might happen in the forEach or map function for batch data processing). I also found that this approach still block the UI when there are bunch of records to update in the background queue.
Therefore, I created a background context and use parent-child model to manipulate data. Basically the main context(parent) is only responsible for fetching data, while the background context(child) manipulates all the data change (add/update/delete) via backgroundContext.perform { }. This approach solves the UI block problem, however the collection mutated error still happen occasionally, and another issue occurs under this structure: for instance, the app would crash when I add a data record in ViewController A, and move to View Controller B, which fetch the same data immediately even if the background context haven't finished adding the data record.
Therefore I would like to ask some suggestions for the Core Data usage on my project. Is there something I did wrong under my parent-child data context model? Or, should I inevitably go for a real multi-context model without parent-child? How to do it?
My main context(parent) and background context(child) are initiated like this:
lazy var _context: NSManagedObjectContext = {
return (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
}()
lazy var _backgroundContext: NSManagedObjectContext = {
let ctx = NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.privateQueueConcurrencyType)
ctx.parent = self._context
return ctx
}()
And the merge function is as follow:
#objc func contextDidSaveContext(notification: NSNotification) {
let sender = notification.object as! NSManagedObjectContext
if sender === self._context {
NSLog("******** Saved main Context in this thread")
self._backgroundContext.perform {
self._backgroundContext.mergeChanges(fromContextDidSave: notification as Notification)
}
} else if sender === self._backgroundContext {
NSLog("******** Saved background Context in this thread")
self._context.perform {
self._context.mergeChanges(fromContextDidSave: notification as Notification)
}
}
else {
NSLog("******** Saved Context in other thread")
self._context.perform {
self._context.mergeChanges(fromContextDidSave: notification as Notification)
}
self._backgroundContext.perform {
self._backgroundContext.mergeChanges(fromContextDidSave: notification as Notification)
}
}
}
Any advice of the core data structure would be appreciated.
Have you explored to use NSFetchedResultsController to fetch the data?
NSFetchedResultsController provides a thread safe way to observe for changes in the data due to Create, update or delete operations in CoreData.
Use NSFetchedResultsController's delegate methods to update the UI.
Ideally, controllerDidChangeContent delegate would give you an indication to update the UI. Documentation for reference
Background
Saving a large amount of data at a time is very slow.
Current Setup
In my app there's a private-queue NSManagedObjectContext as the parent that talks to the NSPersistentStoreCoordinator directly to save data. A child main-queue context is consumed by a NSTreeController for the UI(NSOutlineView).
(My goal was to prevent any occurence of the beach ball. Currently I remedy the problem by only saving data when the app goes inactive. But since the data that are planed to be deleted are not deleted yet, they may still come up in a fetch result. That's another problem I'm trying to solve.)
The Problem
The child main-queue context can only wait when fetching when the parent context is busy saving.
Related Problems
Core Data: parent context blocks child This perhaps is essentially the same question. I've noticed the answer says the setup is intrinsically wrong. Then I wonder is there a way to do the writing and reading at the same time with Core Data?
Correct implementation of parent/child NSManagedObjectContext A commonly asked question lacks answers with insights (sorry...).
What is the most efficient way to delete a large number (10.000+) objects in Core Data? If the objects can't be designated by a NSPredicate, we still need to rely on the traditional delete() (and maybe the Cascade delete rule) Also, it doesn't eliminate the blocking-the-UI issue.
I will update this question when I have more findings.
I'm guessing you're developing for OS X / macOS (NSTreeController & NSOutlineView). I've no experience with macOS - I develop for iOS - so you might need to take that into account when you're reading my response.
I've not yet made the switch to swift - my code is, perhaps obviously, Objective-C...
I'll start with how I prepare the Core Data stack.
I set up two public properties in the header file:
#property (nonatomic, strong) NSManagedObjectContext *mocPrivate;
#property (nonatomic, strong) NSManagedObjectContext *mocMain;
Although this is unnecessary, I also prefer to set up private properties for my Core Data objects, including, for example:
#property (nonatomic, strong) NSPersistentStoreCoordinator *persistentStoreCoordinator;
Once I've pointed to my model URL, established my managed object model NSManagedObjectModel, pointed to my store URL for my NSPersistentStore and established my persistent store coordinator NSPersistentStoreCoordinator (PSC), I set up my two managed object contexts (MOC).
Within the method to "build" my Core Data stack, after I've completed the code per the above paragraph, I then include the following...
if (!self.mocPrivate) {
self.mocPrivate = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[self.mocPrivate setPersistentStoreCoordinator:self.persistentStoreCoordinator];
} else {
// report to console the use of existing MOC
}
if (!self.mocMain) {
self.mocMain = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[self.mocMain setParentContext:self.mocPrivate];
} else {
// report to console the use of existing MOC
}
(I usually include a few NSLog lines in this code to report to my console but I've excluded that here to keep the code clean.)
Note two important aspects to this code...
set the private queue MOC to interact with the PSC; and
set the main queue MOC as the child of the private queue MOC.
Why is this done? First let's highlight a couple of important points:
Saves to memory are relatively fast; and
Saves to disc are relatively slow.
The private queue is asynchronous to the main queue. The User Interface (UI) operates on the main queue. The private queue operates on a separate thread "in the background" working to maintain context and coordinate data persistence with the PSC, perfectly managed by Core Data and iOS. The main queue operates on the main thread with the UI.
Written a different way...
Heavy work completing irregular (managed by Core Data) data persistence to the PSC (saves to disc) is completed in the private queue; and
Light work completing regular (managed by developer) data persistence to the MOC (saves to memory) is completed in the main queue.
In theory this should ensure your UI is never blocked.
But there is more to this solution. How we manage the "save" process is important...
I write a public method:
- (void)saveContextAndWait:(BOOL)wait;
I call this method from any class that needs to persist data. The code for this public method:
- (void)saveContextAndWait:(BOOL)wait {
// 1. First
if ([self.mocMain hasChanges]) {
// 2. Second
[self.mocMain performBlockAndWait:^{
NSError __autoreleasing *error;
BOOL success;
if (!(success = [self.mocMain save:&error])) {
// error handling
} else {
// report success to the console
}
}];
} else {
NSLog(#"%# - %# - CORE DATA - reports no changes to managedObjectContext MAIN_", NSStringFromClass(self.class), NSStringFromSelector(_cmd));
}
// 3. Third
void (^savePrivate) (void) = ^{
NSError __autoreleasing *error;
BOOL success;
if (!(success = [self.mocPrivate save:&error])) {
// error handling
} else {
// report success to the console
}
};
// 4. Fourth
if ([self.mocPrivate hasChanges]) {
// 5. Fifth
if (wait) {
[self.mocPrivate performBlockAndWait:savePrivate];
} else {
[self.mocPrivate performBlock:savePrivate];
}
} else {
NSLog(#"%# - %# - CORE DATA - reports no changes to managedObjectContext PRIVATE_", NSStringFromClass(self.class), NSStringFromSelector(_cmd));
}
}
So I'll work through this to explain what is happening.
I offer the developer the option to save and wait (block), and depending on the developer's use of the method saveContextAndWait:wait, the private queue MOC "saves" using either:
the performBlockAndWait method (developer calls method with wait = TRUE or YES); or
the performBlock method (developer calls method with wait = FALSE or NO).
First, the method checks whether there are any changes to the main queue MOC. Let's not do any work unless we have to!
Second, the method completes a (synchronous) call to performBlockAndWait on the main queue MOC. This performs the call to save method in a code block and waits for completion before allowing the code to continue. Remember this is for regular "saves" of small data sets. The (asynchronous) option to call performBlock is not required here and in fact will derail the effectiveness of the method, as I experienced when I was learning to implement this in my code (failure to persist data due to the save call on the main queue MOC attempting to complete after completion of the save on the private queue MOC).
Third, we write a little block within a block that contains the code to save the private queue MOC.
Fourth, the method checks whether there are any changes to the private queue MOC. This may be unnecessary but it is harmless to include here.
Fifth, depending on the option the developer chooses to implement (wait = YES or NO) the method calls either performBlockAndWait or performBlock on the block within a block (under third above).
In this last step, regardless of the implementation (wait = YES or NO), the function of persisting data to disc, from the private queue MOC to the PSC, is abstracted to the private queue on an asynchronous thread to the main thread. In theory the "save to disc" via the PSC can take as long as it likes because it has nothing to do with the main thread. AND because the private queue MOC has all the data in memory, the main queue MOC is fully and automatically informed of the changes because it is the child of the private queue MOC.
If you import large volumes of data into app, something I am currently working on implementing, then it makes sense to import this data into the private queue MOC.
The private queue MOC does two things here:
It coordinates data persistence (to disc) with the PSC;
Because it is the parent of the main queue MOC (in memory), the main queue MOC will be notified of the data changes in the private queue MOC and merges are managed by Core Data;
Finally, I use NSFetchedResultsController (FRC) to manage my data fetches, which are all completed against the main queue MOC. This maintains data hierarchy. As changes are made to the data sets in either context, the FRC updates the view.
This solution is simple! (Once I spent weeks wrangling my head around it and another few weeks refining my code.)
There is no requirement to monitor notifications for merges or other changes to MOC. Core Data and iOS handle everything in the background.
So if this doesn't work for you - let me know - I may have excluded or overlooked something as I wrote this code well over a year ago.
I've found so many solutions for progress bar update within the same thread and view controller, however they seemed to be not similar cases as mine.
In my application, the main view controller calls loadIntoCoreData()(implemented in class MyLoadingService) which asynchronously loads data into core data by another thread. This function has to continuously update the loading percentage (which is written in NSUserDefaults.standardUserDefaults()) to the main thread so that it could be shown on the progress bar in main view controller. I had ever used a while loop in MainViewController to continuously fetch the current percentage value, like below:
class MainViewController {
override func viewDidLoad() {
MyLoadingService.loadIntoCoreData() { result in
NSUserDefaults.standardUserDefaults().setBool(false, forKey: "isLoading")
// do something to update the view
}
self.performSelectorInBackground("updateLoadingProgress", withObject: nil)
}
func updatingLoadingProgress() {
let prefs = NSUserDefaults.standardUserDefaults()
prefs.setBool(true, forKey: "isLoading")
// here I use a while loop to listen to the progress value
while(prefs.boolForKey("isLoading")) {
// update progress bar on main thread
self.performSelectorOnMainThread("showLoadingProcess", withObject: nil, waitUntilDone: true)
}
prefs.setValue(Float(0), forKey: "loadingProcess")
}
func showLoadingProcess() {
let prefs = NSUserDefaults.standardUserDefaults()
if let percentage = prefs.valueForKey("loadingProcess") {
self.progressView.setProgress(percentage.floatValue, animated: true)
}
}
}
And in the class of function loadIntoCoreData:
class MyLoadingService {
let context = (UIApplication.sharedApplication()delegate as! AppDelegate).managedObjectContext!
func loadIntoCoreData(source: [MyModel]) {
var counter = 0
for s in source {
//load into core data using the class context
NSOperationQueue.mainQueue.addOperationWithBlock({
// updating the value of "loadingProcess" in NSUserDefaults.standardUserDefaults()
// and synchronize it on main queue
})
counter++
}
}
}
The above code can successfully run the progress bar, however it often encounter BAD_ACCESS or some other exceptions(like "Cannot update object that was never inserted") due to the conflicts on core data context (thought it seems that managedObjectContext isn't touched by the main thread). Therefore, instead of using a while loop listening on the main thread, I consider using NSOperationQueue.performSelectorOnMainThread to acknowledge the main thread after each entry. Therefore I put my view controller as an argument sender into loadCoreData and call performSelectorOnMainThread("updateProgressBar", withObject: sender, waitUntilDone: true) but failed with error "unrecognized selector sent to class 'XXXXXXXX'". So I would like to ask if is it possible to update an UI object between threads? Or, how to modify my previous solution so that the core data context conflicts could be solved? Any solutions are appreciated.
class MyLoadingService {
func loadIntoCoreData(sender: MainViewController, source: [MyModel]) {
var counter = 0
for s in source {
//load into core data using the class context
NSOperationQueue.mainQueue.addOperationWithBlock({
// updating the value of "loadingProcess" in NSUserDefaults.standardUserDefaults()
// and synchronize it on main queue
})
NSOperationQueue.performSelectorOnMainThread("updateProgressBar", withObject: sender, waitUntilDone: true)
counter++
}
}
func updateProgressBar(sender: MainViewController) {
sender.progressView.setProgress(percentage, animated: true)
}
}
class MainViewController {
override func viewDidLoad() {
MyLoadingService.loadIntoCoreData(self) { result in
// do something to update the view
}
}
}
First, you are abusing NSUserDefaults in horrible ways. The documentation describes it as this...
The NSUserDefaults class provides a programmatic interface for
interacting with the defaults system. The defaults system allows an
application to customize its behavior to match a user’s preferences.
For example, you can allow users to determine what units of
measurement your application displays or how often documents are
automatically saved. Applications record such preferences by assigning
values to a set of parameters in a user’s defaults database. The
parameters are referred to as defaults since they’re commonly used to
determine an application’s default state at startup or the way it acts
by default.
You are using it to store a global variable.
Furthermore, you are completely abusing the user's CPU in your loop where you continuously are checking the value in the user defaults, and clipping off a selector to the main thread. "Abuse of the CPU" doesn't even come close to describing what this code is doing.
You should use NSProgress for reporting progress. There is a WWDC 2015 presentation dedicated exclusively to using NSProgress.
On to your core data usage.
Unfortunately, since you intentionally redacted all of the core data code, it's impossible to say what is going wrong.
However, based on what I see, you are probably trying to use that managed object context from your app delegate (which is probably still created with the deprecated confinement policy) from a background thread, which is a cardinal sin of the highest order as far as core data is concerned.
If you want to import data as a long running operation, use a private context, and execute the operations in the background. Use NSProgress to communicate progress to anyone wanting to listen.
EDIT
Thanks for the advice on my core data context usage. I digged into all
the contexts in my code and re-organized the contexts inside, the
conflict problem does not happen anymore. As for NSProgress , it's a
pity that the WWDC presentation focus on the feature on iOS 9 (while
my app must compact on iOS 8 devices). However, even though I use
NSProgress, I should still tell the main thread how many data the core
data (on another thread) already has, right? How does the thread on
NSProgress know the loading progress on my core data thread? –
whitney13625
You can still use NSProgress for iOS8, then only real difference is that you can't explicitly add children, but the implicit way still works, and that video explains it as well.
You really should watch the whole video and forget about the iOS9 part, except to know that you must add children implicitly instead of explicitly.
Also, this pre-iOS9 blog post should clear up any questions you have about it.
I have core data objects that are created/updated via HTTP. I also want to create a background thread to continuously receive timestamp and state info from the app and update the core data objects. Should I use NSOperation or GCD for this? Since it's not just a simple task, NSOperation seems better since I can loop within it, but I can't figure out how to pass information into the operation, from the main thread, while it's running. Is there a simple way of doing this? I have seen many threads/articles about sending messages to the main thread from the operation, but nothing about passing messages to it.
Does using NSOperation/GCD seem like a good solution?
Step back and relook at your architecture. You should be using a managed object context that itself uses a private dispatch queue (option NSPrivateQueueConcurrencyType).
You would then use NSURLConnections to get the data you want, and when you get the data in the delegate method you can asynchronously update the repository using performBlock.
Conversely you may want to retrieve data using performBlockAndWait, using block variables or mutable pre-defined objects to receive the results from the block.
I was looking at this all wrong (essentially wanting to subclass NSThread and pass data to/from it). I didn't think about using blocks appropriately.
Essentially, what I'm doing is:
loadStuff:(NSDictionary *)stuff {
// stuff is data from HTTP GET request
NSManagedObjectContext *context = // init context with NSPrivateQueueConcurrencyType
context.parentContext = // main context
[context performBlock:^{
// insert/update entities
// save context
}];
}
But then I need to loop in a background thread to update the entities:
//inside some method
dispatch_async(global_queue, ^{
// _currentTimestamp is updated by a delegate
NSInterval timestamp = _currentTimestamp;
while (run) {
if (timestamp != _currentTimestamp) {
// do calculation
// update entities if needed
// save context
}
}
});