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.
Merchant
(can have manyBranch
es, can have manySector
s)Sector
(can have manyMerchant
s)Branch
(can have oneMerchant
)
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
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.
精彩评论