Crash: iPhone Threading with Blocks

Posted by jtbandes on Stack Overflow See other posts from Stack Overflow or by jtbandes
Published on 2010-06-10T04:03:27Z Indexed on 2010/06/10 4:13 UTC
Read the original article Hit count: 572

I have some convenience methods set up for threading with blocks (using PLBlocks). Then in the main portion of my code, I call the method -fetchArrivalsForLocationIDs:callback:errback:, which runs some web API calls in the background.

Here's the problem: when I comment out the 2 NSAutoreleasePool-related lines in JTBlockThreading.m, of course I get lots of Object 0x6b31280 of class __NSArrayM autoreleased with no pool in place - just leaking errors. However, if I uncomment them, the app frequently crashes on the [pool release]; line, sometimes saying malloc: *** error for object 0x6e3ae10: pointer being freed was not allocated" *** set a breakpoint in malloc_error_break to debug.

I assume I've made a horrible mistake/assumption in threading somewhere, but can anyone figure out what exactly the problem is?

// JTBlockThreading.h

#import <Foundation/Foundation.h>
#import <PLBlocks/Block.h>

#define JT_BLOCKTHREAD_BACKGROUND [self invokeBlockInBackground:^{
#define JT_BLOCKTHREAD_MAIN [self invokeBlockOnMainThread:^{
#define JT_BLOCKTHREAD_END }];
#define JT_BLOCKTHREAD_BACKGROUND_END_WAIT } waitUntilDone:YES];

@interface NSObject (JTBlockThreading)
- (void)invokeBlockInBackground:(void (^)())block;
- (void)invokeBlockOnMainThread:(void (^)())block;
- (void)invokeBlockOnMainThread:(void (^)())block
                      waitUntilDone:(BOOL)wait;
- (void)invokeBlock:(void (^)())block;
@end

// JTBlockThreading.m
#import "JTBlockThreading.h"
@implementation NSObject (JTBlockThreading)

- (void)invokeBlockInBackground:(void (^)())block {
     [self performSelectorInBackground:@selector(invokeBlock:)
                                 withObject:[block copy]];
}
- (void)invokeBlockOnMainThread:(void (^)())block {
     [self invokeBlockOnMainThread:block
                         waitUntilDone:NO];
}
- (void)invokeBlockOnMainThread:(void (^)())block
                      waitUntilDone:(BOOL)wait {
     [self performSelectorOnMainThread:@selector(invokeBlock:)
                                 withObject:[block copy]
                              waitUntilDone:wait];
}
- (void)invokeBlock:(void (^)())block {
     //NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
     block();
     [block release];
     //[pool release];
}

@end

- (void)fetchArrivalsForLocationIDs:(NSString *)locIDs
                                 callback:(JTWSCallback)callback
                                   errback:(JTWSErrback)errback {
     JT_PUSH_NETWORK();
     JT_BLOCKTHREAD_BACKGROUND

     NSError *error = nil;

     // Create API call URL
     NSURL *url = [NSURL URLWithString:
                      [NSString stringWithFormat:@"%@/arrivals/appID/%@/locIDs/%@",
                       TRIMET_BASE_URL, appID, locIDs]];
     if (!url) {
          JT_BLOCKTHREAD_MAIN
          errback(@"That’s not a valid Stop ID!");
          JT_POP_NETWORK();
          JT_BLOCKTHREAD_END
          return;
     }

     // Call API
     NSData *data = [NSData dataWithContentsOfURL:url
                                                   options:0
                                                     error:&error];
     if (!data) {
          JT_BLOCKTHREAD_MAIN
          errback([NSString stringWithFormat:
                     @"Had trouble downloading the arrival data! %@",
                     [error localizedDescription]]);
          JT_POP_NETWORK();
          JT_BLOCKTHREAD_END
          return;
     }
     CXMLDocument *doc = [[CXMLDocument alloc] initWithData:data
                                                               options:0
                                                                  error:&error];
     if (!doc) {
          JT_BLOCKTHREAD_MAIN
          // TODO: further error description
          // (TouchXML doesn't provide information with the error)
          errback(@"Had trouble reading the arrival data!");
          JT_POP_NETWORK();
          JT_BLOCKTHREAD_END
          return;
     }

     NSArray *nodes = nil;

     CXMLElement *resultSet = [doc rootElement];

     // Begin building the response model
     JTWSResponseArrivalData *response = [[[JTWSResponseArrivalData alloc] init] autorelease];
     response.queryTime = [NSDate JT_dateWithTriMetWSTimestamp:
                                [[resultSet attributeValueForName:@"queryTime"] longLongValue]];
     if (!response.queryTime) {
          // TODO: further error check?
          NSLog(@"Hm, query time is nil in %s... response %@, resultSet %@",
                 __PRETTY_FUNCTION__, response, resultSet);
     }

     nodes = [resultSet nodesForXPath:@"//arrivals:errorMessage"
                       namespaceMappings:namespaceMappings
                                      error:&error];
     if ([nodes count] > 0) {
          NSString *message = [[nodes objectAtIndex:0] stringValue];
          response.errorMessage = message; // TODO: this probably won't be used...
          JT_BLOCKTHREAD_MAIN
          errback([NSString stringWithFormat:
                     @"TriMet error: “%@”", message]);
          JT_POP_NETWORK();
          JT_BLOCKTHREAD_END
          return;
     }

     // Build location models
     nodes = [resultSet nodesForXPath:@"/arrivals:location"
                       namespaceMappings:namespaceMappings
                                      error:&error];
     if ([nodes count] <= 0) {
          NSLog(@"Hm, no locations returned in %s... xpath error %@, response %@, resultSet %@",
                 __PRETTY_FUNCTION__, error, response, resultSet);
     }

     NSMutableArray *locations = [NSMutableArray arrayWithCapacity:[nodes count]];

     for (CXMLElement *loc in nodes) {
          JTWSLocation *location = [[[JTWSLocation alloc] init] autorelease];
          location.desc = [loc attributeValueForName:@"desc"];
          location.dir = [loc attributeValueForName:@"dir"];
          location.position = [[[CLLocation alloc]
                                     initWithLatitude:[[loc attributeValueForName:@"lat"] doubleValue]
                                     longitude:[[loc attributeValueForName:@"lng"] doubleValue]] autorelease];
          location.locID = [[loc attributeValueForName:@"locid"] integerValue];
     }


     // Build arrival models
     nodes = [resultSet nodesForXPath:@"/arrivals:arrival"
                       namespaceMappings:namespaceMappings
                                      error:&error];
     if ([nodes count] <= 0) {
          NSLog(@"Hm, no arrivals returned in %s... xpath error %@, response %@, resultSet %@",
                 __PRETTY_FUNCTION__, error, response, resultSet);
     }

     NSMutableArray *arrivals = [[NSMutableArray alloc] initWithCapacity:[nodes count]];

     for (CXMLElement *arv in nodes) {
          JTWSArrival *arrival = [[JTWSArrival alloc] init];
          arrival.block = [[arv attributeValueForName:@"block"] integerValue];
          arrival.piece = [[arv attributeValueForName:@"piece"] integerValue];
          arrival.locID = [[arv attributeValueForName:@"locid"] integerValue];
          arrival.departed = [[arv attributeValueForName:@"departed"] boolValue]; // TODO: verify
          arrival.detour = [[arv attributeValueForName:@"detour"] boolValue]; // TODO: verify
          arrival.direction = (JTWSRouteDirection)[[arv attributeValueForName:@"dir"] integerValue];
          arrival.estimated = [NSDate JT_dateWithTriMetWSTimestamp:
                                    [[arv attributeValueForName:@"estimated"] longLongValue]];
          arrival.scheduled = [NSDate JT_dateWithTriMetWSTimestamp:
                                    [[arv attributeValueForName:@"scheduled"] longLongValue]];
          arrival.fullSign = [arv attributeValueForName:@"fullSign"];
          arrival.shortSign = [arv attributeValueForName:@"shortSign"];
          NSString *status = [arv attributeValueForName:@"status"];
          if ([status isEqualToString:@"estimated"]) {
               arrival.status = JTWSArrivalStatusEstimated;
          } else if ([status isEqualToString:@"scheduled"]) {
               arrival.status = JTWSArrivalStatusScheduled;
          } else if ([status isEqualToString:@"delayed"]) {
               arrival.status = JTWSArrivalStatusDelayed;
          } else if ([status isEqualToString:@"canceled"]) {
               arrival.status = JTWSArrivalStatusCanceled;
          } else {
               NSLog(@"Unknown arrival status %s in %@... response %@, arrival %@",
                      status, __PRETTY_FUNCTION__, response, arv);
          }

          NSArray *blockPositions = [arv nodesForXPath:@"/arrivals:blockPosition"
                                           namespaceMappings:namespaceMappings
                                                          error:&error];
          if ([blockPositions count] > 1) {
               // The schema allows for any number of blockPosition elements,
               // but I'm really not sure why...
               NSLog(@"Hm, more than one blockPosition in %s... response %@, arrival %@",
                      __PRETTY_FUNCTION__, response, arv);
          }
          if ([blockPositions count] > 0) {
               CXMLElement *bpos = [blockPositions objectAtIndex:0];
               JTWSBlockPosition *blockPosition = [[JTWSBlockPosition alloc] init];
               blockPosition.reported = [NSDate JT_dateWithTriMetWSTimestamp:
                                               [[bpos attributeValueForName:@"at"] longLongValue]];
               blockPosition.feet = [[bpos attributeValueForName:@"feet"] integerValue];
               blockPosition.position = [[[CLLocation alloc]
                                                initWithLatitude:[[bpos attributeValueForName:@"lat"] doubleValue]
                                                longitude:[[bpos attributeValueForName:@"lng"] doubleValue]] autorelease];
               NSString *headingStr = [bpos attributeValueForName:@"heading"];
               if (headingStr) {
                    // Valid CLLocationDirections are > 0
                    CLLocationDirection heading = [headingStr integerValue];
                    while (heading < 0) heading += 360.0;
                    blockPosition.heading = heading;
               } else {
                    blockPosition.heading = -1; // indicates invalid heading
               }

               NSArray *tripData = [bpos nodesForXPath:@"/arrivals:trip"
                                          namespaceMappings:namespaceMappings
                                                         error:&error];
               NSMutableArray *trips = [[NSMutableArray alloc] initWithCapacity:[tripData count]];
               for (CXMLElement *tripDatum in tripData) {
                    JTWSTrip *trip = [[JTWSTrip alloc] init];
                    trip.desc = [tripDatum attributeValueForName:@"desc"];
                    trip.destDist = [[tripDatum attributeValueForName:@"destDist"] integerValue];
                    trip.direction = (JTWSRouteDirection)[[tripDatum attributeValueForName:@"dir"] integerValue];
                    trip.pattern = [[tripDatum attributeValueForName:@"pattern"] integerValue];
                    trip.progress = [[tripDatum attributeValueForName:@"progress"] integerValue];
                    trip.route = [[tripDatum attributeValueForName:@"route"] integerValue];
                    [trips addObject:trip];
                    [trip release];
               }
               blockPosition.trips = trips;
               [trips release];

               NSArray *layoverData = [bpos nodesForXPath:@"/arrivals:layover"
                                              namespaceMappings:namespaceMappings
                                                             error:&error];
               NSMutableArray *layovers = [[NSMutableArray alloc] initWithCapacity:[layoverData count]];
               for (CXMLElement *layoverDatum in layoverData) {
                    JTWSLayover *layover = [[JTWSLayover alloc] init];
                    layover.start = [NSDate JT_dateWithTriMetWSTimestamp:
                                         [[layoverDatum attributeValueForName:@"start"] longLongValue]];
                    layover.end = [NSDate JT_dateWithTriMetWSTimestamp:
                                      [[layoverDatum attributeValueForName:@"end"] longLongValue]];
                    // TODO: it seems the API can send a <location> inside a layover (undocumented)... support?
                    [layovers addObject:layover];
                    [layover release];
               }
               blockPosition.layovers = layovers;
               [layovers release];

               arrival.blockPosition = blockPosition;
               [blockPosition release];
          }

          [arrivals addObject:arrival];
          [arrival release];
     }

     // Add arrivals to corresponding locations
     for (JTWSLocation *loc in locations) {
          loc.arrivals = [arrivals selectWithBlock:^BOOL (id arv) {
               return loc.locID == ((JTWSArrival *)arv).locID;
          }];
     }
     [arrivals release];

     response.locations = locations;
     [locations release];

     // Build route status models (used in inclement weather)
     nodes = [resultSet nodesForXPath:@"/arrivals:routeStatus"
                       namespaceMappings:namespaceMappings
                                      error:&error];
     NSMutableArray *routeStatuses = [NSMutableArray arrayWithCapacity:[nodes count]];

     for (CXMLElement *stat in nodes) {
          JTWSRouteStatus *status = [[JTWSRouteStatus alloc] init];
          status.route = [[stat attributeValueForName:@"route"] integerValue];
          NSString *statusStr = [stat attributeValueForName:@"status"];
          if ([statusStr isEqualToString:@"estimatedOnly"]) {
               status.status = JTWSRouteStatusTypeEstimatedOnly;
          } else if ([statusStr isEqualToString:@"off"]) {
               status.status = JTWSRouteStatusTypeOff;
          } else {
               NSLog(@"Unknown route status type %s in %@... response %@, routeStatus %@",
                      status, __PRETTY_FUNCTION__, response, stat);
          }
          [routeStatuses addObject:status];
          [status release];
     }
     response.routeStatuses = routeStatuses;
     [routeStatuses release];

     JT_BLOCKTHREAD_MAIN
     callback(response);
     JT_POP_NETWORK();
     JT_BLOCKTHREAD_END

     JT_BLOCKTHREAD_END
}

© Stack Overflow or respective owner

Related posts about iphone

Related posts about objective-c