4

I have an application that works fine in an older project (that doesn't use ARC and was written against in Xcode 4.0 with iOS 4.2.

I am trying to port this application over to use ARC and storyboard with Xcode 4.2 and iOS 5.

I have got everything ported over and fixed all the error caused by retaining, releasing, deallocing, etc. The project builds fine now. I also rebuilt my Core Data model in the new project and rebuild my model subclasses.

Where the error happens is when I try to parse an XML file in a background thread. The file is local to the project. I NSLog the initializer for each NSOperation class that does the parsing (there are four of them). I am getting to that point of the application, the error occurs when I add the operations to the queue.

Here is my code in the AppDelegate that starts the Operations:

#import "AppDelegate.h"
#import "ACHPhonebook.h"

@implementation AppDelegate

@synthesize window = _window;
@synthesize managedObjectContext = __managedObjectContext;
@synthesize managedObjectModel = __managedObjectModel;
@synthesize persistentStoreCoordinator = __persistentStoreCoordinator;

@synthesize parseQueue;
@synthesize parseOpAD, parseOpEK, parseOpLR, parseOpSZ;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    NSFetchRequest *request = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"ACHPhonebook" inManagedObjectContext:self.managedObjectContext];

    [request setEntity:entity];    

    NSError *error = nil;
    NSUInteger count = [self.managedObjectContext countForFetchRequest:request error:&error];

    NSLog(@"total CD count = %d", count);

    if(
       getenv("NSZombieEnabled") || getenv("NSAutoreleaseFreedObjectCheckEnabled")
       ) {
        NSLog(@"NSZombieEnabled/NSAutoreleaseFreedObjectCheckEnabled enabled!");
    }

    if (count == 0) {
        // Purge the DB
        [ACHPhonebook purgePhoneBook:self.managedObjectContext];

        // NSLog(@"Just purged the phonebook");

        // Set up the NSOperation Queue for the parsing
        parseQueue = [[NSOperationQueue alloc] init];

        // Make an operation to import the phonebook    
        //[self setParseOp:[[ParseOperation alloc] initAndStartParse]]; 
        [self setParseOpAD:[[ParseOperationA_D alloc] initAndStartParse]];
        [self setParseOpEK:[[ParseOperationE_K alloc] initAndStartParse]];
        [self setParseOpLR:[[ParseOperationL_R alloc] initAndStartParse]];
        [self setParseOpSZ:[[ParseOperationS_Z alloc] initAndStartParse]];

        // [parseQueue addOperation:parseOp];

        NSArray *opArray = [[NSArray alloc] initWithObjects:parseOpAD, parseOpEK, parseOpLR, parseOpSZ, nil];

        [parseQueue addOperations:opArray waitUntilFinished:NO];
    }


//    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
//    // Override point for customization after application launch.
//    self.window.backgroundColor = [UIColor whiteColor];
//    [self.window makeKeyAndVisible];
    return YES;
}

Here is one of the NSOperation Classes (they are all the same, just working on different files to help speed up the initial loading of 6000 records.

#import "ParseOperationA-D.h"
#import "ACHPhonebook.h"
#import "AppDelegate.h"
#import "TBXML.h"

@implementation ParseOperationA_D

- (id)initAndStartParse
{
    NSLog(@"ParseOperation init");

    // [self parsePhonebook];

    return self;

}

- (void)mergeChanges:(NSNotification *)notification
{
    id appDelegate = [[UIApplication sharedApplication] delegate];

    NSManagedObjectContext *mainContext = [appDelegate managedObjectContext];

    // Merge changes into the main context on the main thread
   [mainContext performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification:)  
                                withObject:notification
                                waitUntilDone:NO];

    // NSLog(@"Merged Changes");
}

// the main function for this NSOperation, to start the parsing
- (void)main {

    id appDelegate = [[UIApplication sharedApplication] delegate];

    NSManagedObjectContext *ctx = [[NSManagedObjectContext alloc] init];
    [ctx setUndoManager:nil];
    [ctx setPersistentStoreCoordinator: [appDelegate persistentStoreCoordinator]];

    // Register context with the notification center
    NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; 
    [nc addObserver:self
           selector:@selector(mergeChanges:) 
               name:NSManagedObjectContextDidSaveNotification
             object:ctx];

    NSUInteger x = 1;

    TBXML *tbxml = [[TBXML alloc] initWithXMLFile:@"achcentral_aTOd.xml"];

    // Get root element
    TBXMLElement * root = tbxml.rootXMLElement;

    // if root element is valid
    if (root) {

        NSError *error = nil;

        TBXMLElement *thisPBE = [TBXML childElementNamed:@"PhoneBookResults" parentElement:root];

        while (thisPBE != nil) {

            // Set up the Insert command
            ACHPhonebook * xmlPBE = (ACHPhonebook *)[NSEntityDescription insertNewObjectForEntityForName:@"ACHPhonebook" inManagedObjectContext:ctx];

            // Set all the field values from the XML record

            //            TBXMLElement *elName = [TBXML childElementNamed:@"Name" parentElement:thisPBE];
            //            if (elName != nil) {
            //                [xmlPBE setName:[TBXML textForElement:elName]];
            //            }

            TBXMLElement *elTitle = [TBXML childElementNamed:@"title" parentElement:thisPBE];
            if (elTitle != nil) {
                [xmlPBE setTitle:[TBXML textForElement:elTitle]];
            }

            TBXMLElement *elDept = [TBXML childElementNamed:@"department" parentElement:thisPBE];
            if (elDept != nil) {
                // obtain the text from the description element
                [xmlPBE setDepartment:[TBXML textForElement:elDept]];
            }

            TBXMLElement *elExt = [TBXML childElementNamed:@"phone" parentElement:thisPBE];
            if (elExt != nil) {
                [xmlPBE setExt:[TBXML textForElement:elExt]];
            }

            TBXMLElement *elPager = [TBXML childElementNamed:@"pager" parentElement:thisPBE];
            if (elPager != nil) {
                [xmlPBE setPager:[TBXML textForElement:elPager]];
            }

            TBXMLElement *elEmailAddress = [TBXML childElementNamed:@"emailaddress" parentElement:thisPBE];
            if (elEmailAddress != nil) {
                [xmlPBE setEmailAddress:[TBXML textForElement:elEmailAddress]];
            }

            //            TBXMLElement *elNetworkID = [TBXML childElementNamed:@"NetworkID" parentElement:thisPBE];
            //            if (elNetworkID != nil) {
            //                [xmlPBE setNetworkid:[TBXML textForElement:elNetworkID]];
            //            }

            TBXMLElement *elLastName = [TBXML childElementNamed:@"lastName" parentElement:thisPBE];
            if (elLastName != nil) {
                [xmlPBE setLastName:[TBXML textForElement:elLastName]];
            }

            TBXMLElement *elFirstName = [TBXML childElementNamed:@"firstName" parentElement:thisPBE];
            if (elFirstName != nil) {
                [xmlPBE setFirstName:[TBXML textForElement:elFirstName]];
            }

            if (elFirstName != nil && elLastName !=nil) {
                // Make the name field from the first and last names
                NSString *fullName = [[TBXML textForElement:elFirstName] stringByAppendingFormat:@" %@",[TBXML textForElement:elLastName]];
                [xmlPBE setName:fullName];
            }

            TBXMLElement *elPicLoc = [TBXML childElementNamed:@"picFileName" parentElement:thisPBE];
            if (elPicLoc != nil) {
                if (![[TBXML textForElement:elPicLoc] isEqualToString:@""])
                {
                    [xmlPBE setPicloc:[TBXML textForElement:elPicLoc]];

                    NSString *picFilePath = @"https://secure.archildrens.org/insite/badgepics/adbadgepics/";

                    NSURL *url = [NSURL URLWithString:[picFilePath 
                                        stringByAppendingString:xmlPBE.picloc]];
                    NSData * imageData = [[NSData alloc] initWithContentsOfURL: url];
                    if (!imageData)
                    {
                        // The image filename was stored but didn't exist
                        NSString *achImage = 
                        [[NSBundle mainBundle] pathForResource:@"ach" ofType:@"png"]; 
                        imageData = [NSData dataWithContentsOfFile:achImage];
                    }

                    [xmlPBE setPicture:imageData];

                }
            }

            if (x % 50 == 0) {
                // Get ready to save the context
                error = nil;
                // Save the context.
                if (![ctx save:&error])
                {
                    NSLog(@"loadPhoneBook error %@, %@", error, [error userInfo]);
                    abort();
                }

                // Clear out the scratchpad
                [ctx reset];

                //NSLog(@"Got 10");
            }
            // Find the next XML record (this will end the while loop when we reach the end)
            thisPBE = [TBXML nextSiblingNamed:@"PhoneBookResults" searchFromElement:thisPBE];

            // Increment the counter
            x++;

        } // while ...

        if ([ctx hasChanges]) {

            // NSUInteger *left = [[ctx insertedObjects] count];
            error = nil;
            // Save the context.
            if (![ctx save:&error])
            {
                NSLog(@"loadPhoneBook error %@, %@", error, [error userInfo]);
                abort();
            }

            // Clear out the scratchpad
            [ctx reset];

            NSLog(@"Got the last ones, %d", x);
        }

    }
}

@end

I have tried enabling NSZombies and still can't see what is going on. It always breaks with a EXC_BAD_ACCESS error on the

[parseQueue addOperations:opArray waitUntilFinished:NO]; 

line in the AppDelegate, this was my first attempt at multithreading in an iOS app. Like I said before, I have a version that runs that was built against an older version of the SDK and it still runs. This may be (and probably is) something really simple that I am overlooking...

chown
  • 51,908
  • 16
  • 134
  • 170
LJ Wilson
  • 14,445
  • 5
  • 38
  • 62
  • 3
    Your `initAndStartParse` method isn't calling `self = [super init];`. I don't think you're instantiating the NSOperation subclasses correctly. – sho Oct 29 '11 at 14:38
  • This was exactly the problem. Once I fixed that the project compiled without error and ran fine. – LJ Wilson Dec 12 '11 at 21:27

2 Answers2

6

Set MallocStackLogging, guard malloc, and (you may have already done this one) NSZombieEnabled in the debugger. Then, when your App crashes, type this in the gdb console:

(gdb) info malloc-history 0x543216

Replace 0x543216 with the address of the object that caused the crash, and you will get a much more useful stack trace and it should help you pinpoint the exact line in your code that is causing the problem.

See this article for more detailed instructions.

chown
  • 51,908
  • 16
  • 134
  • 170
  • See my comment about sho's comment above. While this wasn't solved by using Zombies and malloc, this answer has helped me find two other issues since then :) – LJ Wilson Dec 12 '11 at 21:29
-1

Instead of using NSOperations, having you tried self performSelectorInBackground:withObject:?

eric.mitchell
  • 8,817
  • 12
  • 54
  • 92
  • I have not. I thought using NSOperations was the preferred way of creating background threads that did lots of work. Can you explain why this method would be better than using NSOperations and NSOperationQueue? – LJ Wilson Oct 29 '11 at 14:20
  • See http://stackoverflow.com/questions/2935007/difference-between-performselectorinbackground-and-nsoperation-subclass – eric.mitchell Oct 29 '11 at 14:23
  • That doesn't go into why I shouldn't be using NSOperations (which is what the example applications that I have seen on Apple's Dev site use). – LJ Wilson Oct 29 '11 at 14:58
  • Performance-wise, the difference between threading with NSOperations and performSelectorInBackground is minimal. I would try performSelectorInBackground, and if that doesn't crash, I would call it better. – eric.mitchell Oct 29 '11 at 15:17
  • That still doesn't give me any idea of what the problem is. I really want to solve the problem and not just band-aid it. Thanks though. – LJ Wilson Oct 29 '11 at 21:01