NSURLConnection taking a long time - ios

This code loads a table view:
- (void)viewDidLoad
{
[super viewDidLoad];
//test data
NSURL *url =[[NSURL alloc] initWithString:urlString];
// NSLog(#"String to request: %#",url);
[ NSURLConnection
sendAsynchronousRequest:[[NSURLRequest alloc]initWithURL:url]
queue:[[NSOperationQueue alloc]init]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if([data length] >0 && connectionError ==nil){
NSArray *arrTitle=[[NSArray alloc]init];
NSString *str=[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
arrTitle= [Helper doSplitChar:[Helper splitChar20] :str];
self.tableView.delegate = self;
self.tableView.dataSource = self;
[self fecthDataToItem:arrTitle];
[self.tableView reloadData];
NSLog(#"Load data success");
}else if (connectionError!=nil){
NSLog(#"Error: %#",connectionError);
}
}];
// arrTitle = [NSArray arrayWithObjects:#"ee",#"bb",#"dd", nil];
}
And it takes 10 - 15s to load. How can I make this faster?
.
Thanks Rob and rmaddy, problem is solve.

As rmaddy points out, you must do UI updates on the main queue. Failure to do so will, amongst other things, account for some of the problems you're experiencing.
The queue parameter of sendAsynchronousRequest indicates the queue upon which you want the completion block to run. So, you can simply specify [NSOperationQueue mainQueue]:
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if([data length] > 0 && connectionError == nil) {
NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSArray *arrTitle = [Helper doSplitChar:[Helper splitChar20] :str];
self.tableView.delegate = self;
self.tableView.dataSource = self;
[self fecthDataToItem:arrTitle];
[self.tableView reloadData];
} else if (connectionError!=nil) {
NSLog(#"Error: %#",connectionError);
}
}];
Or, if you where doing something slow or computationally expensive/slow within that block, go ahead and use your own background queue, but then dispatch the UI updates back to the main queue, e.g.:
[NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
// do something computationally expensive here
// when ready to update the UI, dispatch that back to the main queue
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// update your UI here
}];
}];
Either way, you should always do UI updates (and probably model updates, too, to keep that synchronized) on the main queue.

Related

Using blocks to download image, freezing UI

In my app I am downloading image using blocks but it is freezing my UI. I have one network class which contains method to download image,
-(void)downloadImageWithCompletionHandler:^(NSData *aData, NSError *error)aBlock;
I am calling above method in my view controller to download image. So once the image is downloaded I am using NSData to show in image view. The network class method uses NSURLConnection methods to download the image.
[[NSURLConnection alloc] initWithRequest:theURLRequest delegate:self];
Once the data download is complete I am calling completion handler block of the view controller.
But I am not sure why my UI is freezing? Can anyone help me find where I am doing wrong?
Thanks in advance!
- (void) setThumbnailUrlString:(NSString *)urlString
{
NSString *url= [NSString stringWithFormat:#"%#",urlString];
//Set up Request:
NSMutableURLRequest *request = [[NSMutableURLRequest alloc]init];
[request setURL:[NSURL URLWithString:url]];
NSOperationQueue *queue=[[NSOperationQueue alloc] init];
if ( queue == nil ){
queue = [[NSOperationQueue alloc] init];
}
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse * resp, NSData *data, NSError *error)
{
dispatch_async(dispatch_get_main_queue(),^
{
if ( error == nil && data )
{
UIImage *urlImage = [[UIImage alloc] initWithData:data];
_headImageView.image=urlImage;
_backgroundImageView.image=urlImage;
}
});
}];
}
You need to download the image in background thread to avoid freezing the UI thread.There is a simple demo to achieve this.
- (void)downloadImageWithCompletionHandler:(void(^)(NSData *aData, NSError *error))aBlock {
NSURLRequest *theURLRequest = nil; // assign your request here.
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
[NSURLConnection sendAsynchronousRequest:theURLRequest queue:mainQueue completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
// UIThread.
aBlock(data,connectionError);
}];
}
how to call this method.
[self downloadImageWithCompletionHandler:^(NSData *aData, NSError *error) {
// get UIImage.
UIImage *image = [UIImage imageWithData:aData];
}];
I figured out the problem. Problem was not in the block or using NSUrlConnection method, it is working properly. Problem was, I was saving data in file once I download it. This operation was happening on main thread which was blocking the UI.

Braintree iOS SDK saying no known class method

I m getting this error. Anyone?
-(void)clientToken{
NSURL *clientTokenURL = [NSURL URLWithString:#"http://tectutiveclients.com/projects/carboss/api/getToken"];
NSMutableURLRequest *clientTokenRequest = [NSMutableURLRequest requestWithURL:clientTokenURL];
[clientTokenRequest setValue:#"text/plain" forHTTPHeaderField:#"Accept"];
[NSURLConnection
sendAsynchronousRequest:clientTokenRequest
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
// TODO: Handle errors in [(NSHTTPURLResponse *)response statusCode] and connectionError
clientToken = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
// Initialize `Braintree` once per checkout session
[Braintree setupWithClientToken:clientToken
completion:^(Braintree *braintree, NSError *error) {
UIViewController *dropin = [braintree dropInViewControllerWithDelegate:self];
[self presentViewController:[[UINavigationController alloc] initWithRootViewController:dropin]
animated:YES
completion:nil];
// No known class method for selector setupwithClientToken Error
}];
UIViewController *dropin = [self.braintree dropInViewControllerWithDelegate:self];
[self presentViewController:[[UINavigationController alloc] initWithRootViewController:dropin]
animated:YES
completion:nil];
}];
}
I work at Braintree. If you have any more problems, please get in touch with our support team.
This is an inaccuracy in the docs. Instead, please use braintreeWithClientToken:
Example:
NSString *clientToken = [[NSString alloc] initWithData:data
encoding:NSUTF8StringEncoding];
// Initialize `Braintree` once per checkout session
self.braintree = [Braintree braintreeWithClientToken:clientToken];
Thanks for pointing this out! The docs will be updated shortly.

NSURLConnection sendAsynchronous multiple requests not executed

I was sending 10 requests of url (in a for in loop) to web service
and expected to get 10 JSON format data as return separately
here's my code:
NSArray *reqArray = [10 requests of url inside];
NSMutableArray * saveArray = [prepare to store 10 JSON data in here];
NSInteger counter = [reqArray count];
for (NSURLRequest *request in reqArray) {
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
NSDictionary *result1 = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
NSDictionary *result2 = [result1 objectForKey:#"keyForValueIWant"];
[saveArray addObject:result2];
counter -- ;
if (counter == 0) {
NSLog(#"all done");
}
}];
}
the completionHandler never gets executed and I always get nothing in return.
is there anything I misunderstand bout the NSURLConnetion or I did it wrong?
any advice would be appreciated!
EDIT
I found code gets executed after all the other code in viewDidLoad (where I put it) are done
even I tried to wrap it in dispatch_async(dispatch_get_main_queue(), ^{},
what could make it work immediately whenever I call it? sendSynchronous request?
I guess that you are starting those connections off the main loop, try with:
NSArray *reqArray = [10 requests of url inside];
NSMutableArray * saveArray = [prepare to store 10 JSON data in here];
NSInteger counter = [reqArray count];
for (NSURLRequest *request in reqArray) {
dispatch_async(dispatch_get_main_queue(), ^{
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
NSDictionary *result1 = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
NSDictionary *result2 = [result1 objectForKey:#"keyForValueIWant"];
[saveArray addObject:result2];
counter -- ;
if (counter == 0) {
NSLog(#"all done");
}
}];
});
}
Because NSURLConnection needs a runloop to execute, if the runloop ends, the connections ends.

NSURLConnection sendAsynchronousRequest for image crashes in ios 4

Hi i am using following code to load the image using NSURLConnection SendAsynchronousRequest call for Tableview but it crashes for IOS 4.3 but same code works for IOS 5.
So can anyone please tell me what changes i have to do support for IOS 4.3
i have gone through below links but nothing worked for me.
NSURLConnection sendAsynchronousRequest:queue:completionHandler not working in iOS 4.3
Have a class called
imagefetcher.h
- (void)fetchImageForURL:(NSURL *)url atIndexPath:(NSIndexPath *)indexPath inTableView:(UITableView *)table;
imagefetcher.m
- (void)fetchImageForURL:(NSURL *)url atIndexPath:(NSIndexPath *)indexPath inTableView:(UITableView *)table {
// NOTE: url is just relative
// There is an issue on iOS 5 that causes the memory capacity to be set to 0 whenever a UIWebView is
// used for the first time. This will correct that issue.
NSLog(#"in fetchImageForURL %#",url);
if([[NSURLCache sharedURLCache] memoryCapacity] != URLMemoryCachSize)
{
[[NSURLCache sharedURLCache] setMemoryCapacity:URLMemoryCachSize];
}
NSURLRequest *request = [NSURLRequest requestWithURL:url
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:30.0f];
NSCachedURLResponse *cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:request];
if (cachedResponse)
{
NSData *data = [cachedResponse data];
NSLog(#"from cache");
[self postImageCallbackWithTableView:table atIndexPath:indexPath forURL:url withImageData:data];
}
else
{
returningResponse:&response error:&error];
// NSLog(#"loading synchronously");
[NSURLConnection sendAsynchronousRequest:request
queue:fetcherQueue
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
[self postImageCallbackWithTableView:table atIndexPath:indexPath forURL:url withImageData:data];
}];
// [self postImageCallbackWithTableView:table atIndexPath:indexPath forURL:url withImageData:data];
}
}
in tableview controller i am calling follwing method but it crsahes for IOS 4.3 but same works for IOS 5
tableviewcontroller.m
-viewdidload()
{
[NSURLConnection sendAsynchronousRequest:request queue:fetcherQueue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
//[self postImageCallbackWithTableView:table atIndexPath:indexPath forURL:url withImageData:data];
UIImage *image = [UIImage imageWithData:data];
[self.images setObject:image forKey:index];
[table1 beginUpdates];
[table1 reloadRowsAtIndexPaths:#[index] withRowAnimation:UITableViewRowAnimationNone];
[table1 endUpdates];
}];
}
If you look at the documentation for sendAsynchronousRequest, it requires iOS 5. If you need to support iOS 4.3, you'll have to use connectionWithRequest:delegate: or initWithRequest:delegate: and then implement the NSURLConnectionDataDelegate methods (which, while a little more work, offers other advantages such as being able to monitor the progress or cancel the request if you need).
Or, as the answer provided at that other question suggests, write your own method that provides the sendAsynchronousRequest functionality but that actually calls sendSynchronousRequest.
Or, just replace your call with sendAsynchronousRequest:
[NSURLConnection sendAsynchronousRequest:request queue:fetcherQueue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
// do something with `data`, `error`, and `response`
}];
With a call to sendSynchronousRequest that you'll perform on some NSOperationQueue queue. So, first, define a property for your operation queue:
#property (nonatomic, retain) NSOperationQueue *networkQueue;
And then initialize it, for example in viewDidLoad:
self.networkQueue = [[NSOperationQueue alloc] init];
self.networkQueue.name = #"com.domain.app.networkqueue";
self.networkQueue.maxConcurrentOperationCount = 4;
And then you can use that network operation queue to call sendSynchronousRequest:
[self.networkQueue addOperationWithBlock:^{
NSURLResponse *response = nil;
NSError *error = nil;
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
// assuming you want to interact with your UI and or synchronize changes to your model, dispatch this final processing back to the main queue
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// do something with `data`, `error`, and `response`
}];
}];
Bottom line, just replace your calls to sendAsynchronousRequest with methods, such as sendSynchronousRequest, that were available in iOS 4.3.

Does using blocks auto create new threads?

JUST started doing work with blocks... very confusing. I am using a block like this:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSDictionary *myDictionary = [[mySingleton arrayPeopleAroundMe] objectAtIndex:indexPath.row];
NSMutableString *myString = [[NSMutableString alloc] initWithString:#"http://www.domain.com/4DACTION/PP_profileDetail/"];
[myString appendString:[myDictionary objectForKey:#"userID"]];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:[myString stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding]]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection
sendAsynchronousRequest:urlRequest
queue:queue
completionHandler: ^( NSURLResponse *response,
NSData *data,
NSError *error)
{
[[mySingleton dictionaryUserDetail] removeAllObjects];
[[mySingleton arrayUserDetail] removeAllObjects];
if ([data length] > 0 && error == nil) // no error and received data back
{
NSError* error;
NSDictionary *myDic = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
[mySingleton setDictionaryUserDetail:[myDic mutableCopy]];
NSArray *myArray = [myDic objectForKey:#"searchResults"];
[mySingleton setArrayUserDetail:[myArray mutableCopy]];
[self userDetailComplete];
} else if
([data length] == 0 && error == nil) // no error and did not receive data back
{
[self serverError];
} else if
(error != nil) // error
{
[self serverError];
}
}];
}
Once the connection is completed, this is called:
-(void)userDetailComplete {
ViewProfile *vpVC = [[ViewProfile alloc] init];
[vpVC setPassedInstructions:#"ViewDetail"];
[[self navigationController] pushViewController:vpVC animated:YES];
}
which caused this error to pop up:
"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."
The only way I got rid of the error was by changing userDetailComplete to this:
-(void)userDetailComplete {
dispatch_async(dispatch_get_main_queue(), ^{
ViewProfile *vpVC = [[ViewProfile alloc] init];
[vpVC setPassedInstructions:#"ViewDetail"];
[[self navigationController] pushViewController:vpVC animated:YES];
});
}
My question: is a new thread started automatically every time a block is used? Are there any other pitfalls I should aware of when using blocks?
Blocks do not create threads. They are closures; they just contain runnable code that can be run at some future point.
This is running on a background thread because that's what you asked it to do:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection
sendAsynchronousRequest:urlRequest
queue:queue
...
You created a new queue and then asked NSURLConnection to call you back on that queue. If you want to be called back on the main thread, pass [NSOperationQueue mainQueue]. That's usually waht you want.

Resources