I need to make several https calls to a certain url. Therefore I do something like this
//ViewController Source
-(IBAction) updateButton_tapped {
[self performSelectorInBackground:#selector(updateStuff) withObject:nil];
}
-(void) updateStuff {
// do other stuff here...
NSMutableURLRequest* request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:self.url]];
[request setHTTPMethod:#"POST"];
NSData *postData = [[Base64 encodeBase64WithData:payload] dataUsingEncoding:NSASCIIStringEncoding];
[request setHTTPBody:postData];
NSURLResponse* response = [[NSURLResponse alloc] init];
NSData* data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:nil];
//Process the recieved data...
//Setup another synchronous request
data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:nil];
//Process data
//do this another 4 times (note for loop cannot be use in my case ;) )
//Finally update some view controllers
[[NSNotificationCenter defaultCenter] postNotificationName:#"NotificationIdentifier" object:self];
}
So the problem with this code is that it crashes randomly (not always but frequently). I get no debugging output on the log. Sometime my whole app freezes or it simply crashes the whole program. But it never crashes if I run it on the main thread. Therefore I think the code is correct and I suppose now that it has something to do with the threading on the iphone.
What problems could happen when running the code this way and what might cause a random crashes?
Memory Management, you don't release your request or response objects after allocation.
Please re-check your code so you don't update any GUI in background thread. Also, it should be much better to use asynchronous processing.
The controllers or whatever are probably assuming they're receiving notifications on the main thread, which in your case they're not (and that's never a safe assumption to make). Have the controllers dispatch back to the main thread in their notification callbacks before they do anything with the data/updating the UIKit stuff, etc.
You should also put an #autorelease block around your entire implementation of -updateStuff.
Here's an example of a callback notification you might receive in one of your controllers:
- (void)updateStuffNotificaiton:(NSNotification*)note
{
// Can't assume we're on the main thread and no need to
// test since this is made async by performSelectorInBacground anyway
dispatch_async(dispatch_get_main_queue(), ^{
// relocate all your original method implementation here
});
}
Also note that if your implementation of -updateStuff is creating and manipulating data structures that your notification callback methods then access, it is important to properly guard those accessors. It's often better to pass the data wholesale back to the callbacks in the notification's userInfo dictionary.
An example of adding the autorelease notation to your -updateStuff method:
-(void) updateStuff
{
#autoreleasepool {
// do other stuff here...
NSMutableURLRequest* request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:self.url]];
[request setHTTPMethod:#"POST"];
// rest of method snipped for brevity
//Finally update some view controllers
[[NSNotificationCenter defaultCenter] postNotificationName:#"NotificationIdentifier" object:self];
}
}
Related
I am using objective-C to write an app which needs to dispatch 100 web request and the response will be handled in the call back. My question is, how can I execute web req0, wait for call back, then execute web req1 and so on?
Thanks for any tips and help.
NSURL *imageURL = [[contact photoLink] URL];
GDataServiceGoogleContact *service = [self contactService];
// requestForURL:ETag:httpMethod: sets the user agent header of the
// request and, when using ClientLogin, adds the authorization header
NSMutableURLRequest *request = [service requestForURL:imageURL
ETag: nil
httpMethod:nil];
[request setValue:#"image/*" forHTTPHeaderField:#"Accept"];
GTMHTTPFetcher *fetcher = [GTMHTTPFetcher fetcherWithRequest:request];
fetcher.retryEnabled = YES;
fetcher.maxRetryInterval = 0.3;
fetcher.minRetryInterval = 0.3;
[fetcher setAuthorizer:[service authorizer]];
[fetcher beginFetchWithDelegate:self
didFinishSelector:#selector(imageFetcher:finishedWithData:error:)];
}
- (void)imageFetcher:(GTMHTTPFetcher *)fetcher finishedWithData:(NSData *)data error:(NSError *)error {
if (error == nil) {
// got the data; display it in the image view. Because this is sample
// code, we won't be rigorous about verifying that the selected contact hasn't
// changed between when the fetch began and now.
// NSImage *image = [[[NSImage alloc] initWithData:data] autorelease];
// [mContactImageView setImage:image];
NSLog(#"successfully fetched the data");
} else {
NSLog(#"imageFetcher:%# failedWithError:%#", fetcher, error);
}
}
You can't simply call this code in a loop as GTMHTTPFetcher works asynchronously so the loop, as you see, will iterate and start all instances without any delay.
A simple option is to put all of the contacts into a mutable array, take the first contact from the array (remove it from the array) and start the first fetcher. Then, in the finishedWithData callback, check if the array contains anything, if it does remove the first item and start a fetch with it. In this way the fetches will run serially one after the other.
A better but more complex solution would be to create an asynchronous NSOperation (there are various guides on the web) which starts a fetch and waits for the callback before completing. The benefit of this approach is that you can create all of your operations and add them to an operation queue, then you can set the max concurrent count and run the queue - so you can run multiple fetch instances at the same time. You can also suspend the queue or cancel the operations if you need to.
Using NSOperationQueue to upload multiple files to server.
//Class B
While uploading multiple files to server at the time move to popviewcontroller (class A). App suddenly crashed.
Is there any way to uploading a files to server without interrupt.
Thanks in Advance
//Class B
-(void)UploadtoS3
{
// Convert file to data from locapathfile here
NSData* imgData = [NSData dataWithContentsOfFile:localFilePath]; //VIDEO FILEPATH OR IMAGEPATH
if(![self upload_NetworkQueue]) // If Queue is not initialized
{
[[self upload_NetworkQueue] cancelAllOperations];
[self setUpload_NetworkQueue:[ASINetworkQueue queue]];
[[self upload_NetworkQueue] setDelegate:self];
[[self upload_NetworkQueue] setRequestDidFailSelector:#selector(upload_RequestFailed:)];
[[self upload_NetworkQueue] setQueueDidFinishSelector:#selector(upload_RequestDone:)];
[[self upload_NetworkQueue] setShowAccurateProgress:YES];
[[self upload_NetworkQueue] setMaxConcurrentOperationCount:1];
}
NSString *s3keyPath = [NSString stringWithFormat:#"/test123/%#",fileName];
NSLog(#"UPLOAD IMAGE S3 FILE NAME ----------- %#",s3keyPath);
request = [ASIS3ObjectRequest PUTRequestForData:imgData withBucket:testBuck key:s3keyPath];
[request setSecretAccessKey:s3SecretKey];
[request setAccessKey:s3AccessKey];
[request setTimeOutSeconds:20];
[request setDelegate:self];
[request setNumberOfTimesToRetryOnTimeout:3];
[request setMimeType:mimeType];
[request setUploadProgressDelegate:self];
[[self upload_NetworkQueue] addOperation:request];
[[self upload_NetworkQueue] go];
}
As the other poster suggested, you should move away from ASI's code. It is no longer supported.
As to your problem, though. My guess is that the code you posted is in a view controller. (Class B in your post) You are setting the view controller as the delegate of your networking class.
Then you are popping from the view controller that initiated the download to another view controller. That causes the view controller that requested the uploads to be deallocated. Thus, when the download queue tries to send a notification to it's delegate, you crash.
In general you should not make a view controller manage application global things like downloads. It would be better to create a singleton class that manages your downloads for you. If you're not familiar with the singleton design pattern then do a google search. There are lots of tutorials online explaining how to set up a singleton in iOS/Objective-C.
I have some data that I am getting from a server and then displaying in my UIViewController class. To do this, I have two classes. The UIViewController and another one named ServerCommunicator. UIViewController is the delegate for ServerCommunicator class. The serverCommunicator looks as follows:
- (void)fetchServerData:(NSString *) serverAddress{
NSURL *url = [[NSURL alloc] initWithString:serverAddress];
[NSURLConnection sendAsynchronousRequest:[[NSURLRequest alloc] initWithURL:url] queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (error) {
[self.delegate fetchingSongsFailedWithError:error];
} else {
[self.delegate receivedSongsJSON:data];
}
}];
}
The UIViewController allocates the serverCommunicator, sets itself as delegate and then issue the fetch request.
- (void)viewDidLoad {
[super viewDidLoad];
self.songServerCommunicator = [[serverCommunicator alloc] init];
self.songServerCommunicator.delegate = self;
[self.songServerCommunicator fetchServerData:<some_server_ip>];
}
After it does that it implements the required protocol method:
- (void)receivedSongsJSON:(NSData *)data{
NSLog(#"received server response");
/* Parses the data and displays in textfield/imageview */
}
My problem is that when I do display the data received in the delegate method, it doesn't get reflected right away in the UI. It is very weird, sometimes it gets shown 20 seconds laters on its own, other times it takes like a minute. I am not sure whats going on. I know for a fact that the data was fetched right away because the logged message gets printed way before the UIView gets updated.
Thanks for any help on this.
Make sure you are on the main thread when you update the UI
Other people have pointed out the problem, but they did not provide the solution in concrete code. This is the coded solution:
- (void)fetchServerData:(NSString *) serverAddress{
NSURL *url = [[NSURL alloc] initWithString:serverAddress];
[NSURLConnection sendAsynchronousRequest:[[NSURLRequest alloc] initWithURL:url] queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
dispatch_async(
dispatch_get_main_queue(),
^void {
if (error) {
[self.delegate fetchingSongsFailedWithError:error];
} else {
[self.delegate receivedSongsJSON:data];
}
}
);
}];
}
You must understand how Grand Central Dispatch works in iOS. GCD is an abstraction layer for multithreading.
The main queue, which your UI runs on, is a serial queue. That means you can't have two blocks of code running at the same time. It would be poor user experience if you were to do a long network query on the main queue, because it would prevent any UI code from running. This would make the app appear like it is frozen to the user.
To solve the freezing UI issue, iOS gives you other queues to do work without blocking up the main queue. iOS provides ways to create your own custom queues or use pre-made global concurrent queues. The queues that NSURLConnection uses is not known to us because we don't have the Cocoa source code. But what we do know is that NSURLConnection is definitely not using the main queue because UI is not frozen while it is grabbing data from a server.
Since NSURLConnection is running on a different thread than what the main queue (UI) runs on, you cannot just update UI at any random time. This is a common problem with multithreaded programs. When multiple threads access the same block of code, instructions may be interleaved and both blocks get unexpected results. One of the unexpected results that you experienced was the 20 second delay of the UI finally being updated. To prevent interleaving, you need to run all the UI code on the same thread. You do this by enqueuing your UI update code to the end of the main queue.
The method receivedSongsJSON() is called from a callback given to [NSURLConnection sendAsynchronousRequest] which I think is being called from a background thread.
Even if the method receivedSongsJSON() is declared in your UIViewController it will be executed in background thread if it is called from one.
As #robdashnash has indicated make sure you call all the UI updating code from main thread. If you are not sure how to do that please check the documentation of Grand Central Dispatch (GCD) here.
I have a remote service that i call and it processes the request asynchronously. When the data is returned, i'll refresh my local UI.
But sometimes when the View disappears and if asynchronous call is still in the queue then the app crashes with error EXEC BAD ACCESS (i.e. the object is already released) i.e.
My app crashes when the service returns but the ViewController is disposed.
Mainly i am getting error when calling [delegate respondsToSelector:#selector (methodName:)], after the the view controller is no longer exist.
May be i need to cancel all my asynchronous calls (running or waiting in queue) in viewWillDisappear. But i am not able to cancel the running calls.
I have already tried this but in viewWillDisappear my self.navigationController.delegate is already nil.
Edit:
Method to call service:
{
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:#“%#method_name”,Base_URL]]];
[request setRequestMethod:#"POST"];
[request setTimeOutSeconds:600];
[request setPostValue:userID forKey:#“id”];
[request setDelegate:self];
[request setDidFinishSelector:#selector(requestFinished:)];
[request setDidFailSelector:#selector(requestFailed:)];
[request startAsynchronous];
}];
[operationQueue addOperation:operation];
}
And my requestFinished method (where my app crashes)
-(void)requestFinished:(ASIHTTPRequest *)request
{
// some stuff
// It's working fine when I normally run my app but fails when I rapidly changes the View Controller.
if ([delegate respondsToSelector:#selector(gotResponseData:)]) // Here my app crashes
{
[delegate gotResponseData:responseDict];
}
}
Delegate property in .h file:
#property (nonatomic,assign)id <protocolName>delegate;
Mainly this app crashes when I quickly switches between View Controller.
I'll edit my question if needed.
Kindly provide me some guidance.
In your block operation you should use a weak reference to self so that if/when self is released the operation doesn't call an invalid reference.
Indeed, you shouldn't actually need the operation as you are starting an asynchronous process inside the operation anyway.
Your delegate property in self should also be weak as your code indicates the problem is that the delegate is using an unsafe unretained approach (because of where you say the crash occurs).
Generally you would only have one request running from self so you should maintain a reference to the request and cancel it if self is destroyed.
I have a lot of classes which are sending requests and finally it all comes to SplitViewController. In the SplitUIviewclass I have to long poll and write the data in a table view. The long polling is done in the background thread, so I have declared a variable in app delegate, but when it comes to that it is nil. And the problem is whenever I try to access the NSMutablearray through the appdelegate, its coming as nil and the array is being released. My code for long polling is
- (void) longPoll {
#autoreleasePool
{
//compose the request
NSError* error = nil;
NSURLResponse* response = nil;
NSURL* requestUrl = [NSURL URLWithString:#"http://www.example.com/pollUrl"];
NSURLRequest* request = [NSURLRequest requestWithURL:requestUrl];
//send the request (will block until a response comes back)
NSData* responseData = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response error:&error];
//pass the response on to the handler (can also check for errors here, if you want)
[self performSelectorOnMainThread:#selector(dataReceived:)
withObject:responseData waitUntilDone:YES];
}
//send the next poll request
[self performSelectorInBackground:#selector(longPoll) withObject: nil];
}
- (void) startPoll {
[self performSelectorInBackground:#selector(longPoll) withObject: nil];
}
- (void) dataReceived: (NSData*) theData {
//i write data received here to app delegate table
}
If I call any other method in my SplitView class from data received, I'm losing control, also I cannot print my app delegate values in data received or the variables being released, I cannot call reload table or any other method from here.
Cant you set your properties in your ViewControllers as strong/retain like so
property (strong,retain) NSMutableArray *myData;
BTW, I learned a moment ago that it is bad practise to use your AppDelegate as a storage place for global containers. The ApplicationDelegate is a place for application delegate methods and for the initial setup of the foundation of your app; such as setting up the navigationController.
So consider storing your data in the appropriate place, perhaps core data or something else.