Richard Stelling
Richard Stelling

Reputation: 25665

App hanging/crashing after adding NSSet of entities with relationships

I have to main issues that I believe are related as they both occur on the same line of code.

Data Model

NB: I have simplified the code and model as best I can.

I have 3 entities in my Core data model.

Data is downloaded (in JSON) to the app. Each Merchant is iterated over sectors are extracted, if the sector exists it is fetched and added to a NSMutableArray.

...
//Iterating through Merchants
...
for(NSDictionary *sector in sectors) {
    NSLog(@"\tfetch sectors ID %@", [sector objectForKey:@"sector_id"]);
        
    NSPredicate *sectorPredicate = [NSPredicate predicateWithFormat:@"%K == %d", @"sectorID", [[sector objectForKey:@"sector_id"] integerValue]];
    [sectorRequest setPredicate:sectorPredicate];
                        
    NSArray *existingSector = [self.managedObjectContext executeFetchRequest:sectorRequest error:&error];
                
    if(!error && [existingSector count] == 1) {
        NSLog(@"\tfound sector");
    [merchantSectors addObject:[existingSector objectAtIndex:0]];
    }
    else {
        NSLog(@"\tcreate a new sector");
            
        //Create a new sector
        Sector *newSector = [[Sector alloc] initWithEntity:sectorEntity insertIntoManagedObjectContext:self.managedObjectContext];
        newSector.sectorID = [NSNumber numberWithInteger:[[sector objectForKey:@"sector_id"] integerValue]];
        newSector.name = [sector objectForKey:@"name"];
            
        [merchantSectors addObject:newSector];
            
        [newSector release]; newSector = nil;
    }
}
    
[sectorRequest release]; sectorRequest = nil;
    
NSLog(@"\tadd sectors to merchant");
[currentMerchant addSector:merchantSectors]; //<---- crash and hang

The App will either hang at:

 [currentMerchant addSector:merchantSectors];

or sometimes throw an exception:

*** Terminating app due to uncaught exception \
'NSInternalInconsistencyException', reason: \ 
'-[__NSCFSet addObject:]: mutating method sent to immutable object'

The Branch parsing code is almost identical but never has these issues or the app will hang or crash before it becomes an issue (??).

If the App is deleted and reinstalled the code will work fine, is it possible that existing identical relationships are causing this problem?

Edit: The parsing of the JSON is called using an NSInvocationOperation, so when it hangs the interface stays responsive. The crash version kills the app.

Edit 2: Merchant.h and Merchant.m

Merchant.h

#import <CoreData/CoreData.h>

@class Branch;
@class Sector;

@interface Merchant :  NSManagedObject  
{
}

@property (nonatomic, retain) NSString * street;
@property (nonatomic, retain) NSString * locality;
@property (nonatomic, retain) NSString * city;
@property (nonatomic, retain) NSNumber * merchantID;
@property (nonatomic, retain) NSString * postcode;
@property (nonatomic, retain) NSString * property;
@property (nonatomic, retain) NSString * organisation;
@property (nonatomic, retain) NSDate * expires;
@property (nonatomic, retain) NSSet * Branch;
@property (nonatomic, retain) NSSet* Sector;

@end


@interface Merchant (CoreDataGeneratedAccessors)
- (void)addBranchObject:(Branch *)value;
- (void)removeBranchObject:(Branch *)value;
- (void)addBranch:(NSSet *)value;
- (void)removeBranch:(NSSet *)value;
- (void)addSectorObject:(Sector *)value;
- (void)removeSectorObject:(Sector *)value;
- (void)addSector:(NSSet *)value;
- (void)removeSector:(NSSet *)value;

@end

Merchant.m

#import "Merchant.h"
#import "Branch.h"

@implementation Merchant 

@dynamic street;
@dynamic locality;
@dynamic city;
@dynamic merchantID;
@dynamic postcode;
@dynamic property;
@dynamic organisation;
@dynamic expires;
@dynamic Branch;
@dynamic Sector;

@end

Upvotes: 1

Views: 718

Answers (1)

Dmitry Shashlov
Dmitry Shashlov

Reputation: 426

Try to add Sectors to Merchant one by one using CoreData add<Key>Object: and remove<Key>Object: auto-generated methods (as described in Custom To-Many Relationship Accessor Methods)

for(NSDictionary *sector in sectors) {
    NSPredicate *sectorPredicate = [NSPredicate predicateWithFormat:@"%K == %d", @"sectorID", [[sector objectForKey:@"sector_id"] integerValue]];
    [sectorRequest setPredicate:sectorPredicate];

    NSArray *existingSector = [self.managedObjectContext executeFetchRequest:sectorRequest error:&error];

    if(!error && [existingSector count] == 1) 
    {
        [currentMerchant addSectorObject:[existingSector lastObject]];
    }
    else 
    {
        Sector *newSector = [[Sector alloc] initWithEntity:sectorEntity insertIntoManagedObjectContext:self.managedObjectContext];
        newSector.sectorID = [NSNumber numberWithInteger:[[sector objectForKey:@"sector_id"] integerValue]];
        newSector.name = [sector objectForKey:@"name"];

        [currentMerchant addSectorObject:newSector];

        [newSector release];
    }
}

Or you can retrieve mutable proxy object contains currentMerchants's sectors via mutableSetValueForKey: and add sectors to it:

NSMutableSet *merchantSectors = [currentMerchant mutableSetValueForKey:@"sector"];
for(NSDictionary *sector in sectors) {
    NSPredicate *sectorPredicate = [NSPredicate predicateWithFormat:@"%K == %d", @"sectorID", [[sector objectForKey:@"sector_id"] integerValue]];
    [sectorRequest setPredicate:sectorPredicate];

    NSArray *existingSector = [self.managedObjectContext executeFetchRequest:sectorRequest error:&error];

    if(!error && [existingSector count] == 1) 
    {
        [merchantSectors addObject:[existingSector lastObject]];
    }
    else 
    {
        Sector *newSector = [[Sector alloc] initWithEntity:sectorEntity insertIntoManagedObjectContext:self.managedObjectContext];
        newSector.sectorID = [NSNumber numberWithInteger:[[sector objectForKey:@"sector_id"] integerValue]];
        newSector.name = [sector objectForKey:@"name"];

        [merchantSectors addObject:newSector];

        [newSector release];
    }
}

Anyway, for convenience it's better to use lowercase sectors name for Mecrhant entity for to-many relationship with Sector entity: lowercase not to be ambiguous with Sector class name, and with s at and to be sure, that getter methods for this property return multiple objects.

Upvotes: 1

Related Questions