I am trying to update my custom label using GCD however all I am getting is the text I initialized the label with:
-(void)drawCardInfo{
__block NSString *cardString;
PPLinearLayoutLabelItem *cardDetails = [[PPLinearLayoutLabelItem alloc] initWithText:#"CREDIT CARD INFO" font:[PPFonts regular18] maxWidth:[LayoutValues getMaxWidthClipped]];
[cardDetails setPaddingTop:20];
[cardDetails setPaddingBottom:10];
[self.topContainerContent addObject:cardDetails];
PPLinearLayoutLabelItem *cardInfo = [[PPLinearLayoutLabelItem alloc] initWithText:#"Data" font:[PPFonts bold16] maxWidth:[LayoutValues getMaxWidthClipped]];
[cardInfo setPaddingTop:0];
[self.topContainerContent addObject:cardInfo];
LinearLayoutHorizontalLine *line1 = [[LinearLayoutHorizontalLine alloc] initWithMaxWidth:[LayoutValues getMaxWidthClipped]];
[self.topContainerContent addObject:line1];
dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSOperationQueue *networkQueue = [[NSOperationQueue alloc] init];
networkQueue.maxConcurrentOperationCount = 5;
NSURL *url = [NSURL URLWithString:#"https://xxxxxxxxxxxxxxxxxx"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc]
initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSString *string = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding];
if([string isEqualToString:#""]){
} else {
NSMutableDictionary *dict=[NSJSONSerialization JSONObjectWithData:[string dataUsingEncoding:NSUTF8StringEncoding] options:kNilOptions error:nil];
NSString *cardType = [dict objectForKey:#"credit_card_type"];
NSString *cardFinalFour = [dict objectForKey:#"credit_card_last_4"];
cardString = [NSString stringWithFormat:#"%# **** %#",cardType, cardFinalFour];
NSLog(#"%#",cardString);
}
dispatch_async(dispatch_get_main_queue(), ^{ [cardInfo setText:cardString]; });
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"%s: AFHTTPRequestOperation error: %#", __FUNCTION__, error);
}];
[networkQueue addOperation:operation];
});
}
I am successfully logging out the value proving that the network call works fine on a separate thread. Still my label is not being updated. How can I mitigate this?
Looks like you are trying to update the text for the label in a background thread. Updating UI elements on anywhere other than the main thread is not such a good idea since you never know when a asynchronous job will finish.
You can use the following ways to go about it:
Get the main thread and then update the text label
dispatch_async(dispatch_get_main_queue(), ^{
//Code here to which needs to update the UI in the UI thread goes here
});
Use dispatch_sync
Use NSOperations with dependancies (This will be the longest)
Related
UPDATE
I am making an api call getPacks() and successfully getting the values of two variables _numberOfPacks and _numberOfAutoRefills.
However, when I try to put it in a label it fails and gives output as (null) Packs & (null) Autorefiils
the calls are made in the following order:
getpacks()-------first (it sets value on _numberOfPacks and _numberOfAutoRefills as global variables
drawQuantity()-----set _numberOfPacks and _numberOfAutoRefills on labels.
-(void) getPacks: (IHPlacedOrder*)order{
NSOperationQueue *networkQueue = [[NSOperationQueue alloc] init];
networkQueue.maxConcurrentOperationCount = 5;
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"https://XXXXXXX.XXXXX/%#",order.ID]];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc]
initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSString *string = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding];
if([string isEqualToString:#""]){
} else {
NSMutableDictionary *dict=[NSJSONSerialization JSONObjectWithData:[string dataUsingEncoding:NSUTF8StringEncoding] options:kNilOptions error:nil];
NSLog(#"%#",dict);
_numberOfPacks = [[dict objectForKey:#"prescription_interval"] valueForKey:#"quantity_per_interval"];
_numberOfAutoRefills = [[dict objectForKey:#"prescription_interval"]valueForKey:#"num_intervals"];
NSLog(#"PACKS:%# , AUTOREFILLS:%#",_numberOfPacks,_numberOfAutoRefills);
_quantity.text = [NSString stringWithFormat: #"%#,%#",_numberOfPacks,_numberOfAutoRefills];
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"%s: AFHTTPRequestOperation error: %#", __FUNCTION__, error);
}];
[networkQueue addOperation:operation];
}
followed by:
-(void)drawQuantity: (IHPlacedOrder*)order{
PPLinearLayoutLabelItem *quantityLabel = [[PPLinearLayoutLabelItem alloc] initWithText:[NSString stringWithFormat:#"%# QUANTITY & %# REFILLS", _numberOfPacks,_numberOfAutoRefills] font:[PPFonts regular18] maxWidth:[LayoutValues getMaxWidthClipped]];
[quantityLabel setPaddingBottom:5];
[quantityLabel setPaddingTop:20];
[self.topContainerContent addObject:quantityLabel];
NSString* quantityText = [NSString stringWithFormat:#"Packs + Autorefills"];
PPLinearLayoutLabelItem *quantity = [[PPLinearLayoutLabelItem alloc] initWithText:quantityText font:[PPFonts genericParagraphFontBold] maxWidth:[LayoutValues getMaxWidthClipped]];
[quantity setPaddingBottom:20];
[self.topContainerContent addObject:quantity];
LinearLayoutHorizontalLine *line1 = [[LinearLayoutHorizontalLine alloc] initWithMaxWidth:[LayoutValues getMaxWidthClipped]];
[self.topContainerContent addObject:line1];
}
I found the solution. I was using wrong method call to set labels. The thing is my application uses PPLinearLayoutLabelItem as a custom built label. I had to call [self.quantity setLabel:#"%#",data_fromapi] to make it work. setText would just set the text within PPLinearLayoutLabelItem class and not set the Label.
Firstly, I've added some functions which need to call from different ViewControllers in one class file. For those function, I've add SVProgressHUD as preprocess and dismiss after all process is finished. SVProgressHUD is displaying correctly before process is started. But after process, SVProgressHUD is never dismissed at all. Please let me know how to solve that issue.
I've added [SVProgressHUD dismiss]; in all process is finished. But never dismissed.
common.m
- (NSDictionary *) loadBlahClass:(NSString *)paramUserId {
[SVProgressHUD showWithStatus:#"Loading Cards..."];
__block NSDictionary *jsonResponse;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.completionQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
manager.requestSerializer = [AFJSONRequestSerializer serializer];
[manager.responseSerializer.acceptableContentTypes setByAddingObject:#"application/json"];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
NSString *urlStr = [NSString stringWithFormat:#"MY_URL"];
[manager GET:urlStr parameters:nil
success:^(AFHTTPRequestOperation *operation, id responseObject)
{
NSString *jsonString = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding];
NSData *data = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
[SVProgressHUD dismiss];
dispatch_semaphore_signal(semaphore);
}
failure:
^(AFHTTPRequestOperation *operation, NSError *error) {
[SVProgressHUD dismiss];
[self showAlert:APP_NAME alertMessage:[error localizedDescription]];
dispatch_semaphore_signal(semaphore);
}];
[SVProgressHUD dismiss];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return jsonResponse;
}
testViewController.m
_allTableData = [[NSMutableArray alloc]init];
NSDictionary *jsonResponse = [commClass loadBlahClass:strUserId];
NSNumber *status = [jsonResponse valueForKey:#"Success"];
NSString *message = [jsonResponse valueForKey:#"Message"];
NSArray *dataArray = [jsonResponse valueForKey:#"lstCC"];
if([status intValue] == 1) {
for(int i=0; i<[dataArray count]; i++) {
//working .......
}
[_ccTable reloadData];
[SVProgressHUD dismiss];
} else {
[commClass showAlert:APP_NAME alertMessage:message];
[SVProgressHUD dismiss];
}
I've found answer. Something like that.
[SVProgressHUD show];
__block BOOL result;
dispatch_async(queue, ^{
result = [self autanticate];
NSLog(#"autantication result = %d", result);
result = [self getCSRFToken];
NSLog(#"Login success result = %d", result);
dispatch_async(dispatch_get_main_queue(), ^{
[SVProgressHUD dismiss];
})
});
I have an app which downloads a set of photos from a server. I am using an Asynchronous request because I don't want the UI to be blocked. However, I am finding that the request is very slow and takes ages to load.
I know you can set the queue type to [NSOperationQueue mainQueue] but that just puts the Asynchronous request back on the main thread which defeats the whole point of making the request Asynchronously in the first place.
Is there anyway to speed up the request or to tell iOS: "Run this request in the background, but do it ASAP, don't leave it till the end of the queue"???
Here is my code:
// Set up the photo request.
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:PHOTO_URL, pass_venue_ID, PHOTO_CLIENT_ID, PHOTO_CLIENT_SECRET]];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// Begin the asynchromous image loading.
[NSURLConnection sendAsynchronousRequest:urlRequest queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (error == nil) {
// Convert the response data to JSON.
NSError *my_error = nil;
NSDictionary *feed = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:&my_error];
// Check to see if any images exist
// for this particular place.
int images_check = [[NSString stringWithFormat:#"%#", [[[feed objectForKey:#"response"] valueForKey:#"photos"] valueForKey:#"count"]] intValue];
if (images_check > 0) {
// Download all the image link properties.
images_prefix = [[[[feed objectForKey:#"response"] valueForKey:#"photos"] valueForKey:#"items"] valueForKey:#"prefix"];
images_suffix = [[[[feed objectForKey:#"response"] valueForKey:#"photos"] valueForKey:#"items"] valueForKey:#"suffix"];
images_width = [[[[feed objectForKey:#"response"] valueForKey:#"photos"] valueForKey:#"items"] valueForKey:#"width"];
images_height = [[[[feed objectForKey:#"response"] valueForKey:#"photos"] valueForKey:#"items"] valueForKey:#"height"];
// Set the image number label.
number_label.text = [NSString stringWithFormat:#"1/%lu", (unsigned long)[images_prefix count]];
// Download up to 5 images.
images_downloaded = [[NSMutableArray alloc] init];
// Set the download limit.
loop_max = 0;
if ([images_prefix count] > 5) {
loop_max = 5;
}
else {
loop_max = [images_prefix count];
}
for (NSUInteger loop = 0; loop < loop_max; loop++) {
// Create the image URL.
NSString *image_URL = [NSString stringWithFormat:#"%#%#x%#%#", images_prefix[loop], images_width[loop], images_height[loop], images_suffix[loop]];
// Download the image file.
NSData *image_data = [NSData dataWithContentsOfURL:[NSURL URLWithString:image_URL]];
// Store the image data in the array.
[images_downloaded addObject:image_data];
}
// Load the first image.
[self load_image:image_num];
}
else if (images_check <= 0) {
// error...
}
}
else {
// error
}
}];
Thanks for your time, Dan.
i think your problem isnt the request running slow, its that you are updating UI elements not on the main thread, surround any UI updates (like setting the text on labels) with
dispatch_sync(dispatch_get_main_queue(), ^{
<#code#>
});
As Fonix said its not iOS that responding slow but dataWithContentsOfURL doesn't work in background thread. Apple's recommendation is that you should use NSURLConnection asynchronously with delegates
- didReceiveResponse
- didReceiveData
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:theURL cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:_mAuthenticationTimeoutInterval];
In these methods you can make use of chunks of data as well.
If you actually want these multiple downloads to be faster you should use parallel downloading using NSOperationQueue. You can refer enter link description here
I think a good solution could be using AFNetworking when combined with NSOperation, check this code I wrote to do more than one operation asynchronously
NSMutableArray *operations = [[NSMutableArray alloc] init];
for(NSObject *obj in caches) {
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setURL:url];
//...set up your mutable request options here
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.responseSerializer = [AFJSONResponseSerializer serializer];
operation.responseSerializer.acceptableContentTypes = [NSSet setWithObject:#"application/json"];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSInteger statusCode = operation.response.statusCode;
if(statusCode==200) {
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"API Call error:%#", error.localizedDescription);
}];
[[requestManager operationQueue] addOperation:operation];
[operations addObject:operation];
if([operations count] >= MAX_API_CALL) break;
}
[AFHTTPRequestOperation batchOfRequestOperations:operations progressBlock:^(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations) {
} completionBlock:^(NSArray *operations) {
NSError *error;
for (AFHTTPRequestOperation *op in operations) {
if (op.isCancelled){
}
if (op.responseObject){
// process your responce here
}
if (op.error){
error = op.error;
}
}
}];
I am trying to use MBProgressHUD with NSURLConnection, but I'm not sure how to make the animation spin in MBProgressHUDModeDeterminate.
Basically I have this code in viewDidLoad:
- (void)viewDidLoad
{
[super viewDidLoad];
[self fetchFromServer];
self.HUD = [ [MBProgressHUD alloc] initWithView:self.view];
self.HUD.labelText = #"Loading Schedule...";
[self.view addSubview:self.HUD];
self.HUD.dimBackground = YES;
[self.HUD show:YES];
}
When this view is loaded, I call a method to fetch from the database. I want MBProgressHUD to load. Then I want it to end once the data has been fetched.
- (void)fetchFromServer
{
NSURL *url = [[NSURL alloc] initWithString:#"url.com"];
[NSURLConnection sendAsynchronousRequest:[ [NSURLRequest alloc] initWithURL:url] queue:[ [NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
NSString* str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSArray *jsonObj = [NSJSONSerialization JSONObjectWithData:[str dataUsingEncoding:NSUTF8StringEncoding]
options:0 error:&error];
scheduleJSONArray = jsonObj;
// Progress in HUD should end here after scheduleJSONArray is set
[self.HUD hide:YES];
} ];
}
I don't know what to use to update the progress of HUD. Can anyone assist?
Thanks
You better use AFNetworking:
MBProgressHUD *HUD = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
// Set up HUD
__weak ViewController *weakSelf = self;
NSURLRequest *request = [[AFHTTPRequestSerializer serializer] requestWithMethod:#"GET" URLString:#"url.com" parameters:nil error:nil];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
__weak AFHTTPRequestOperation *weakOperation = operation;
[operation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
[weakSelf updateHUDForOpetation:weakOperation
totalBytes:totalBytesExpectedToRead
readBytes:totalBytesRead
index:[videoNames indexOfObject:videoName]
total:videoNames.count];
}];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
[MBProgressHUD hideHUDForView:self.view animated:YES];
}];
Then update HUD manually. Like this:
- (void)updateHUDTotalBytes:(long long)totalBytes
readBytes:(long long)readBytes {
MBProgressHUD *HUD = [MBProgressHUD HUDForView:self.view];
HUD.mode = MBProgressHUDModeDeterminate;
HUD.labelText = [NSString stringWithFormat:#"%lld/%lld", readBytes / 1024, totalBytes / 1024];
HUD.progress = (double)readBytes / (double)totalBytes;
}
EDIT: The highlighted row in the screenshot is what I have a problem with, why is NSURLConnection running on [NSThread main] when I'm not calling it, AFNetworking is.
I'm using AFNetworking for my project, but when running Time Profiler in Instruments I'm seeing a lot of activity on the main thread for NSURLConnection, I have a feeling this is not what I want.
My method is
- (void)parseArticles {
NSMutableArray *itemsToParse = [[FMDBDataAccess sharedDatabase] getItemsToParse];
NSMutableArray *operations = [[NSMutableArray alloc] init];
for (Post *p in itemsToParse) {
NSMutableString *strURL = [NSMutableString new];
[strURL appendString:#"http://xxxxxxxxxxxxxxxxxxxxxx.php?url="];
[strURL appendString:[p href]];
NSURL *url = [NSURL URLWithString:strURL];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[[ParserClient sharedInstance] registerHTTPOperationClass:[AFHTTPRequestOperation class]];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
dispatch_async(loginParseQueue, ^{
Parser *parse = [[Parser alloc] init];
[parse parseLink:responseObject rowID:[p rowID]];
});
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"%#",error);
}];
[operations addObject:operation];
}
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
[operationQueue setMaxConcurrentOperationCount:3];
[operationQueue addOperations:operations waitUntilFinished:NO];
}
Why would AFNetworking be using the main thread? and how do I fix it.
AFNetworking is running on a child thread not in main thread, but every thread has a main method, which is on the image you post. This is not the main thread.Now tell me What do you want to fix?
It's because AFNetworking uses "successCallbackQueue" to route the completion block :
AFHTTPRequestOperation.m :
self.completionBlock = ^{
if (self.error) {
if (failure) {
dispatch_async(self.failureCallbackQueue ?: dispatch_get_main_queue(), ^{
failure(self, self.error);
});
}
} else {
if (success) {
dispatch_async(self.successCallbackQueue ?: dispatch_get_main_queue(), ^{
success(self, self.responseData);
});
}
}
};
You can simply assign a different thread to success and failure completion blocks :
dispatch_queue_t backgroundQueue = dispatch_queue_create("com.name.bgqueue", NULL);
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.successCallbackQueue = backgroundQueue;
operation.failureCallbackQueue = backgroundQueue;
EDIT:
Here is some code to run operations in a background thread. Use of any function called from the UI thread will run on on the UI thread. You can use a technique similar to the one specified below to run your operation on a background thread, and then dispatch the result back to the UI thread for later use.
Here is the technique I used, you may replace my sendSynchronousRequest call with your AFHTTPRequestOperation :
Specify a special type (a block) so you can pass blocks of code around.
typedef void (^NetworkingBlock)(NSString* stringOut);
Then, you need to dispatch to a background thread, so as not to freeze your UI thread.
Here's a function to call stuff in a background thread, and then wait for a response, and then call a block when done without using the UI thread to do it:
- (void) sendString:(NSString*)stringIn url:(NSString*)url method:(NSString*)method completion:(NetworkingBlock)completion {
//build up a request.
NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]];
NSData *postData = [stringIn dataUsingEncoding:NSUTF8StringEncoding];
[request setHTTPMethod:method];
[request setValue:[NSString stringWithFormat:#"%d", postData.length] forHTTPHeaderField:#"Content-Length"];
[request setValue:#"application/json" forHTTPHeaderField:#"Content-Type"]; //or whatever
[request setHTTPBody:postData];
//dispatch a block to a background thread using GCD (grand central dispatch)
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURLResponse *response;
NSError* error;
//request is sent synchronously here, but doesn't block UI thread because it is dispatched to another thread.
NSData* responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
//call complete, so now handle the completion block.
if (completion) {
//dispatch back to the UI thread again
dispatch_async(dispatch_get_main_queue(), ^{
if (responseData == nil) {
//no response data, so pass nil to the stringOut so you know there was an error.
completion(nil);
} else {
//response received, get the content.
NSString *content = [[NSString alloc] initWithBytes:[responseData bytes] length:responseData.length encoding:NSUTF8StringEncoding];
NSLog(#"String received: %#", content);
//call your completion handler with the result of your call.
completion(content);
}
});
}
});
}
Use it like this:
- (void) myFunction {
[self sendString:#"some text in the body" url:#"http://website.com" method:#"POST" completion:^(NSString *stringOut) {
//stringOut is the text i got back
}];
}