I am building a chat application which repeatedly calls a web service using AFNetworking. The chat screen constantly polls this service for new chat messages. Everything related to the service works fine, but the UI keeps freezing and none of the buttons are working.
Here is the code:
- (void)GetAllIncomingMessages
{
NSURL *url = [NSURL URLWithString:weatherUrl];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFJSONRequestOperation *operation =
[AFJSONRequestOperation JSONRequestOperationWithRequest: request
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
[self ParseJson:(NSDictionary *)JSON];
[self GetAllIncomingMessages];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON)
{
[self GetAllIncomingMessages];
UIAlertView *av = [[UIAlertView alloc] initWithTitle:#"Error "
message:[NSString stringWithFormat:#"%#",error]
delegate:nil
cancelButtonTitle:#"OK" otherButtonTitles:nil];
[av show];
}];
[operation setAuthenticationChallengeBlock:
^( NSURLConnection* connection, NSURLAuthenticationChallenge* challenge )
{
if( [[challenge protectionSpace] authenticationMethod] == NSURLAuthenticationMethodHTTPBasic )
{
if( [challenge previousFailureCount] > 0 )
{
// Avoid too many failed authentication attempts which could lock out the user
[[challenge sender] cancelAuthenticationChallenge:challenge];
}
else
{
[[challenge sender] useCredential:[NSURLCredential credentialWithUser:#"username" password:#"password" persistence:NSURLCredentialPersistenceForSession] forAuthenticationChallenge:challenge];
}
}
else
{
// Authenticate in other ways than NTLM if desired or cancel the auth like this:
[[challenge sender] cancelAuthenticationChallenge:challenge];
}
}];
[operation start];
}
I reload the table view each time, but the UI still freezes. I tried using a background thread and that didn't work either.
I know this is an old question but I just bumped into it. Just FYI AFNetworking use a dispatched async queue to perform the connection operation and give back to you a JSON format of the NSData retrieved (as you probably already know) in the main queue. So AFNetworking is definitely not the problem.
My suggestion is try to perform ParseJson: and GetAllIncomingMessages: in a separated thread or dispatch an async queue yourself and you'll see your UI no longer freezing.
Something like:
static dispatch_queue_t your_app_queue() {
static dispatch_once_t onceToken;
static dispatch_queue_t _myQueue;
dispatch_once(&onceToken, ^{
_myQueue = dispatch_queue_create("com.myapp.queue", DISPATCH_QUEUE_SERIAL);
});
return _myQueue;
}
AFJSONRequestOperation *operation =
[AFJSONRequestOperation JSONRequestOperationWithRequest: request
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
__block myClass = self;
dispatch_async(your_app_queue(), ^{
[myClass ParseJson:(NSDictionary *)JSON];
[myClass GetAllIncomingMessages];
});
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON){
__block myClass = self;
dispatch_async(your_app_queue(), ^{
[myClass GetAllIncomingMessages];
dispatch_async(dispatch_get_main_queue(), ^{
UIAlertView *av = [[UIAlertView alloc] initWithTitle:#"Error "
message:[NSString stringWithFormat:#"%#",error]
delegate:nil
cancelButtonTitle:#"OK" otherButtonTitles:nil];
[av show];
});
});
}];
Or:
AFJSONRequestOperation *operation =
[AFJSONRequestOperation JSONRequestOperationWithRequest: nil
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
[self performSelectorInBackground:#selector(ParseJson:) withObject:JSON];
[self performSelectorInBackground:#selector(GetAllIncomingMessages) withObject:nil];
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON){
[self performSelectorInBackground:#selector(GetAllIncomingMessages) withObject:nil];
UIAlertView *av = [[UIAlertView alloc] initWithTitle:#"Error "
message:[NSString stringWithFormat:#"%#",error]
delegate:nil
cancelButtonTitle:#"OK" otherButtonTitles:nil];
[av show];
}];
And should be fine. Hope this help!
Related
I am communicating with server in my ios app. I have following method in which I'm opening an alertview. I want to show a loading view while app is getting response from the server.
- (void) showDetailedQuestion:(id)sender
{
//loading view
self.loading_alert = [[UIAlertView alloc] initWithTitle:#"Loading\nPlease Wait..." message:nil delegate:self cancelButtonTitle:nil otherButtonTitles: nil];
[self.loading_alert show];
UIActivityIndicatorView *indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
// Adjust the indicator so it is up a few pixels from the bottom of the alert
indicator.center = CGPointMake(loading_alert.bounds.size.width / 2, loading_alert.bounds.size.height - 50);
[indicator startAnimating];
[self.loading_alert addSubview:indicator];
UIButton *btn = (UIButton*)sender;
int indx = btn.tag;
NSLog(#"tag:%d",indx);
answerAnQuestion *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"Answer"];
vc.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal ;
vc.que_id = [self.que_id valueForKey:[NSString stringWithFormat:#"%d",indx]];
vc.qid_list = self.que_id;
vc.crnt = indx;
[self presentViewController:vc animated:YES completion:nil];
[self.loading_alert dismissWithClickedButtonIndex:0 animated:YES];
}
and in another answerAnQuestion.m
- (void)viewDidLoad
{
NSString *address = [NSString stringWithFormat:#"%#%#%#%#%#%#%#", path,#"questions/",que_id,#"?token=",token,#"&user_id=",usrId];
NSURL *URL = [NSURL URLWithString:address];
NSLog(#"%#",address);
[NSURLRequest setAllowsAnyHTTPSCertificate:YES forHost:[URL host]];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL cachePolicy:NSURLCacheStorageAllowedInMemoryOnly
timeoutInterval:60.0];
[request setHTTPMethod:#"GET"];
responseData = [[NSMutableData alloc] init];
NSURLResponse *response = nil;
NSError *error = nil;
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
if (data)
{
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*) response;
//If you need the response, you can use it here
int statuscode = [httpResponse statusCode];
NSString *responseMsg = [NSHTTPURLResponse localizedStringForStatusCode:statuscode];
NSLog(#" Status code: %d",statuscode );
NSLog(#" Status msg: %#",responseMsg );
}
else
{
// Handle error by looking at response and/or error values
NSLog(#"%#",error);
}
}
My problem is alertview is only shown up for a moment when view is changing. It suppose to open when I click the button. What could be the reason? how to solve this?
EDIT 1:
If i make asynchronous request to server then i'm not able to set those data in my tableview. I can set those data in my tableview Only if send synchronous request,but it blocks the app. Why this is happening ?
Any help will be appreciated.
Thank you.
You are sending SynchronousRequest on main thread, so it is blocking your UI thread. Read multithreading you will get various tutorial on this. I can suggest you to go for GCD or NSOperation and NSOperationQueue. Google for any of the above and you will get various sample for the same.
Or you can send asynchronous request as follows...
[NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error){
//Write code you want to call when data is received,
//Like dismissing loading view and populating UI.
}];
Updated:
//Display alert view, before sending your request..
[alertview show];
//send first request
[NSURLConnection sendAsynchronousRequest:request1 queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error){
//Write code you want to call when data is received,
//send second request
[NSURLConnection sendAsynchronousRequest:request2 queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error){
//Write code you want to call when data is received,
//send third request
[NSURLConnection sendAsynchronousRequest:request3 queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error){
//Write code you want to call when data is received,
//dismiss alert view on main thread
dispatch_async(getmainqueue, ^(void) {
// dismiss alert view...
});
}];
}];
}];
I have worked with Nuzhat Zari code, and I thank him for it, but also have experienced some issues with some core data operations between nested "sendAsynchronousRequest" (getting some weird thread and memory errors) so, my solution was unnest the calls to "sendAsynchronousRequest" and use some main thread variable validation.
#interface myMainThreadClass
#property (nonatomic,assign) NSInteger *currentAsyncTasks;
#end
#implementation
// Use init or viewDidLoad to make "currentAsyncTasks=0"!!
-(void)method
{
[self showLoadingAlert]; //or some ui update function
currentAsyncTasks++;
[NSURLConnection sendAsynchronousRequest:urlRequest queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error){
//do something with data
dispatch_async(dispatch_get_main_queue(), ^(void) {
[self dismissAlertInFinalTask]
});
}];
currentAsyncTasks++;
[NSURLConnection sendAsynchronousRequest:urlRequest2 queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error){
//do something with data
dispatch_async(dispatch_get_main_queue(), ^(void) {
[self dismissAlertInFinalTask]
});
}];
currentAsyncTasks++;
[NSURLConnection sendAsynchronousRequest:urlRequest3 queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error){
//do something with data
dispatch_async(dispatch_get_main_queue(), ^(void) {
[self dismissAlertInFinalTask]
});
}];
}
-(void)dismissAlertInFinalTask
{
currentNetworkTasks--;
if (currentNetworkTasks == 0)
{
[self dismissLoadingAlert];//or some ui update function;
}
}
#end
I also want to know if someone has managed to do multiple request using NSURLConnection delegate NSURLConnectionDataDelegate and ui calls.
I am having trouble determining the best way to manage updates to my apps SQLite database (using Core Data)
When my app launches, it hits a server to determine which tables need updating. I then do a service call for each of those tables. Once I get the JSON back for each, it creates/updates the corresponding objects in my SQLite DB.
What I am doing works, as it performs each request and updates each table that needs to be-- but I don't think I am doing this correctly.
Doing this still locks my UI Thread and I need to be able to run this code asynchronously in the background every 10 minutes or so.
AFJSONRequestOperation* operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
NSMutableArray *operations = [NSMutableArray new];
//for each of the tables that need updating, create a AFJSONRequestOperation
for(NSString *str in [JSON valueForKey:#"Views"])
{
NSString* path = [NSString stringWithFormat:#"cache/%#/?deviceUID=%#&token=%#", str, #"00000-00000-0000-00001", [_globals getToken]];
NSURLRequest* request = [client requestWithMethod:#"GET" path:path parameters:nil];
AFJSONRequestOperation* operation2 = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON)
{
[self updateTable:str withJSON:JSON];
}
failure:nil];
[operations addObject:operation2];
}
//AFHTTPClient
[client enqueueBatchOfHTTPRequestOperations:operations progressBlock:nil completionBlock:^(NSArray *operations) {
//this gets called way before the objects are done updating to the DB
NSLog(#"DONE ALL REQUESTS");
[_HUD hide:YES]; // getting called after getting all of the JSON not after all tables are updated
}];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
[_HUD hide:YES];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Failed" message:[error localizedDescription] delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles: nil];
[alert show];
}];
[operation start];
Heres my updateTable function with only 1 condition
- (void)updateTable:(NSString *)tblName withJSON:(id)JSON
{
for(NSDictionary *record in [JSON valueForKey:#"Records"])
{
NSString *viewName = [[record valueForKey:#"ViewName"] lowercaseString];
//Worker
if([viewName isEqualToString:[[NSString stringWithFormat:#"Worker_vw_iSales"] lowercaseString]])
{
if([Worker doesWorkerExist:[record valueForKey:#"JSONData"]])
{
NSLog(#"deleting old worker");
[ad.managedObjectContext deleteObject:[Worker doesWorkerExist:[record valueForKey:#"JSONData"]]];
}
NSEntityDescription *desc = [NSEntityDescription entityForName:NSStringFromClass([Worker class]) inManagedObjectContext:ad.managedObjectContext];
Worker *worker = [[Worker alloc] initWithEntity:desc insertIntoManagedObjectContext:ad.managedObjectContext];
[worker initWithJSONSting:[record valueForKey:#"JSONData"]];
NSLog(#"Creating Worker: %#", worker.firstName);
}
}
}
I hope this is not all too confusing-- if so I can try to explain more.
I may be doing this completely wrong, and if I am just let me know. I have tried a few other things, including using an NSOperationQueue instead of AFHHTTPs enqueueBatchOfHTTPRequestOperations:requests but I cannot get the behavior I am looking for.
Thanks!
What you are looking for now is setSuccessCallbackQueue: on AFJSONRequestOperation. AFNetworking set all their success blocks to run on the main queue unless otherwise specified.
What I've done is
#implementation myClass {
dispatch_queue_t backgroundQueue;
}
- (id)init
{
if (self = [super init]){
backgroundQueue = dispatch_queue_create("com.proj.myClass", 0);
}
return self;
}
- (void)doSomeStuff
{
NSMutableURLRequest *request = [[myAFAPIClient sharedClient] requestWithMethod:#"GET" path:path parameters:params];
AFJSONRequestOperation *myOperation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
//Success Block
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
}];
[myOperation setSuccessCallbackQueue:backgroundQueue];
[[myAFAPIClient sharedClient].operationQueue addOperation:myOperation];
}
So the different would be you are enqueue'ing operations, where I'm just adding them straight to the client.
Also, where I have //Success Block I do all sorts of things such as dispatching other methods onto backgroundQueue, which those methods make more JSON requests and I don't have any issues.
Can't receive JSON synchronously inside a block using AFNetworking. I checked this solution. It
always nil at the end of method.
Here is my method:
- (BOOL)whois:(NSString *)domain withZone: (NSString*) zone
{
__block NSString *resultCode;
NSURL *url = [[NSURL alloc] initWithString:#"myurl"];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
resultCode = [JSON valueForKeyPath:[NSString stringWithFormat:#"%#.%#", domain,zone]]; //checked with NSLog, works well
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Request Failed with Error: %#, %#", error, error.userInfo);
}];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation: operation];
[operation waitUntilFinished];
if(resultCode == #"available") //nil here
{
return YES;
}
return NO;
}
Instead of creating aNSOperationQueue, start your AFJSONRequestOperation with [operation start] and then call [operation waitUntilFinished] and it will block the main thread until it's finished. Then your resultCode should not be nil.
As #mattt said in the post you linked, it is strongly discouraged to freeze the thread like this. Consider figuring out another way to do this, such as calling a new method you hope to continue from your success block, and a different failure method from your failure block.
Your method can't function with its current design.
- (BOOL)whois:(NSString *)domain withZone: (NSString*) zone
{
__block NSString *resultCode;
NSURL *url = [[NSURL alloc] initWithString:#"myurl"];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
// *** Runs 1st
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
// *** runs 3rd
resultCode = [JSON valueForKeyPath:[NSString stringWithFormat:#"%#.%#", domain,zone]]; //checked with NSLog, works well
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Request Failed with Error: %#, %#", error, error.userInfo);
}];
// *** Runs 2nd
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation: operation];
[operation waitUntilFinished];
if(resultCode == #"available") //nil here
{
return YES;
}
return NO;
}
Because the material in the block runs third, and asynchronously, you won't be able to return that value to the greater method in the manner it is currently designed. Perhaps use something like this:
- (void)whois:(NSString *)domain withZone: (NSString*) zone
{
NSURL *url = [[NSURL alloc] initWithString:#"myurl"];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
__weak id weakSelf = self;
// Runs 1st
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
NSString *resultCode = [JSON valueForKeyPath:[NSString stringWithFormat:#"%#.%#", domain,zone]]; //checked with NSLog, works well
[weakSelf receivedResultCode:resultCode];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Request Failed with Error: %#, %#", error, error.userInfo);
}];
}
- (void) receivedResultCode:(NSString *)resultCode {
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation: operation];
[operation waitUntilFinished];
if(resultCode == #"available") //nil here
{
// do #YES stuff
}
else {
// do #NO stuff
}
}
Obviously you'll have to change the design of whoever's calling it because it won't return a value in the way you specified. Perhaps there is a better solution, but I think this is the type of design required for it to work.
I'm trying to get the AFJSONRequestOperation to work with http://www.crowdkind.org/mobile/offers.
I get through the synchronous part and it returns data and puts it in the table just fine. When I go to convert it to AFJSONRequestOperation - I don't get a response. The operation is sent as the NSLog tells me that. But I never get "Operation succeeded or failed". Any idea what might be happening? Here's my searchBarButtonClicked method...
URLtoSearch is set to... http://www.crowdkind.org/mobile/offers. (feel free to click to as it's valid JSON).
- (void)searchBarSearchButtonClicked
{
searchResults = [NSMutableArray arrayWithCapacity:10];
[queue cancelAllOperations];
isLoading = YES;
[self.tableView reloadData];
NSURL *url = [self urlWithSearchText:URLtoSearch];
NSLog(#"URL: %#", URLtoSearch);
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFJSONRequestOperation *operation = [AFJSONRequestOperation
JSONRequestOperationWithRequest:request
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
NSLog(#"Operation succeeded");
[self parseDictionary:JSON];
[searchResults sortUsingSelector:#selector(compareName:)];
isLoading = NO;
[self.tableView reloadData];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Operation failed");
[self showNetworkError];
isLoading = NO;
[self.tableView reloadData];
}];
operation.acceptableContentTypes = [NSSet setWithObjects:#"application/json", #"text/json", #"text/javascript", nil];
[queue addOperation:operation];
NSLog(#"Operation added");
}
I get no response. Check the link, it's valid json. Any thoughts greatly appreciated!
I am using AFNetworking and creating a post request for which I require json feedback. The code below works however I have two main questions; where do I release the ActivityIndicator Manager? The second question is this code correct, being new I get confused with blocks so I really want to know if I am doing it right thing for optimum performance, even though it works.
NSURL *url = [NSURL URLWithString:#"mysite/user/signup"];
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:url];
AFNetworkActivityIndicatorManager * newactivity = [[AFNetworkActivityIndicatorManager alloc] init];
newactivity.enabled = YES;
NSDictionary *params = [NSDictionary dictionaryWithObjectsAndKeys:
usernamestring, #"login[username]",
emailstring, #"login[email]",
nil];
NSMutableURLRequest *request = [httpClient requestWithMethod:#"POST" path:#"mysite/user/signup"parameters:params];
[httpClient release];
AFJSONRequestOperation *operation = [AFJSONRequestOperation operationWithRequest:request success:^(id json) {
NSString *status = [json valueForKey:#"status"];
if ([status isEqualToString:#"success"]) {
[username resignFirstResponder];
[email resignFirstResponder];
[self.navigationController dismissModalViewControllerAnimated:NO];
}
else {
UIAlertView *alert =[[UIAlertView alloc] initWithTitle:#"Login Unsuccessful"
message:#"Please try again"
delegate:NULL
cancelButtonTitle:#"OK"
otherButtonTitles:NULL];
[alert show];
[alert release];
}
}
failure:^(NSHTTPURLResponse *response, NSError *error) {
NSLog(#"%#", error);
UIAlertView *alert =[[UIAlertView alloc] initWithTitle:#"Login Unsuccessful"
message:#"There was a problem connecting to the network!"
delegate:NULL
cancelButtonTitle:#"OK"
otherButtonTitles:NULL];
[alert show];
[alert release];
}];
NSOperationQueue *queue = [[[NSOperationQueue alloc] init] autorelease];
[queue addOperation:operation];
NSLog(#"check");
}
Thank you very much for your help in advance :)
I know this question is a bit old, but I still wanted to contribute.
As steveOhh said, you should use [[AFNetworkActivityIndicatorManager sharedManager] setEnabled:YES] to turn on the activity network indicator. It is a singleton, and hence it doesn't require you to manually alloc-init and release. As to the other question, I noticed you are missing some parameters in your block calls, also, you can do this, which is much cleaner code:
NSURL *url = [NSURL URLWithString:#"mysite/user/signup"];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:[NSURLRequest requestWithURL:url] success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
// your success code here
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
// your failure code here
}];
[operation start]; // start your operation directly, unless you really need to use a queue
Why not use this instead?
[[AFNetworkActivityIndicatorManager sharedManager] setEnabled:YES];
Hence there's no need to alloc and init
Can't say much on the other codes, just started out learning objective-C and AFNetworking.. :)
Regards,
Steve0hh