bool _WebTryThreadLock(bool) crash with UITableView - iphone

I am trying to display tweets in UITableView.
Code :-
- (void)requestTimeline:(int)count
{
NSURL *url = [NSURL URLWithString:#"http://api.twitter.com/1/statuses/user_timeline.json"];
NSMutableDictionary *parameters = [[NSMutableDictionary alloc] init];
[parameters setObject:#"abc" forKey:#"screen_name"];
[parameters setObject:#"20" forKey:#"count"];
[parameters setObject:#"1" forKey:#"include_entities"];
TWRequest *request = [[TWRequest alloc] initWithURL:url parameters:parameters requestMethod:TWRequestMethodGET];
[request performRequestWithHandler:^(NSData *responseData1, NSHTTPURLResponse *urlResponse, NSError *error)
{
if (responseData1 != nil)
{
NSError *error = nil;
self.itemsToDisplay = [NSJSONSerialization JSONObjectWithData:responseData1 options:NSJSONReadingMutableLeaves error:&error];
if (self.itemsToDisplay != nil)
{
[self.tableView reloadData];
}
else
{
NSLog(#"Error serializing response data %# with user info %#.", error, error.userInfo);
}
}
else
{
NSLog(#"Error requesting timeline %# with user info %#.", error, error.userInfo);
}
}];
}
self is UITableViewController's subclass.
Due to block, twitter request will be performed on another thread not on main thread. Thats why it get crashed.
//TWRequest.h
// Issue the request. This block is not guaranteed to be called on any particular thread.
- (void)performRequestWithHandler:(TWRequestHandler)handler;
Crash reort:-
bool _WebTryThreadLock(bool), 0xa8e4f50: Tried to obtain the web lock
from a thread other than the main thread or the web thread. This may
be a result of calling to UIKit from a secondary thread. Crashing
now...
How resolve this issue ?
Thanks...

the web view must only ever be used from the main thread. much of UIKit -- the tableView reloadData command is also not thread-safe
dispatch_async(dispatch_get_main_queue(), ^{
//all you ever do with UIKit.. in your case the reloadData call
[self.tableView reloadData];
});

Solved :
Also below code works :
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self.tableView reloadData];
}];

try:
[self.tableView performSelectorInMainThread:#selector(reloadData)];
instead of:
[self.tableView reloadData];

Insert this, may be it helps:
[self.tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO];

Related

issue with NSOperationQueue, weakSelf and blocks

I have the following code:
[[AHPinterestAPIClient sharedClient] getPath:requestURLPath parameters:nil
success:^(AFHTTPRequestOperation *operation, id response) {
[weakSelf.backgroundQueue_ addOperationWithBlock:^{
[self doSomeHeavyComputation];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[weakSelf.collectionView_ setContentOffset:CGPointMake(0, 0)];
[weakSelf.collectionView_ reloadData];
[weakSelf.progressHUD_ hide:YES];
[[NSNotificationCenter defaultCenter] performSelector:#selector(postNotificationName:object:) withObject:#"UIScrollViewDidStopScrolling" afterDelay:0.3];
[weakSelf.progressHUD_ hide:YES];
}];
}];
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[weakSelf.progressHUD_ hide:YES];
[weakSelf.collectionView_.pullToRefreshView stopAnimating];
NSLog(#"Error fetching user data!");
NSLog(#"%#", error);
}];
For some reason this worked just fine in iOS 5, but not iOS 6 (it crashes). Now I am not going to ask about iOS 6 because it's still under NDA. What I want to know is, whether the code above is wrong? If yes how do I fix it.
If I put the code inside the block outside of the mainQueue then it's fine.
What I am trying to do here is to do the NSOperationQueue mainQueue only after the [self doSomeHeavyComputation] is done. So this is a dependency, how should I add this dependency?
Update:
Here's the crash log if it helps:
It is recommended to “unwind” weak references in the block, so please try this:
__weak id weakSelf = self;
[client getPath:path parameters:nil success:^(id op, id response) {
id strongSelf = weakSelf;
if (strongSelf == nil) return;
__weak id internalWeakSelf = strongSelf;
[strongSelf.backgroundQueue_ addOperationWithBlock:^{
id internalStrongSelf = internalWeakSelf;
if (internalStrongSelf == nil) return;
[internalStrongSelf doSomeHeavyComputation];
__weak id internalInternalWeakSelf = internalStrongSelf;
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
id internalInternalStrongSelf = internalInternalWeakSelf;
if (internalInternalStrongSelf == nil) return;
[internalInternalStrongSelf reloadCollectionView];
}];
}];
}
failure:^(id op, NSError *error) {
id strongSelf = weakSelf;
if (strongSelf == nil) return;
[strongSelf stopProgress];
NSLog(#"Error fetching user data: %#", error);
}];

IOS5 - AFNetworking processing request

I'm making an application in which I have to call some webservices. I chose to work with AFNetworking.
I followed the Twitter example provided in the library. Everything works well except that I have permanently the little "processing circle" in the notification bar (see the image below).
Here's the code I have for my request :
- (id)initWithAttributes:(NSDictionary *)attributes
{
self = [super init];
if (!self) {
return nil;
}
_name = [attributes valueForKeyPath:#"name"];
return self;
}
+ (void)itemsListWithBlock:(void (^)(NSArray *items))block
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSDictionary *user = [defaults objectForKey:#"user"];
NSDictionary *company = [defaults objectForKey:#"company"];
NSMutableDictionary *mutableParameters = [NSMutableDictionary dictionary];
/*
** [ Some stuff to set the parameters in a NSDictionnary ]
*/
MyAPIClient *client = [MyAPIClient sharedClient];
[[AFNetworkActivityIndicatorManager sharedManager] setEnabled:YES];
[[AFNetworkActivityIndicatorManager sharedManager] incrementActivityCount];
NSURLRequest *request = [client requestWithMethod:#"POST" path:#"getMyList" parameters:mutableParameters];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
NSMutableArray *mutableItems = [NSMutableArray arrayWithCapacity:[JSON count]];
for (NSDictionary *attributes in JSON) {
ListItem *item = [[ListItem alloc] initWithAttributes:attributes];
[mutableItems addObject:item];
}
if (block) {
block([NSArray arrayWithArray:mutableItems]);
}
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON){
[[[UIAlertView alloc] initWithTitle:#"Error" message:[error localizedDescription] delegate:nil cancelButtonTitle:nil otherButtonTitles:#"Ok", nil] show];
if (block) {
block(nil);
}
}];
[operation start];
}
Does this means my request isn't finished ? I'm not really getting what I'm doing wrong here...
If someone could help, I'd really appreciate. Thanks.
Don't call [[AFNetworkActivityIndicatorManager sharedManager] incrementActivityCount]; this increase the activity count with 1 and the [operation start]; will call it also. now the activity count is 2 and will get decreased when the operation is done. But since you called the incrementActivityCount it will bring it back to 1 and not 0.
Just call [[AFNetworkActivityIndicatorManager sharedManager] setEnabled:YES]; once, for example place it in the application:applicationdidFinishLaunchingWithOptions: method of your applications appDeletage.
Also I would suggest to add the operation to a NSOperationQueue and not just call start on it.

iOS - Thread does not going back to the main thread

I having trouble with my threads.
After i segue a couple of times between 2 screen when the thread is busy. The thread don't perform every line.., The breakpoint just disappear when it has to return to the main thread.
Can somebody please help me ?
I release the thread when the view is unload.
Thanks,
- (void)fetchFeedDataIntoDocument
{
NSString * labelString = [NSString stringWithFormat:#"Feed Fetcher %#", self.pageTitle];
const char *label = [labelString UTF8String];
self.fetchF = dispatch_queue_create(label, NULL);
dispatch_async(self.fetchF, ^{
NSArray *feeds = [FeedFetcher getDataForJson:self.pageTitle downloadBy:#"up"];
NSDictionary *lastfeed;
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate getManagedObjectContext];
if ([feeds count] > 0)
{
lastfeed = [feeds objectAtIndex:0];
[FeedFetcher setLastUpdateIdToCatgorie:self.pageTitle WithId:[lastfeed objectForKey:#"id"] AndPulishDate:[lastfeed objectForKey:#"publish_up"]];
}
for (NSDictionary *feedInfo in feeds) {
[Feed FeedWithInfo:feedInfo InManageObject:context];
}
NSError *error = nil;
[context save:&error];
if (error){
NSLog(#"Error save : %#", error);}
dispatch_async(dispatch_get_main_queue(), ^{
[self setupFetchedResultsController];
[self.tableView reloadData];
[self downloadImagesForFeeds:feeds];
});
});
You are accessing the managedObjectContext from a different thread from where it was created. This is Core Data Threading Rule #1.
You are getting the MOC from the app delegate. If it's the normal Xcode-generated MOC, then it is created with thread-confinement concurrency. You can't even call performBlock with it. You can only access that MOC from the main thread. Period. Anything else is playing with fire, at best.
If you want to do all the work in a separate thread, you need a separate MOC as well. Like this (just typed - not compiled)...
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
moc.parentContext = appDelegate.managedObjectContext;
[moc performBlock:^{
// Go get your remote data and whatever you want to do
// Calling save on this MOC will push the data up into the "main" MOC
// (though it is now in the main MOC it has not been saved to the store).
[moc save:&error];
}];
Which would translate into something like this...
- (void)fetchFeedDataIntoDocument
{
NSString * labelString = [NSString stringWithFormat:#"Feed Fetcher %#", self.pageTitle];
const char *label = [labelString UTF8String];
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *mainContext = [appDelegate getManagedObjectContext];
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
context.parentContext = mainContext;
[context performBlock:^{
NSArray *feeds = [FeedFetcher getDataForJson:self.pageTitle downloadBy:#"up"];
NSDictionary *lastfeed;
if ([feeds count] > 0)
{
lastfeed = [feeds objectAtIndex:0];
[FeedFetcher setLastUpdateIdToCatgorie:self.pageTitle WithId:[lastfeed objectForKey:#"id"] AndPulishDate:[lastfeed objectForKey:#"publish_up"]];
}
for (NSDictionary *feedInfo in feeds) {
[Feed FeedWithInfo:feedInfo InManageObject:context];
}
NSError *error = nil;
[context save:&error];
if (error){
NSLog(#"Error save : %#", error);}
DO you really want to continue on error?
dispatch_async(dispatch_get_main_queue(), ^{
// Data has been pushed into main context from the background
// but it still needs to be saved to store...
// Do not forget to perform error handling...
NSError *error = nil;
[mainContext save:&error];
[self setupFetchedResultsController];
[self.tableView reloadData];
[self downloadImagesForFeeds:feeds];
});
});
EDIT
The code generated by Xcode for creating the MOC uses init, which uses NSConfinementConcurrencyType. You can replace it with MainConcurrency, without any problems, but get several benefits.
In your app delegate file, replace...
__managedObjectContext = [[NSManagedObjectContext alloc] init];
with this...
__managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
Now, your main MOC can be "parented" and you can also call performBlock on it as well.
what about doing this...
-(void)functionToCallFetch {
[self performSelectorInBackground:#selector(fetchFeedDataIntoDocument) withObject:nil];
}
- (void)fetchFeedDataIntoDocument
{
NSArray *feeds = [FeedFetcher getDataForJson:self.pageTitle downloadBy:#"up"];
NSDictionary *lastfeed;
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate getManagedObjectContext];
if ([feeds count] > 0)
{
lastfeed = [feeds objectAtIndex:0];
[FeedFetcher setLastUpdateIdToCatgorie:self.pageTitle WithId:[lastfeed objectForKey:#"id"] AndPulishDate:[lastfeed objectForKey:#"publish_up"]];
}
for (NSDictionary *feedInfo in feeds) {
[Feed FeedWithInfo:feedInfo InManageObject:context];
}
NSError *error = nil;
[context save:&error];
if (error){
NSLog(#"Error save : %#", error);}
//dispatch_async(dispatch_get_main_queue(), ^{
// [self setupFetchedResultsController];
// [self.tableView reloadData];
// [self downloadImagesForFeeds:feeds];
//});
[self performSelectorOnMainThread:#selector(setupFetchedResultsController) withObject:nil waitUntilDone:NO];
[self.tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO];
[self performSelectorOnMainThread:#selector(downloadImagesForFeeds:) withObject:feeds waitUntilDone:NO];
}
Maybe that would work better?
Change following code..
dispatch_queue_t queue1 = dispatch_queue_create("com.MyApp.AppTask",NULL);
dispatch_queue_t main = dispatch_get_main_queue();
dispatch_async(queue1,
^{
dispatch_async(main,
^{
[self setupFetchedResultsController];
[self.tableView reloadData];
[self downloadImagesForFeeds:feeds];
});
});
Hope, this will help you
Did you try building a method like :
- (void)methodToBePerformedOnMainThread{
[self setupFetchedResultsController];
[self.tableView reloadData];
[self downloadImagesForFeeds:feeds];
}
and call it with
[self performSelectorOnMainThread:#selector(methodToBePerformedOnMainThread) withObject:nil waitUntilDone:NO];
at the end of fetchFeedDataIntoDocument
Edit :
Did you try to wrap it with NSOperationQueue in place of dispatch_queue ?
Like :
NSOperationQueue *operationQueue = [NSOperationQueue new];
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(fetchFeedDataIntoDocument) object:nil];
if(operation != nil){
[operationQueue addOperation:operation];

iOS 5 NSURLConnection with NSOperationQueue - Providing UI Feedback

I need to make multiple NSURLConnections to a JSON Web Service. I would like each WS call to keep in UI informed, probably with a UIActivityIndicatorView and label. So far I've created a NSURLConnection helper class to handle the connection and placed the URL delegates in the View. This works great for updating the UI with a single WS call.
For multiple calls, I'm trying to use an NSOperationQueue. I'd like to setMaxConcurrentOperationCount to one on the queue so that each Operation executes one at a time. Here's the relevant code on my View Controller:
ViewController.m
#import "URLOperationHelper.h"
#implementation ViewController
- (IBAction)showPopup:(id)sender
{
// Dictonary holds POST values
NSMutableDictionary *reqDic = [NSMutableDictionary dictionary];
// Populate POST key/value pairs
[reqDic setObject:#"pw" forKey:#"Password"];
[reqDic setObject:#"ur" forKey:#"UserName"];
operationQueue = [[NSOperationQueue alloc] init];
[operationQueue setMaxConcurrentOperationCount:1];
[operationQueue cancelAllOperations];
[operationQueue setSuspended:YES];
URLOperationHelper *wsCall1 = [[URLOperationHelper alloc] initWithURL:#"urlString1" postParameters:reqDic urlDelegate:self];
URLOperationHelper *wsCall2 = [[URLOperationHelper alloc] initWithURL:#"urlString2" postParameters:reqDic urlDelegate:self];
[operationQueue addOperation:wsCall1];
[operationQueue addOperation:wsCall2];
}
// Did the URL Connection receive a response
-(void) connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
NSLog(#"Did receive response: %#", response);
NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
int code = [httpResponse statusCode];
// Handle status code here
webData = [[NSMutableData alloc]init];
}
// Did the URL Connection receive data
-(void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
NSLog(#"Did receive data: %#", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
assert(webData != nil);
[webData appendData:data];
}
// Did the connection fail with an error?
-(void) connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
NSLog(#"%#", error);
}
// Executes after a successful connection and data download
-(void) connectionDidFinishLoading:(NSURLConnection *)connection
{
NSLog(#"Connection finished");
}
#end
And here is my URLOperationHelper.m
#implementation URLHelper
- (id)initWithURL:(NSString *)urlPath
postParameters:(NSMutableDictionary *)postParameters
urlParentDelegate:(id) pDelegate
{
if(self = [super init])
{
connectionURL = urlPath;
postParams = postParameters;
parentDelegate = pDelegate;
}
return self;
}
- (void)done
{
// Cancel the connection if present
if(urlConnection)
{
[urlConnection cancel];
urlConnection = nil;
}
// Alert
[self willChangeValueForKey:#"isExecuting"];
[self willChangeValueForKey:#"isFinished"];
executing = NO;
finished = YES;
[self willChangeValueForKey:#"isFinished"];
[self willChangeValueForKey:#"isExecuting"];
}
- (void)cancel
{
// Possibly add an NSError Property
[self done];
}
- (void)start
{
// Make sure this operation starts on the main thread
if(![NSThread isMainThread])
{
[self performSelectorOnMainThread:#selector(start) withObject:nil waitUntilDone:NO];
return;
}
// Make sure that the operation executes
if(finished || [self isCancelled])
{
[self done];
return;
}
[self willChangeValueForKey:#"isExecuting"];
executing = YES;
[self main];
[self willChangeValueForKey:#"isExecuting"];
}
- (void)main
{
NSError *error = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:postParams options:NSJSONWritingPrettyPrinted error:&error];
// Convert dictionary to JSON
NSString *requestJSON = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
NSLog(#"JSONRequest: %#", requestJSON);
// Declare Webservice URL, request, and return data
url = [[NSURL alloc] initWithString:connectionURL];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
NSData *requestData = [NSData dataWithBytes:[requestJSON UTF8String] length:[requestJSON length]];
// Build the request
[request setHTTPMethod:#"POST"];
[request setValue:[NSString stringWithFormat:#"%d", [requestData length]] forHTTPHeaderField:#"Content-Length"];
[request setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:requestData];
// Connect to Webservice
// Responses are handled in the delegates below
urlConnection = [[NSURLConnection alloc] initWithRequest:request delegate:parentDelegate startImmediately:YES];
}
- (BOOL)isConcurrent
{
return YES;
}
- (BOOL)isExecuting
{
return executing;
}
-(BOOL)isFinished
{
return finished;
}
#end
The problem that I'm having is the Start method for the URLOperation is never called. The OperationQueue is created and the Operations are called, but nothing happens after that, execution or thread wise.
Also, is this a correct line of thinking to provide UI feedback using NSOperationQueues like this? I.E. calling the NSURLDelegates from the Operation?
If you set setSuspended to YES before adding the Operations then your Operations will be queued into a suspended queue.. i suggest not to suspend the queue at
furthermore, your operation never ends anyway. You need to assign the operation itself as the delegate and implement all necessary delegate methods. In these methods you can forward the messages to your parentDelegate and decide when you are finished and call your done method when appropriate (i suggest connection:didFailWithError: and connectionDidFinishLoading:)
There is a good tutorial here: http://blog.9mmedia.com/?p=549
You are also not completely implementing key-value-coding compilant properties correct. Whenever you call willChangeValueForKey: you also need to call didChangeValueForKey afterwards:
- (void)start
{
...
[self willChangeValueForKey:#"isExecuting"];
executing = YES;
[self didChangeValueForKey:#"isExecuting"];
[self main];
}
and:
- (void)done
{
...
// Alert
[self willChangeValueForKey:#"isExecuting"];
[self willChangeValueForKey:#"isFinished"];
executing = NO;
finished = YES;
[self didChangeValueForKey:#"isFinished"];
[self didChangeValueForKey:#"isExecuting"];
}
See this Q/A for KVC: when to use "willChangeValueForKey" and "didChangeValueForKey"?

NSOperation and EXC_BAD_ACCESS

I have a few apps which are largely data driven, so most screens are basically composed of:
Open the screen
Download the data via an NSOperation
Display data in a UITableView
Make a selection from the UITableView
Go to new screen, and start over from step 1
I am finding that everything works in normal usage, but if the user goes away from the app for a while and then comes back, I'm getting an EXC_BAD_ACCESS error when the next NSOperation runs. This doesn't seem to matter if the user sends the app into the background or not, and it only seems to occur if there's been at least a few mins since the previous data connection was made.
I realise this must be some form of over-releasing, but I'm pretty good with my memory management and I can't see anything wrong. My data calls generally look like this:
-(void)viewDidLoad {
[super viewDidLoad];
NSOperationQueue* tmpQueue = [[NSOperationQueue alloc] init];
self.queue = tmpQueue;
[tmpQueue release];
}
-(void)loadHistory {
GetHistoryOperation* operation = [[GetHistoryOperation alloc] init];
[operation addObserver:self forKeyPath:#"isFinished" options:0 context:NULL];
[self.queue addOperation:operation];
[operation release];
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([keyPath isEqual:#"isFinished"] && [object isKindOfClass:[GetHistoryOperation class]]) {
GetHistoryOperation* operation = (GetHistoryOperation*)object;
if(operation.success) {
[self performSelectorOnMainThread:#selector(loadHistorySuceeded:) withObject:operation waitUntilDone:YES];
} else {
[self performSelectorOnMainThread:#selector(loadHistoryFailed:) withObject:operation waitUntilDone:YES];
}
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
-(void)loadHistorySuceeded:(GetHistoryOperation*)operation {
if([operation.historyItems count] > 0) {
//display data here
} else {
//display no data alert
}
}
-(void)loadHistoryFailed:(GetHistoryOperation*)operation {
//show failure alert
}
And my operations generally looks something like this:
-(void)main {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSError* error = nil;
NSString* postData = [self postData];
NSDictionary *dictionary = [RequestHelper performPostRequest:kGetUserWalkHistoryUrl:postData:&error];
if(dictionary) {
NSNumber* isValid = [dictionary objectForKey:#"IsValid"];
if([isValid boolValue]) {
NSMutableArray* tmpDays = [[NSMutableArray alloc] init];
NSMutableDictionary* tmpWalksDictionary = [[NSMutableDictionary alloc] init];
NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"yyyyMMdd"];
NSArray* walksArray = [dictionary objectForKey:#"WalkHistories"];
for(NSDictionary* walkDictionary in walksArray) {
Walk* walk = [[Walk alloc] init];
walk.name = [walkDictionary objectForKey:#"WalkName"];
NSNumber* seconds = [walkDictionary objectForKey:#"TimeTaken"];
walk.seconds = [seconds longLongValue];
NSString* dateStart = [walkDictionary objectForKey:#"DateStart"];
NSString* dateEnd = [walkDictionary objectForKey:#"DateEnd"];
walk.startDate = [JSONHelper convertJSONDate:dateStart];
walk.endDate = [JSONHelper convertJSONDate:dateEnd];
NSString* dayKey = [dateFormatter stringFromDate:walk.startDate];
NSMutableArray* dayWalks = [tmpWalksDictionary objectForKey:dayKey];
if(!dayWalks) {
[tmpDays addObject:dayKey];
NSMutableArray* dayArray = [[NSMutableArray alloc] init];
[tmpWalksDictionary setObject:dayArray forKey:dayKey];
[dayArray release];
dayWalks = [tmpWalksDictionary objectForKey:dayKey];
}
[dayWalks addObject:walk];
[walk release];
}
for(NSString* dayKey in tmpDays) {
NSMutableArray* dayArray = [tmpWalksDictionary objectForKey:dayKey];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"startDate" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
NSArray* sortedDayArray = [dayArray sortedArrayUsingDescriptors:sortDescriptors];
[sortDescriptor release];
[tmpWalksDictionary setObject:sortedDayArray forKey:dayKey];
}
NSSortDescriptor* sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:nil ascending:NO selector:#selector(localizedCompare:)];
self.days = [tmpDays sortedArrayUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]];
self.walks = [NSDictionary dictionaryWithDictionary:tmpWalksDictionary];
[tmpDays release];
[tmpWalksDictionary release];
[dateFormatter release];
self.success = YES;
} else {
self.success = NO;
self.errorString = [dictionary objectForKey:#"Error"];
}
if([dictionary objectForKey:#"Key"]) {
self.key = [dictionary objectForKey:#"Key"];
}
} else {
self.errorString = [error localizedDescription];
if(!self.errorString) {
self.errorString = #"Unknown Error";
}
self.success = NO;
}
[pool release];
}
-(NSString*)postData {
NSMutableString* postData = [[[NSMutableString alloc] init] autorelease];
[postData appendFormat:#"%#=%#", #"LoginKey", self.key];
return [NSString stringWithString:postData];
}
----
#implementation RequestHelper
+(NSDictionary*)performPostRequest:(NSString*)urlString:(NSString*)postData:(NSError**)error {
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
NSURL* url = [NSURL URLWithString:[NSString stringWithFormat:#"%#/%#", kHostName, urlString]];
NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:30];
[urlRequest setHTTPMethod:#"POST"];
if(postData && ![postData isEqualToString:#""]) {
NSString *postLength = [NSString stringWithFormat:#"%d", [postData length]];
[urlRequest setHTTPBody:[postData dataUsingEncoding:NSASCIIStringEncoding]];
[urlRequest setValue:postLength forHTTPHeaderField:#"Content-Length"];
[urlRequest setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
}
NSURLResponse *response = nil;
error = nil;
NSData *jsonData = [NSURLConnection sendSynchronousRequest:(NSURLRequest *)urlRequest returningResponse:(NSURLResponse **)&response error:(NSError **)&error];
NSString *jsonString = [[NSString alloc] initWithBytes: [jsonData bytes] length:[jsonData length] encoding:NSUTF8StringEncoding];
NSLog(#"JSON: %#",jsonString);
//parse JSON
NSDictionary *dictionary = nil;
if([jsonData length] > 0) {
dictionary = [[CJSONDeserializer deserializer] deserializeAsDictionary:jsonData error:error];
}
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
return dictionary;
}
If I have the autorelease pool in place, the crash occurs on [pool release]. If I don't, then the crash just looks to appear in the main.m method, and I don't seem to get any useful information. It's difficult to track down when I have to wait 10 mins in between every test!
If anyone can offer any clues or directions to go, that'd be much appreciated.
It's almost certain you're overreleasing something in your code, seeing that the crash is occurring during a [pool release] (There's a autorelease pool in the main method as well).
You can find it using Xcode - use build and analyze to have the static analyser pinpoint potential problems. Run it and post the results.
try this:
http://cocoadev.com/index.pl?NSZombieEnabled
also, you should avoid:
1) calling UIKit methods from secondary threads
2) making (synchronous) url requests from the main thread.
you must be doing one in any case in RequestHelper's performPostRequest method.
My guess is this section
GetHistoryOperation* operation = (GetHistoryOperation*)object;
if(operation.success) {
[self performSelectorOnMainThread:#selector(loadHistorySuceeded:) withObject:operation waitUntilDone:YES];
} else {
[self performSelectorOnMainThread:#selector(loadHistoryFailed:) withObject:operation waitUntilDone:YES];
}
If the sleep happens at a bad point here, you have an object being passed to another thread. I'd find a way around having to pass the operation as the object.
This is a really old question, so sorry for the dredge, but there is no accepted answer.
I was also getting a EXC_BAD_ACCESS on NSOperationQueue -addOperation for seemingly no reason, and after a few days of hunting down memory leaks, and turning on all the debugger options i could find (malloc guard, zombies) and getting nothing, I found an NSLog warning that said: "[NSoperation subclass] set to IsFinished before being started by the queue."
When I modified my base operation subclass, so that its -cancel function only set (IsRunning = NO) and (IsFinished = YES) IF AND ONLY IF (IsRunning == YES), NSOperationQueue stopped crashing.
So if you're ever calling NSOperationQueue -cancelAllOperations, or you're doing that manually (i.e. for (NSOperation *op in queue.allOperations) ) double check to make sure that you don't set IsFinished on those operations in your subclass implementation.