I want to put in a timeout in case it takes too long to find my location, send out the relevant url, and parse the xml. It worked when I used performSelector:withObject:afterDelay in the locationManager (just to test getting the xml), but when I put similar code around my parser it doesn't actually abort the parsing. I am testing this by dropping the delay to 0.01.
My problem is: even with the delay set to 0.01, it still waits for all the parsing to complete first, and only then does it put up the alertView that is coded in the parsingDidTimeout method.
I did try this with a timer, and that wasn't working as well as performSelector: does in the other parts of my code. Either way, it doesn't put up the alertView, and stop the parsing, until after the parsing has finished, no matter how long that takes.
I create a url which requires a radius. First I try a small radius, but if I don't get the data I need, I expand the radius and send the url again and parse again. Here is part of my StartParsing method.
xmlParser = [[NSXMLParser alloc] initWithContentsOfURL:url];
XMLParser *parser = [[XMLParser alloc] initXMLParser];
[xmlParser setDelegate:parser];
if (!hadToExpandRadius){//meaning, only do this the first time I send out the url and parse
[self performSelector:#selector(parsingDidTimeout:) withObject:nil afterDelay:0.01];
}
//Start parsing the XML file.
BOOL success = [xmlParser parse];
if(success){
if((didNotGetTheDataYet) && (radius < 500)){
hadToExpandRadius = YES;
radius = radius + 35;
[self startParsing];//do this same method, with larger radius
}
else {
NSLog(#"No Errors");
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(parsingDidTimeout:) object:nil];}
[parser release];
}
-(void)parsingDidTimeout{
[xmlParser abortParsing];
UIAlertView *servicesDisabledAlert = [[UIAlertView alloc] initWithTitle:#"Try Later" message:#"We need a better connection. We can get the data later." delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[servicesDisabledAlert show];
[servicesDisabledAlert release];
[myActivityView stopAnimating];
}
Thank you for your help.
Calling performSelector:withObject:afterDelay: you ask the run loop to call the selector later. But [xmlParser parse] blocks the run loop, so it doesn't have a chance to call you selector.
abortParsing is designed to be called inside parsers' delegate methods.
The workaround can be to parse in a separate thread.
Found it -- just extra ":" in my performSelector:#selector(parsingDidTimeout:)!
I thought it was something fancy having to do with the second thread. Just syntax.
Thanks for explaining about the parse blocking the run loop. I was hoping not to need another thread, but your suggestion fixed my problem. Thanks.
Related
How can I wait for a certain task to be done?
This is what I'm trying to do:
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:stringURL]];
if (!data) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:[NSString stringWithFormat:#"%#", NOCONNECTIONALERTTITLE] message:[NSString stringWithFormat:#"%#", NOCONNECTIONALERTMESSAGE] delegate:self cancelButtonTitle:[NSString stringWithFormat:#"%#", NOCONNECTIONALERTCANCELBUTTON] otherButtonTitles:nil, nil];
[alert show];
} else { ... }
This if is always false, since its called before data even is initialized. I'm 100% sure, that the connection is established.
I want to use this as an easy alternative to checking the internet-connection programmatically.
dataWithContentsOfURL: is a blocking call, so data is initialised when you try to use it. If it's nil then you likely have an issue with the URL you're trying to load.
Because this is blocking you shouldn't use it on the main thread. And, generally, you shouldn't want to wait for completion - you should embrace the asynchronous nature of network operations and use appropriate asynchronous API and designs to handle them.
Log the NSURL that you are creating to check it exists (and fix if it doesn't).
Your simplest option for running an asynchronous download and handling the result is to use NSURLConnection +sendAsynchronousRequest:queue:completionHandler:. Supply the queue as [NSOperationQueue mainQueue] so that your callback is run on the main thread and you can update your UI.
Update: This problem was also reported here, with a more detailed treatment of the cause:
UIImageWriteToSavedPhotosAlbum saves only 5 image out of 10. Why?
In my case as well, the error was: "Write busy" - this seems to be an issue related to device speed. There is probably some solution that involves manually handling threading or similar - but, inspired by Tommy's answer below, I serialized the saving of images, and that works around the problem.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Situation:
I'm trying to save a large-ish collection of images to the camera roll using a for-loop (number of images depends on user input - may be 1, 2, 3, or theoretically hundreds). Images are pointed to by an NSSet (though I could just as easily do an NSArray), and the NSSet is set to nil after the for-loop completes.
I'm using UIImageWriteToSavedPhotosAlbum() to save out the images on a detached thread (using NSThread's detachNewThreadSelector:toTarget:withObject: class method), and starting a UIActivityIndicator spinner on the main thread.
Problem:
When I attempt to save out more than ~5, any image after the ~5th will output this error in the log:
*** -[NSKeyedUnarchiver initForReadingWithData:]: data is NULL
For example, let's say I exported 9 images. The for-loop will run through all 9 images (confirmed by NSLogging), but I'll get around 4 iterations of the error above (and only 5 images saved to the camera roll).
If I add a breakpoint in the loop and wait a second or two in between each iteration, however, they are all saved correctly without complaint. So..
Theory:
Based on my logs and observations, UIImageWriteToSavedPhotosAlbum() is clearly running asynchronously and is somehow 'too slow' to keep up with my application.
Is there a simple way to force it to run synchronously (ideally on the main thread)? I've been experimenting with adding reference counts to the images I'm trying to save out, but (1) this feels hacky, and (2) I haven't solved the problem regardless.
Any advice would be great. Thanks!
I use ALAssetsLibrary and a dispatch_queue to make sure that images are saved sequentially:
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
dispatch_queue_t queue = dispatch_queue_create("com.myApp.saveToCameraRoll", NULL);
[images enumerateObjectsUsingBlock:^(UIImage *image, NSUInteger idx, BOOL *stop) {
dispatch_async(queue, ^{
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[library writeImageToSavedPhotosAlbum:image.CGImage metadata:metaData completionBlock:^(NSURL *assetURL, NSError *writeError) {
if (writeError) {
// handle the error
}
else {
if (image == [images lastObject]) {
dispatch_async(dispatch_get_main_queue(), ^{
// perhaps indicate to the user that save has finished
});
}
}
dispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
dispatch_release(sema);
});
}];
If you're desperate not to write multiple images at once then you could use UIImageWriteToSavedPhotosAlbum's completion target and selector to implement a form of tail recursion. Something like:
- (void)writeSetToSavedPhotosAlbum:(NSMutableSet *)images
{
if(![images count]) return;
UIImage *imageToSave = [[[images anyObject] retain] autorelease];
[images removeObject:imageToSave];
NSLog(#"I shall now write image %#", imageToSave);
UIImageWriteToSavedPhotosAlbum(
imageToSave,
self,
#selector(writeSetToSavedPhotosAlbum:),
images);
}
EDIT: it may also be worth seeing whether you get the same results with ALAssetsLibrary's -writeImageToSavedPhotosAlbum:orientation:completionBlock:, which takes a block for completion so is even more straightforward to work with.
The docs do mention you'll be notified asynchronously. I wonder if this might work: for each image you want to save create a block operation and add it to main operation queue. Kind of like:
for (UIImage *toSave in imagesToSave)
{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
UIImageWriteToSavedPhotosAlbum(toSave, target, selector, ctx);
}];
}
Might be worth a shot, let me know if it helps!
In this Code Work Save image :
UIImageWriteToSavedPhotosAlbum([self screenshot], nil, nil, nil);
UIAlertView *alert=[[UIAlertView alloc]initWithTitle:#"Save" message:#"Photo saved to album" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
[alert show];
In an iOS app, I'm running a fairly large script on a UIWebView using stringByEvaluatingJavaScriptFromString (large in terms of the length of the javascript string). There is a brief pause after calling the javascript causing other elements on the screen to hiccup for a moment.
Placing the javascript call in a function called in the background with self performSelectorInBackground breaks the application. Is there a safe way to call run this on a background thread or otherwise prevent the interface from pausing?
No, Webviews and the Webkit JavaScript engine are both single-threaded and cannot be used on a background thread.
A better option is to split up your JavaScript into discrete execution blocks and pipeline them using a JavaScript timer, like this (JS code, not Obj-C):
var i = 0;
var operation = function() {
switch (i) {
case 0:
//do first part of code
break;
case 1:
//do second part of code
break;
case 2:
//do third part of code
break;
etc...
}
//prepare to execute next block
i++;
if (i < TOTAL_PARTS) {
setTimeout(operation, 0);
}
};
operation();
That will prevent your script from blocking user interaction while it executes
Well, I was doing the same thing. I had to run a synchronous ajax request which was freezing my UI. So this is how I fixed it :
__block NSString *message;
dispatch_queue_t q = dispatch_queue_create("sign up Q", NULL);
dispatch_async(q, ^{
NSString *function = [[NSString alloc] initWithFormat: #"signup(\'%#\',\'%#\',\'%#\')",self.email.text,self.password.text,self.name.text];
dispatch_async(dispatch_get_main_queue(), ^{
NSString *result = [self.webView stringByEvaluatingJavaScriptFromString:function];
NSLog(#"%#",result);
if ([result isEqualToString:#"1"]) {
message = [NSString stringWithFormat:#"Welcome %#",self.name.text];
[self.activityIndicator stopAnimating];
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}
else {
message = [NSString stringWithFormat:#"%# is a registered user",self.name.text];
[self.activityIndicator stopAnimating];
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Message" message:message delegate:self cancelButtonTitle:#"Okay" otherButtonTitles: nil];
[alertView show];
});
});
The logic is simple. Go to a new thread, and from within that, dispatch to the main queue and then do the JS work and everything worked like a charm for me...
Anything you do with a UIWebView must be done on the main thread. It's a UI element, so this is why performSelectorInBackground breaks your app.
You could try putting that call into an NSOperation. Since you are working with a UI element, be sure to use the [NSOperationQueue mainQueue].
I have a UITableViewController that when opened displays a table of the following object:
class {
NSString *stringVal;
int value;
}
However, whenever this controller opens, I want it to download the data from the internet and display "Connecting..." in the status bar and refresh the stringVal and value of all of the objects. I do this by refreshing the array in the UITableViewController. However, to do this the UI hangs sometimes or even displays "blank" table cells until the operation has ended. I'm doing this in an NSOperationQueue to download the data, but I'm wondering if there's a better way to refresh the data without those weird UI bugs.
EDIT:
the UI no longer displays blank cells. This was because cellForRowAtIndexPath was setting nil values for my cellText. However, it still seems somewhat laggy when tableView.reloadData is called even though I'm using NSOperationQueue.
EDIT2:
Moreover, I have two problems: 1. the scrolling prevents the UI from being updated and 2. when the scrolling does stop and the UI starts to update, it hangs a little bit. A perfect example of what I'm trying to do can be found in the native Mail app when you view a list of folders with their unread count. If you constantly scroll the tableview, the folders unread count will be updated without any hanging at all.
Based on your response in the question comments, it sounds like you are calling [tableView reloadData] from a background thread.
Do not do this. UIKit methods, unless otherwise specified, always need to be called from the main thread. Failing to do so can cause no end of problems, and you are probably seeing one of them.
EDIT: I misread your comment. It sounds like you are not updating the UI from a background thread. But my comments about the architecture (i.e. why are you updating in a background thread AFTER the download has finished?).
You state that "when the data comes back from the server, I call a background operation..." This sounds backwards. Normally you would have your NSURLConnection (or whatever you are using for the download) run on the background thread so as not to block to UI, then call out to the main thread to update the data model and refresh the UI. Alternatively, use an asynchronous NSURLConnection (which manages its own background thread/queue), e.g.:
[NSURLConnection sendAsynchronousRequest:(NSURLRequest *)
requestqueue:(NSOperationQueue *)queue
completionHandler:(void (^)(NSURLResponse*, NSData*, NSError*))handler];
And just make sure to use [NSOperationQueue mainQueue] for the queue.
You can also use GCD, i.e., nested dispatch_async() calls (the outer to a background queue for handling a synchronous connection, the inner on the main queue to handle the connection response).
Finally, I will note that you in principle can update your data model on the background thread and just refresh the UI from the main thread. But this means that you need to take care to make your model code thread-safe, which you are likely to mess up at least a couple times. Since updating the model is probably not a time consuming step, I would just do it on the main thread too.
EDIT:
I am adding an example of how one might use GCD and synchronous requests to accomplish this. Clearly there are many ways to accomplish non-blocking URL requests, and I do not assert that this is the best one. It does, in my opinion, have the virtue of keeping all the code for processing a request in one place, making it easier to read.
The code has plenty of rough edges. For example, creating a custom dispatch queue is not generally necessary. It blindly assumes UTF-8 encoding of the returned web page. And none of the content (save the HTTP error description) is localized. But it does demonstrate how to run non-blocking requests and detect errors (both at the network and HTTP layers). Hope this is helpful.
NSURL *url = [NSURL URLWithString:#"http://www.google.com"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
dispatch_queue_t netQueue = dispatch_queue_create("com.mycompany.netqueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(netQueue,
^{
// We are on a background thread, so we won't block UI events (or, generally, the main run loop)
NSHTTPURLResponse *response;
NSError *error;
NSData *data = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response
error:&error];
dispatch_async(dispatch_get_main_queue(),
^{
// We are now back on the main thread
UIAlertView *alertView = [[UIAlertView alloc] init];
[alertView addButtonWithTitle:#"OK"];
if (data) {
if ([response statusCode] == 200) {
NSMutableString *body = [[NSMutableString alloc] initWithData:data
encoding:NSUTF8StringEncoding];
[alertView setTitle:#"Success"];
[alertView setMessage:body];
}
else {
[alertView setTitle:#"HTTP Error"];
NSString *status = [NSHTTPURLResponse localizedStringForStatusCode:[response statusCode]];
[alertView setMessage:status];
}
}
else {
[alertView setTitle:#"Error"];
[alertView setMessage:#"Unable to load URL"];
}
[alertView show];
[alertView release];
});
});
dispatch_release(netQueue);
EDIT:
Oh, one more big rough edge. The above code assumes that any HTTP status code != 200 is an error. This is not necessarily the case, but handling this is beyond the scope of this question.
See also:
Objective-C Asynchronous Web Request with Cookies
I spent a day writing this code and can anyone tell me what is wrong here?
WSHelper is inherited from NSObject, I even tried NSDocument and NSObjectController and everything..
-(void) loadUrl: (NSString*) urlStr{
url = [[NSURL alloc] initWithString:urlStr];
request = [NSURLRequest requestWithURL:url cachePolicy: NSURLRequestReloadIgnoringCacheData timeoutInterval: 60.0];
connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];
if(connection)
{
receivedData = [[NSMutableData data] retain];
//[connection start];
}
else
{ display error etc... }
NSApplication * app = [NSApplication sharedApplication];
[app runModalForWindow: waitWindow];// <-- this is the problem...
}
-(void)connection: (NSURLConnection*)connection didReceiveData:(NSData*)data{
progressText = #"Receiving Data...";
[receivedData appendData:data];
}
-(void)connection: (NSURLConnection *)connection didFailWithError:(NSError *)error{
progressText = #"Error...";
NSAlert * alert = [[NSAlert alloc] init];
[alert setMessageText:[error localizedDescription]];
[alert runModal];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
progressText = #"Done...";
pData = [[NSData alloc] initWithData:receivedData];
[self hideWindow];
}
The code just wont do anything, it doesnt progress at all. I even tried it with/without startImmediately:YES but no luck !!!, this is executed in main window so even the thread and its run loop is running successfully.
I tried calling synchronous request, and it is working correctly !! But I need async solution.
I have added CoreServices.Framework in project, is there anything more I should be adding to the project? any compiler settings? Or do i have to initialize anything before I can use NSURLConnection?
Any solution to run NSURLConnection on different thread on its own NSRunLoop, Objective-C and MAC Development has no sample code anywhere in documentation that makes everything so difficult to code.
I also met the same problem that didn't get the delegate method called when using NSURLConnection in a Modal Window.
after some investigation, following code resolve it.
NSURLConnection* conn = [[NSURLConnection alloc] initWithRequest:requst delegate:self startImmediately:NO];
[conn scheduleRunLoop:[NSRunLoop currentRunLoop] forMode:NSModalPanelRunLoopMode];
[conn start];
However, when connectionDidFinishLoading called, [NSApp stopModal] doesn't work, need call [NSApp abortModal] instead.
Firstly you're making starting the connection too complicated. Change to:
connection = [[NSURLConnection alloc] initWithRequest:request delegate:self]
Remove [connection start]. Now:
Is your app definitely running the run loop normally? NSURLConnection requires this to work.
Are you able to perform a synchronous load of the URL request?
In the debugger, can you see that url is what you expect it to be? What is it?
Is it possible that you're deallocating WSHelper before any delegate messages are received? NSURLConnection is asynchoronous after all.
One does not need to do anything special to use NSURLConnection, it's a straightforward part of the Foundation framework. No special compiler settings required. No initialization before use. Nothing. Please don't start blindly trying stuff like bringing in CoreServices.Framework.
As sending the request synchronously works, there must be something wrong with your handling of the asynchronous aspect. It could be:
The runloop is not running in NSDefaultRunLoopMode so the connection is unable to schedule itself.
Some other part of your code is calling -cancel on the connection before it has a chance to load.
You are managing to deallocate the connection before it has a chance to load.
Real problem
Ah, in fact I've just realised what's going on. You are calling:
-[NSApp runModalForWindow:]
Read the description of what this method does. It's not running the run loop like NSURLConnection expects. I'd say that really, you don't want to be presenting a window quite like this while running a URL connection for it.
I'd also suggest that you implement the -connection:didReceiveResponse: delegate method too. You want to check here that the server is returning the expected status code.
You say that you're using this in a modal dialog? A modal dialog puts the run loop into a different mode. You should be able to get this to work by scheduling it to run in the modal dialog run loop mode, in addition to the normal run loop mode. Try adding this line of code after you allocate connection in loadURL:
[connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSModalPanelRunLoopMode];
Hope that helps.
How do you know it isn't doing anything? Are there any error or warning messages during the compile? Are any error messages showing up on console when the program is running?
Have you tries setting breakpoints in your code and following through what you expect to be happening?