dispatch_async confliction - objective-c

Currently i am working on a chatting app, I am trying to upload the image, every thing is working fine except that when image is uploading the UI freeze, so async approach came into the scene, this is what i am trying to do:
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info{
[self dismissModalViewControllerAnimated:YES];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul);
dispatch_async(queue, ^{
UIImage *image = [info objectForKey: UIImagePickerControllerOriginalImage];
NSData *imgData = UIImageJPEGRepresentation(image, 1.0);
//[self performSelectorOnMainThread:#selector(send:) withObject:imgData waitUntilDone:YES];
[self send:imgData];
});
}
I am getting this error:
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...
WebThreadLock
-[UITextView setText:]
-[HPTextViewInternal setText:]
-[HPGrowingTextView setText:]
-[chatViewController send:]
__74-[chatViewController imagePickerController:didFinishPickingMediaWithInfo:]_block_invoke_0
_dispatch_call_block_and_release
_dispatch_worker_thread2
_pthread_wqthread
start_wqthread
I am using HPGrowingTextView to give a iMessage kind of expandable typing area for typing messages, but getting this problem.
I searched this error
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
peoples suggests using performSelectorOnMainThread but this approach again freeze the UI.
How to solve this conflict or is there any other approach.
Inside [self send:imageData]
...building a url and appending hFile(imageData)
[body appendData:[NSData dataWithData:hFile]];
[body appendData:[[NSString stringWithFormat:#"\r\n--%#--\r\n",boundary] dataUsingEncoding:NSUTF8StringEncoding]];
// setting the body of the post to the reqeust
[request setHTTPBody:body];
NSData *returnData = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
NSString *returnString = [[NSString alloc] initWithData:returnData encoding:NSUTF8StringEncoding];
NSString *imgUrl = [NSString stringWithFormat:#"http://www.www.www/uImages/thumbs/%#",returnString];
...
after uploading, thumbnail of image is returned, if I use [NSURLConnection sendAsynchronousRequest I get empty thumbnail which I am diplaying in uitableview.

When you want to change anything in the UI you should do it on the main thread.
So if you want to change the text of HPGrowingTextView control you have, you can do the following:
dispatch_async(dispatch_get_main_queue(), ^{
growingTextView.text = #"Some text";
})

You are getting the crash because you are calling send outside the main thread. The stack trace is obvious about this fact.
You need to make those calls on the main thread. When you do, however, your UI will of course hang because of this call...
NSData *returnData = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
From the method name, it's obvious that this call is synchronous, and will block until the result is returned.
Thus, you need to use the asynchronous form instead.
sendAsynchronousRequest:queue:completionHandler:

Related

how do you refresh ABUnknownPersonViewController?

My problem: I have a ABUnknownPersonViewController which needs to get an image from an online database. I have implemented an NSSession to download the image. The thread adds the image to the ABRecordRef then adds it to the ABUnknownPersonViewController. When the controller is pushed on the stack, it doesn't show the image...therfore sadness ensues.
NSString *imageUrl = dict[#"mug"];
__block NSURL *url = [NSURL URLWithString:imageUrl];
__block NSData *urlData = nil;
NSURLSessionConfiguration *sessionConfig =
[NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session =
[NSURLSession sessionWithConfiguration:sessionConfig
delegate:self
delegateQueue:nil];
NSURLSessionDownloadTask *getImageTask =
[session downloadTaskWithURL:[NSURL URLWithString:imageUrl]
completionHandler:^(NSURL *location,
NSURLResponse *response,
NSError *error) {
urlData = [NSData dataWithContentsOfURL:url];
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
newPerson = controller.displayedPerson;
ABPersonSetImageData(newPerson, (__bridge CFDataRef)(urlData), &anError);
controller.displayedPerson = newPerson;
if(urlData != nil) {
NSLog(#"I got here");
[self viewWillAppear:YES];
[[NSNotificationCenter defaultCenter] postNotificationName:#"com.razeware.imagegrabber.imageupdated" object:(__bridge id)(newPerson)];
}
});
}];
[getImageTask resume];
**Oddly enough, if I choose "Create New Contact" the image appears (So, the thread is working?). If I click cancel on iPhone then the image appears on the ABUnknownPersonViewController. So, it seems like the controller just needs to be refreshed. How?
I've tried [[self view] setNeedsDisplay]; //ain't working'
Help please!
OK - This may not be the best practice. However, since you already have [self viewWillAppear:YES]; you can try calling [self viewWillDisappear:NO]; before viewWillAppear. This is likely to refresh the content.
Sourced from here.
I had a similar problem recently and got this from somewhere else. Try adding a dispatch sync after the async to the main queue like this:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul);
dispatch_async(queue, ^{
dispatch_sync(dispatch_get_main_queue(), ^{
// your image fetch code here
});
});
I found a solution:
ABRecordRef cannot be updated in a thread. It must be updated on the main thread.

Code execution with sendAsynchronousRequest

A variable is being set to (null) due to the sendAsynchronousRequest not completing before the request is complete. See code:
main.m:
GlobalSettings *globalsettings = [[GlobalSettings alloc] init];
NSString *url = [globalsettings facebookLink];
NSLog(#"URL: %#", url);
So, inside GlobalSettings:
-(NSString *)facebookLink
{
__block NSString *strReturn;
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:#"http://urlEditedOut/"]];
__block NSDictionary *json;
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
json = [NSJSONSerialization JSONObjectWithData:data
options:0
error:nil];
strReturn = json[#"FB"];
}];
return strReturn;
}
So this works fine, has been tested inside the completion block. However back in main.m the variable url is being set to (null) due to (i assume) the async request still connecting / processing request.
How do you combat this so that the variable is saved as the correct value?
The url is set to null because the method returns immediately due to the asynchronous request. The way to avoid this is a delegate. make main the delegate of the GobalSettings and call the delegate method from the completion block. This SO-Post isnt an exact duplicate, but its close enough to get you started.
How to return the ouput if method is using NSURLConnection Asynchronous calls with blocks
Avt's answer is what i would suggest, but returning a block works, too.

App crashes on slow InternetConnection

I am getting data from server in applicationDidBecomeActive method.When net connection is too slow app keep crashing.I do not know how to handle this problem.any help will be appreciated.thanks in advance.
NSString *post =[[NSString alloc] initWithFormat:#"=%##=%#",myString,acMobileno];
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"http:///?data=%#&no=%#",myString,acMobileno]];
NSData *postData = [post dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
NSString *postLength = [NSString stringWithFormat:#"%d", [postData length]];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setURL:url];
[request setHTTPMethod:#"POST"];
[request setValue:postLength forHTTPHeaderField:#"Content-Length"];
[request setHTTPBody:postData];
NSError *error1 = [[NSError alloc] init];
NSHTTPURLResponse *response = nil;
NSData *urlData=[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error1];
NSString *string;
if ([response statusCode] >=200 && [response statusCode] <300)
{
string = [[NSString alloc] initWithData:urlData encoding:NSMacOSRomanStringEncoding];
}
It's probably crashing because the connection has started downloading, but it hasn't finished therefore allowing the complier to pass your if statement, which would inevitably give a nil urlData parameter.
To fix this, you should be checking to see if there is an error, and then the response headers for the download. Also, I recommend running this operation on a background thread so that it doesn't block the user experience - at the moment, the app will have a delayed launch depending on the size of your file, and the user's download speed.
NSError *error1 = nil;
NSHTTPURLResponse *response = nil;
NSData *urlData=[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error1];
NSString *string = nil;
if (error != nil && ([response statusCode] >=200 && [response statusCode] <300)){
string = [[NSString alloc] initWithData:urlData encoding:NSMacOSRomanStringEncoding];
}
else {
NSLog(#"received error: %#", error.localizedDescription);
}
For a background thread, run the above code in a dispatch_async statement, or use -sendAsynchronousRequest: instead of -sendSynchronousRequest.
Alternatively, as #Viral said, it is possible that the request is taking too long, and the app is hanging as a result of the synchronous request not finishing before the UI should have been loaded.
Most probably, it's due to synchronous call in Application's delegate method. It is taking too much time to load the UI (As internet connection is slow and you are calling the web service on main thread); and therefore OS thinks your App has hanged due to unresponsive UI and crash the App itself.
Just for debugging purpose, try the same code in your FirstViewController's viewDidAppear method. It should work fine there. And if it is so, you need to change your call to somewhere else (also, preferably in some background thread, OR Async).
EDIT: Though, If it works elsewhere, you need to change the call as Async OR on background thread for smoother UX.

How to download image asynchronously using blocks?

I want to click a button to start downloading image and to update my UIImageView to new image once it is updated. The problem with my code is that it only downloads stuff, and not updates. It only updates if I click it again.
I want it to update the image some time in future, when the image is downloaded. How do I do that?
Edit: I have found the wrong code, changing it a bit helped and it all works.
Here comes another question - how do I simplify this code without turning it a mess? It looks excessive.
- (IBAction)getImage
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^{
NSURL *imageURL = [NSURL URLWithString:#"http://example.com/1.jpg"];
__block NSData *imageData;
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^{
imageData = [NSData dataWithContentsOfURL:imageURL];
dispatch_sync(dispatch_get_main_queue(), ^{
self.image = [UIImage imageWithData:imageData];
});
});
});
self.imageView.image = self.image;
}
You are setting the imageView before the image is done downloading, you need to move the logic into the block. Also there is no reason for you to do an extra dispatch_sync inside of your dispatch_async.
- (IBAction)getImage
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^{
NSURL *imageURL = [NSURL URLWithString:#"http://example.com/1.jpg"];
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
//This is your completion handler
dispatch_sync(dispatch_get_main_queue(), ^{
//If self.image is atomic (not declared with nonatomic)
// you could have set it directly above
self.image = [UIImage imageWithData:imageData];
//This needs to be set here now that the image is downloaded
// and you are back on the main thread
self.imageView.image = self.image;
});
});
//Any code placed outside of the block will likely
// be executed before the block finishes.
}
Check out https://github.com/rs/SDWebImage
I use it to download images in the background with progress notification. It can be added simply to your project using Cocoapods (http://cocoapods.org).
There are several other async image loaders available on Cocoapods and GitHub if that doesn't work for you.
This is what I've been using although it doesn't provide any progress which I think is often useful. This is simple through.
- (void)downloadImageWithURL:(NSURL *)url completionBlock:(void (^)(BOOL succeeded, NSData *image))completionBlock
{
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if ( !error )
{
completionBlock(YES,data);
NSLog(#"downloaded FULL size %lu",(unsigned long)data.length);
} else{
completionBlock(NO,nil);
}
}];
}

Using NSURLConnection sendAsynchronousRequest

I have an application in Xcode 4.5.2. It sends a URL request to download an image and sets the image in an image view. I have the following code that works fine to accomplish this:
dispatch_queue_t downloadQueue = dispatch_queue_create("Get Facebook Friend", NULL);
dispatch_async(downloadQueue, ^{
self.firstFriendImage = [UIImage imageWithData:
[NSData dataWithContentsOfURL:
[NSURL URLWithString:
[[self.facebookPhotosAll objectAtIndex:self.randomIndex1]
objectForKey:#"pic_big"]]]];
dispatch_async(dispatch_get_main_queue(), ^{
[self postDownloadTasks:self.topView setLabel:self.firstFriendLabel
withFriendName:self.firstFriendName cropImage:self.firstFriendImage
inImageView:self.friendOneView atYPoint:22];
});
});
So although this code works fine, being new to objective C, I am trying to explore the language a bit to see how else I can do this same thing. So I tried to use the NSURLConnection sendAsynchronousRequest: method (after looking at some examples on here), but I can't seem to get this method to work. This is what I did:
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
NSString *string = [[self.facebookPhotosAll
objectAtIndex:self.randomIndex1]objectForKey:#"pic_big"];
[NSURLConnection sendAsynchronousRequest: [NSURL URLWithString:
string] queue:queue
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error){
// Make sure eveything is ok
if(error){
// do something
}
self.firstFriendImage = [UIImage imageWithData:data];
dispatch_async(dispatch_get_main_queue(), ^{
[self postDownloadTasks:self.topView setLabel:self.firstFriendLabel
withFriendName:self.firstFriendName cropImage:self.firstFriendImage
inImageView:self.friendOneView atYPoint:22];
});
}];
So this code doesn't work at all (it's not returning any data). The app crashes when the method is called and I get the following message:
** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSURL _CFURLRequest]: unrecognized selector sent to instance 0xa39dd20'
Can anyone tell me how to accomplish what I did in the first code excerpt using the NSURLConnection sendAsynchronousRequest method?
In the send asynchronousRequest method, your URL string is missing a part. It should be like in your first method that worked:
[[self.facebookPhotosAll objectAtIndex:self.randomIndex1] objectForKey:#"pic_big"]