Crash: iPhone Threading with Blocks
- by jtbandes
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
}