开发者

App hanging/crashing after adding NSSet of entities with relationships

开发者 https://www.devze.com 2023-01-28 21:26 出处:网络
I have to main issues that I believe are related as they both occur on the same line of code. Data Model

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 many Branches, can have many Sectors)
  • Sector (can have many Merchants)
  • Branch (can have one Merchant)

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.

0

精彩评论

暂无评论...
验证码 换一张
取 消