Break at exception without error information - objective-c

This code is breaking for a non-fatal exception, but no significant error information is provided. This is after a core data entity has been updated.
-(void) commitBackgroundChangesAndNotify:(NSString *)notification{
NSLog(#"enter commit");
if([self.backgroundMOC hasChanges]) {
NSError *error = nil;
if (!([self.backgroundMOC save:&error])){ // breaks here
NSLog(#"Unresolved Error %# %#", error, [error userInfo]);
}
// [[NSNotificationCenter defaultCenter] postNotificationName:notification object:nil];
}
NSLog(#"exit commit");
}
It breaks in the debugger whether I have breakpoints enabled or not. If I run it without the debugger, it appears to run okay.
This code is run several times during an NSOperation. During the operation, the break occurs the second time it is called to save (meaning changes to the context have already been successfully saved once.) I verified that the backgroundMOC was created in the same thread that the changes and the commits are being made, which is the thread of the NSOperation. I also looked at the counts for the moc insertedObjects, updatedObjects, and deletedObjects. These all look right.
The calling code looks like this:
- (void) calculateBoundsForSearch {
NSArray *searchesArray = [currentSearchEntity.resultOfSearch allObjects];
MyMKMapBounds bounds = [MapController calculateBoundsForArrayOfSearchResults:searchesArray];
currentSearchEntity.minLatitude = [NSNumber numberWithFloat:bounds.min.latitude];
currentSearchEntity.minLongitude = [NSNumber numberWithFloat:bounds.min.longitude];
currentSearchEntity.maxLatitude = [NSNumber numberWithFloat:bounds.max.latitude];
currentSearchEntity.maxLongitude = [NSNumber numberWithFloat:bounds.max.longitude];
[dataInterface commitBackgroundChangesAndNotify:#"bounds"];
}
This is called by two objects. For one, there is no exception.
The only output in the console is this:
2012-07-22 10:47:13.790 myApp[46578:17e07] enter commit
Catchpoint 7 (exception thrown).2012-07-22 10:47:21.160 myApp[46578:17e07] exit commit
This is a non-fatal exception. One thing I noticed, which I don't understand, is that when it breaks, I have to click continue twice to get past it. The message Catchpoint 7 (exception thrown). is shown after the first click.
I'm trying to figure out what tools I have on hand to determine the cause of the exception. Of course, any ideas on what is causing the exception would be helpful, too.

Related

XCTest Completion Handler Assertion Retain Cycle

I have been writing Xcode tests using the XCTest framework for a while, mostly async tests of the getters of a service with completion handlers of the following format with no issues:
XCTestExpectation *promise = [self expectationWithDescription:#"Get Something should succeed"];
[self.myService getSomethingOnCompletion:^(NSError * _Nullable error) {
XCTAssertNil(error, #"Error should be nil");
[promise fulfill];
}];
[self waitForExpectations:#[promise] timeout:2.0];
Suddenly today I go to write my first async setter test of the below format, but get warnings on the XCTAssert...() statement within the block saying:
Capturing 'self' strongly in this block is likely to lead to a retain cycle
XCTestExpectation *promise = [self expectationWithDescription:#"Set Something should succeed"];
[self.myService setSomething:#"..." onCompletion:^(NSError * _Nullable error) {
XCTAssertNil(error, #"Error should be nil");
[promise fulfill];
}];
[self waitForExpectations:#[promise] timeout:2.0];
I've even gone to the lengths of commenting out the entire contents of setSomething: onCompletion: such that it does not do anything, clean and rebuild, yet the warning still persists.
I do not understand what self it is referring to, as the only thing going on inside the block is an XCTAssert...() and [XCTestExpectation fulfill]. Furthermore, I do not understand why none of the 30+ tests I've written of the first format have no warnings associated with them, but all 5+ I've written of the 2nd format do.
Any explanation on what's going on here and how I can resolve it would be appreciated.
(Using Xcode 10.0)
Edit 1:
The issue seems to be with the method name, setSomething: onCompletion:. Changing it to anything else, such as doSomething: onCompletion: removes the warning. I still don't know how/ why Xcode interprets the set command in such a fashion that it presents the warning, so any information would be appreciated.
Edit 2:
The following are the method signatures of setSomething and doSomething:
- (void)setSomething:(EnumType)type onCompletion:(SetSomethingCompletionHandler)completion;
- (void)doSomething:(EnumType)type onCompletion:(SetSomethingCompletionHandler)completion
Where SetSomethingCompletionHandler is defined as :
typedef void (^SetSomethingCompletionHandler)(NSError * _Nullable error);

iOS concurrency: NSOperationQueue and ARC issue

I am currently implementing a multithreaded application and I encounter a strange race condition (leading to an ARC problem: error for object 0x7f8bcbd6a1c0: pointer being freed was not allocated).
The app creates multiple NSOperations, each one is downloading and processing information. As in every one of these NSOperations an error may occur (e.g., the web service is not available), I want to propagate the error up so I can handle it. However, I seem to encounter a race condition and the operations try to access invalid memory.
A minimalistic example which shows the memory access problem is the following one:
- (void) createError: (NSError**) err {
*err = [[NSError alloc] initWithDomain:#"Test" code:12345 userInfo:nil];
}
- (void)viewDidLoad {
[super viewDidLoad];
__block NSError *globalError = nil;
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSOperation *op = nil;
for (int i=0; i<10; i++) {
op = [NSBlockOperation blockOperationWithBlock:^{
NSError *err = nil;
[self createError:&err];
// This loop increases the chance to get the race condition
for (int j=0; j<10000;j++) {
#synchronized(globalError) {
globalError = err;
}
}
}];
[queue addOperation:op];
}
[queue waitUntilAllOperationsAreFinished];
if (globalError) {
NSLog(#"An error occured in at least one of the operations");
}
}
Whenever I run this program, I get an exception. Mostly it is error for object 0x7fc0b860e860: pointer being freed was not allocated, however, sometimes I also got an EXC_BAD_ACCESS (code=EXC_I386_GPFLT) break in the debugger.
I have added the for-Loop over 10000 iterations only to increase the chance that the race condition occurs. If I leave it, the error only occurs in rare occasions (but still has to be fixed, obviously I am doing something wrong here).
The #synchronized directive uses the object you pass to control the synchronization, so, as you note, you're trying to synchronize on nil. And even if it wasn't nil, the object referenced by the #synchronized directive is a different object every time, largely defeating the attempt to synchronize.
The easiest change is to use #synchronized(self), instead. Or, create a NSLock object and then lock and unlock it when you access globalError. Or, if this was a high demand situation, use GCD queue (either serial queue or reader-writer pattern).

How to get variables out of a block in iOS

I have the following code:
__block NSDictionary *results;
if (username.length != 0 && password.length != 0) {
NSMutableDictionary* params =[NSMutableDictionary dictionaryWithObjectsAndKeys:
#"login", #"command",
username, #"username",
password, #"password",
nil];
[[API sharedInstance] commandWithParams:params onCompletion:^(NSDictionary *json) {
for (id key in json) {
NSLog(#"key: %#, value: %#", key, [json objectForKey:key]);
}
results = json;
}];
}
for (id key in results) {
NSLog(#"key: %#, value: %#", key, [results objectForKey:key]);
}
This some code from a tutorial that I'm trying to play with to figure some things out. In the tutorial, there was a lot of code done in the 'on completion' part and I'd like to just have it return the NSDictionary so the calling code can handle the result itself.
The problem I'm having is that results is not being set. The first NSLog for loop prints output but the 2nd one does not. What am I missing?
You're missing understaning of asynch operations. The completion block is not invoked until the operation which you initiated has been completed. However, the NSLog statement is executed immediately after the message send to [API sharedInstance] - and results is nil then.
Usually, a completion handler indicates an asynchronous request. In that case, the code in the commandWithParams:onCompletion: method will not have yet run when the code after it executes.
So, you are looking at the results object, but the completion handler has not yet run to set its value, so it's still nil.
The problem is most likely that your commandWithParams:onCompletion: method is asynchronous. That is, it is dispatched onto another thread.
I'll assume this is a network call since this is what it looks like, but this applies to any async method. Basically it's downloading your data in the background so that you don't freeze up the app.
So what does this mean for your code? Well, you create an uninitialized pointer to an NSDictionary. Then you ask your API class's shared instance to dispatch a network request for you. Then immediately after the request is dispatched, most likely before the network request/long running process finishes, the next line of code is executed which happens to be your for loop.

MagicalRecord + Core Data not finding objects between contexts despite objectID string comparison working fine

I have an NSFetchedResultsController that queries on a Core Data entity, 'MyGalleryPhoto'.
I'm trying to delete some objects, and coming up against some problems. I'm using MagicalRecord. Here is my original attempt at the code, which in my view should work fine. At the point the code is run, the objects definitely exist, because they display in the fetchedResultsController.
[MagicalRecord saveInBackgroundWithBlock:^(NSManagedObjectContext *localContext) {
for (MyGalleryPhoto *myGalleryPhoto in [self.fetchedResultsController.fetchedObjects objectsAtIndexes: self.selectedIndexes]) {
NSError *error = nil;
MyGalleryPhoto *localMyGalleryPhoto = (MyGalleryPhoto *) [localContext existingObjectWithID: myGalleryPhoto.objectID error: &error];
NSLog(#"error: %#:%#", [error localizedDescription], [error userInfo]);
NSLog(#"mygp: %#", [localMyGalleryPhoto description]);
[localMyGalleryPhoto deleteInContext: localContext];
}
} completion:^(void){
}];
This code does not work. The myGalleryPhoto entry is not found and the error returned is: "The operation couldn’t be completed. (Cocoa error 133000.)" I've also tried using MR_inContext, which just calls existingObjectWithId:error:.
After a lot of messing around I've come up with this vile frankenstein's monster, that gets all the records out of the entity and compares the string representations of the ObjectIDs. This works fine. Why? I'm using a copy of MagicalRecord I downloaded from GitHub today, XCode up to date, latest SDK, et cetera.
[MagicalRecord saveInBackgroundWithBlock:^(NSManagedObjectContext *localContext) {
NSArray *allMyGalleryPhotos = [MyGalleryPhoto findAllInContext: localContext];
for (MyGalleryPhoto *myGalleryPhoto in [self.fetchedResultsController.fetchedObjects objectsAtIndexes: self.selectedIndexes]) {
MyGalleryPhoto *myGalleryPhotoToDelete = nil;
for (MyGalleryPhoto *existingMyGalleryPhoto in allMyGalleryPhotos) {
NSString *existingURLString = [[existingMyGalleryPhoto.objectID URIRepresentation] absoluteString];
NSString *URLString = [[myGalleryPhoto.objectID URIRepresentation] absoluteString];
NSLog(#"ExistingURLString: %#", existingURLString);
NSLog(#"URLString: %#", URLString);
if ([URLString isEqualToString: existingURLString]) {
myGalleryPhotoToDelete = existingMyGalleryPhoto;
}
}
if (myGalleryPhotoToDelete) [myGalleryPhotoToDelete deleteInContext: localContext];
}
} completion:^(void){
}];
Cocoa Error 13000 is a Referential Integrity error, as described in the documentation. That means you're looking for an object that doesn't exist in the store. On a more practical level, that means that your contexts (I'm assuming you have more than one Managed Object Context) are not in sync. That is, you've added a new object to one context, while the other doesn't have that object because the previous context has not been saved.
Regarding your code, the first problem I see in the first example is that you are crossing thread boundaries at the very start. The fetchedResultsController has a reference to objects in another context (I'm going to assume the default context). Every time saveInBackground is called, it gives you a new context to use, but it also puts that block of code on a background thread. Crossing thread boundaries, even in the new version of Core Data, is going to give you crazy, hard to track down problems at random times.
The gist if what you're trying to do in the first (simpler) block of code is you have a collection of photo objects you want to remove from your application. I would do something like this instead:
NSPredicate *objectsToDelete = [NSPredicate predicateWithFormat:#"self in %#", self.fetchedResultsController.fetchedObjects];
[MagicalRecord saveInBackgroundWithBlock:^(NSManagedObjectContext *)localContext
{
[MyGalleryPhoto deleteAllMatchingPredicate:objectsToDelete inContext:localContext];
}];
The deleteAllMatchingPredicate method should do a lookup of the objects in the correct context (which you weren't doing in the first block of code) so they can be deleted. It also sets up the objects to load as faults, so we're not going to load everything in memory, only to delete it immediately. It'll load only what it needs, and no more.
I would not use existingObjectWithID: in this case. This method never loads faults. Your use case means it'll load the entire object into memory, only to delete it anyway.

NSFetchedResultsController not displaying changes from background thread

My app is using an NSFetchedResultsController tied to a Core Data store and it has worked well so far, but I am now trying to make the update code asynchronous and I am having issues. I have created an NSOperation sub-class to do my updates in and am successfully adding this new object to an NSOperationQueue. The updates code is executing as I expect it to and I have verified this through debug logs and by examining the SQLite store after it runs.
The problem is that after my background operation completes, the new (or updated) items do not appear in my UITableView. Based on my limited understanding, I believe that I need to notify the main managedObjectContext that changes have occurred so that they may be merged in. My notification is firing, nut no new items appear in the tableview. If I stop the app and restart it, the objects appear in the tableview, leading me to believe that they are being inserted to the core data store successfully but are not being merged into the managedObjectContext being used on the main thread.
I have included a sample of my operation's init, main and notification methods. Am I missing something important or maybe going about this in the wrong way? Any help would be greatly appreciated.
- (id)initWithDelegate:(AppDelegate *)theDelegate
{
if (!(self = [super init])) return nil;
delegate = theDelegate;
return self;
}
- (void)main
{
[self setUpdateContext:[self managedObjectContext]];
NSManagedObjectContext *mainMOC = [self newContextToMainStore];
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self
selector:#selector(contextDidSave:)
name:NSManagedObjectContextDidSaveNotification
object:updateContext];
[self setMainContext:mainMOC];
// Create/update objects with mainContext.
NSError *error = nil;
if (![[self mainContext] save:&error]) {
DLog(#"Error saving event to CoreData store");
}
DLog(#"Core Data context saved");
}
- (void)contextDidSave:(NSNotification*)notification
{
DLog(#"Notification fired.");
SEL selector = #selector(mergeChangesFromContextDidSaveNotification:);
[[delegate managedObjectContext] performSelectorOnMainThread:selector
withObject:notification
waitUntilDone:YES];
}
While debugging, I examined the notification object that is being sent in contextDidSave: and it seems to contain all of the items that were added (excerpt below). This continues to make me think that the inserts/updates are happening correctly but somehow the merge is not being fired.
NSConcreteNotification 0x6b7b0b0 {name = NSManagingContextDidSaveChangesNotification; object = <NSManagedObjectContext: 0x5e8ab30>; userInfo = {
inserted = "{(\n <GCTeam: 0x6b77290> (entity: GCTeam; id: 0xdc5ea10 <x-coredata://F4091BAE-4B47-4F3A-A008-B6A35D7AB196/GCTeam/p1> ; data: {\n changed =
The method that receives your notification must indeed notify your context, you can try something like this, which is what I am doing in my application:
- (void)updateTable:(NSNotification *)saveNotification
{
if (fetchedResultsController == nil)
{
NSError *error;
if (![[self fetchedResultsController] performFetch:&error]) {
//Update to handle the error appropriately.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
}
else
{
NSManagedObjectContext *context = [fetchedResultsController managedObjectContext];
// Merging changes causes the fetched results controller to update its results
[context mergeChangesFromContextDidSaveNotification:saveNotification];
// Reload your table view data
[self.tableView reloadData];
}
}
Hope that helps.
Depending on the specifics of what you are doing, you may be going about this the wrong way.
For most cases, you can simply assign a delegate using NSFetchedResultsControllerDelegate. You provide an implementation for one of the methods specified in "respondingToChanges" depending on your needs, and then send the tableView a reloadData message.
The answer turned out to be unrelated to the posted code which ended up working as I expected. For reasons that I am still not entirely sure of, it had something to do with the first launch of the app. When I attempted to run my update operation on launches after the Core Data store was created, it worked as expected. I solved the problem by pre-loading a version of the sqlite database in the app so that it did not need to create an empty store on first launch. I wish I understood why this solved the problem, but I was planning on doing this either way. I am leaving this here in the hope that someone else may find it useful and not lose as much time as I did on this.
I've been running into a similar problem in the simulator. I was kicking off an update process when transitioning from the root table to the selected folder. The update process would update CoreData from a web server, save, then merge, but the data didn't show up. If I browsed back and forth a couple times it would show up eventually, and once it worked like clockwork (but I was never able to get that perfect run repeated). This gave me the idea that maybe it's a thread/event timing issue in the simulator, where the table is refreshing too fast or notifications just aren't being queued right or something along those lines. I decided to try running in Instruments to see if I could pinpoint the problem (all CoreData, CPU Monitor, Leaks, Allocations, Thread States, Dispatch, and a couple others). Every time I've done a "first run" with a blank slate since then it has worked perfectly. Maybe Instruments is slowing it down just enough?
Ultimately I need to test on the device to get an accurate test, and if the problem persists I will try your solution in the accepted answer (to create a base sql-lite db to load from).