I just noticed that webViewDidFinishLoad method blocks an entire application, so i can't even touch any buttons.
I need to parse the resulting page of the UIWebView and it can take a lot of time. So what's the best way to parse it without blocking an entire application? Maybe create another thread?
Parse it in the background using GCD:
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
// Get the contents from the UIWebView (in the main thread)
NSString *data = [webView stringByEvaluatingJavaScriptFromString:#"document.documentElement.textContent"];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Parse the data here
dispatch_sync(dispatch_get_main_queue(), ^{
// Update the UI here
});
});
}
It's normal -webViewDidFinishLoad to be called on the main thread. What you need to do is to get the html and do the parsing operation by dispatching it to another queue.
Related
I'm writing an iOS app that is getting data from a server. I have several ViewControllers. I used to load data for that viewcontroller under the viewDidLoad method
-(void)ViewDidload
{
[self loadData];
}
-(void)loadData
{
//calling to webservice caller class
}
But this reduces the app's performance. What is the best method to load data within a viewcontroller? For webservice callings, I have a seperate class. Within my loadData method I call to that particular method inside the webservice calling class.
This is going to block my UI.
What do you mean with "this reduces the app performance". Your app is lagging when you are calling your webservice? This is not because you are calling that in viewDidLoad this is because you are doing that in the main thread.
To call your webservice you can use:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
// Call here your web service
dispatch_sync(dispatch_get_main_queue(), ^{
// push here the results to your ViewController
});
});
With this simple solution you are downloading data from your webservice in a separate thread. And pushing the data to your ViewController with the mainThread. This code is not freezing your app. However you will have a moment that nothing happens. This is a good moment to use a UIActivityIndicatorVew.
I guess your interface is lagging.
Try this:
-(void)ViewDidload
{
[NSThread detachNewThreadSelector:#selector(loadData) toTarget:self withObject:nil];
}
In my iOS app (a kind of flashCard application) I'm using a UIWebView and once the webview content loading is finished I need to perform some UI operations (changes).
I'm checking for this in webViewDidFinishLoad.
When a user taps on a card it will flip and different content is gets loaded. I am using the code below in this flipAction as well as in swipeAction (when user moves from one card to another) to check:
if (![[myWebView stringByEvaluatingJavaScriptFromString:#"document.readyState"] isEqualToString:#"complete"])
{
[self performSelector:#selector(myCustomMethod:) withObject:self afterDelay:3.0];
}
Sometimes, not always, my UI will freeze on the above if condition and after that the UI will not respond further. The app must be manually killed and relaunched.
Do I need to call stringByEvaluatingJavaScriptFromString: method other than thread?
or what may be the cause for this?
You can try background thread
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul);
dispatch_async(queue, ^{
// async operation
// Call your method here
dispatch_sync(dispatch_get_main_queue(), ^{
// Update UI here
});
});
I am navigating by clicking a button to a viewcontroller where I am loading webview,but after clicking the button it is taking some time,how to navigate faster and load webview faster,please help.I have only the following code in second viewcontroller.
-(void)viewWillAppear:(BOOL)animated{
self.navigationController.navigationBarHidden=YES;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
NSURLRequest *request=[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://myurl"]];
dispatch_async(dispatch_get_main_queue(), ^{
[self.wb loadRequest:request] ;
});
});
}
Try this code
-(void)viewWillAppear:(BOOL)animated{
self.navigationController.navigationBarHidden=YES;
dispatch_queue_t jsonParsingQueue = dispatch_queue_create("jsonParsingQueue", NULL);
// execute a task on that queue asynchronously
dispatch_async(jsonParsingQueue, ^{
NSURLRequest *request=[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://myurl"]];
dispatch_async(dispatch_get_main_queue(), ^{
[self.wb loadRequest:request] ;
});
});
}
If I understand your question, there isn't much you can do to make if faster. That request speed is based on internet speed (Over which you don't have much of a control).
Also the request already happens asynchronously, so there's no need to do that yourself.
You are combining two things as you navigate to your webview
loading and displaying a view
Retrieving data from the internet
You can only directly influence the first one, the second one is well beyond your control.
By performing the asynchronous NSURLRequest from within the viewWillAppear method, you are telling iOS to delay showing the view until the internet has given you all the data it needs.
A better approach is to configure all the visual elements of your new view, display that view in the interface, and then AFTER the new view is visible, perform your NSURLRequest.
Adding a UIActivityIndicator may also help your users realize that your app was snappy and responsive, and the delay they are experiencing is from the internet.
Perhaps the easiest way to fix this would be to move your code over to
- (void)viewDidLoad {}
I set up 2 UIWebViews, the first is controlling the second. They are communicating though ajax requests.
I want to load a website in the second WebView and then proceed with other tasks. Unfortunately this is crashing. It is crashing because the Web Thread is being occupied by the first right after it gets a response. The second has no time to load the web page and causes a deadlock.
I want to delay the response until the second WebView has fully loaded the web page. Currently the second WebView starts loading right after the first WebView gets and response (thats when the Web Thread is being released).
Is it possible to "suspend"/"pause" the current (first WebView) execution until the second WebView has finished loading? This means to start the execution of the second WebView as well.
events:
First WebView sends command to load web page (using synchronous AJAX command)
Web Thread blocked by task of first WebView
Execution of command and computation of Response
Returning Response
Second WebView starts Loading of web page
deadlock
I want event 5 to be before event 4. Is this possible?
Solution:
As you can read in the comments I've solved my problem by making then work concurrently. Basically I had to make use of the Grand Central Dispatch (GCD). Another option would be to implement it with NSOperationQueues which gives you more control about the flow of execution, but tends to be more complicated to implement.
helpful literature:
Apple: Concurrency Programming Guide
Multithreading and Grand Central Dispatch on iOS for Beginners Tutorial
How To Use NSOperations and NSOperationQueues
Now, this is may require some tweaking, but it should give you a good place to start.
Basically, we create a concurrent GCD queue and dispatch 2 async calls to load HTML strings with the contents of your 2 different URLS.
When the requests complete they will load their html strings into your web views. Note that the first UIWebView will only load its data if the second UIWebView has already been loaded.
__weak ViewController *bSelf = self;
dispatch_queue_t webQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul);
dispatch_async(webQueue, ^{
NSError *error;
bSelf.html1 = [NSString stringWithContentsOfURL:[NSURL URLWithString:#"http://google.com"] encoding:NSASCIIStringEncoding error:&error];
if( !bSelf.secondLoaded)
{
dispatch_sync(dispatch_get_main_queue(), ^{
[bSelf.webView1 loadHTMLString:bSelf.html1 baseURL:nil];
});
}
});
dispatch_async(webQueue, ^{
NSError *error;
bSelf.html2 = [NSString stringWithContentsOfURL:[NSURL URLWithString:#"http://amazon.com"] encoding:NSASCIIStringEncoding error:&error];
bSelf.secondLoaded = YES;
dispatch_sync(dispatch_get_main_queue(), ^{
[bSelf.webView2 loadHTMLString:bSelf.html2 baseURL:nil];
if( bSelf.html1 != nil )
{
[bSelf.webView1 loadHTMLString:bSelf.html1 baseURL:nil];
}
});
});
Yes, the two best ways to do this would be to use either Grand Central Dispatching (GCD) or NSOperation and NSOperationQueue.
The explanation of this is quite long, but I would direct you to read something like this. You can find a lot of other resources if you search for these terms in google.
Have you tried something like this?
- (void)viewDidLoad
{
[super viewDidLoad];
self.webView.delegate = self;
self.webView2.delegate = self;
[self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"yourURL"]]];
}
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
if (webView == self.webView)
{
if (!self.webView.isLoading)
{
[self.webView2 loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"yourURL"]]];
}
}
}
I have a method that builds a package, sends it to a web service, gets a package back, opens it and returns me a nsdictionary. How can I call it on a background queue in order to display a HUD while it requests the data?
You could detach a new thread like following
- (void) fetchData
{
//Show Hud
//Start thread
[NSThread detachNewThreadSelector:#selector(getDataThreaded)
toTarget:self
withObject:nil];
}
- (void) getDataThreaded
{
//Start Fetching data
//Hide hud from main UI thread
dispatch_async(dispatch_get_main_queue(), ^{
//Update UI if you have to
//Hide Hud
});
}
Grand central dispatch (gcd) provides great support for doing what you ask. Running something in the background using gcd is simple:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_NORMAL, 0) ^{
NSDictionary* data = [self fetchAndParseData];
dispatch_async(dispatch_get_main_queue(), ^{
[self dataRetrieved:data];
});
});
This call will return immediately (so your UI will continue to be responsive) and dataRetrieved will be called when the data is ready.
Now, depending on how fetchAndParse data works it may need to be more complicated. If you NSURLConnection or something similar, you might need to create an NSRunLoop to process data callbacks on the gcd thread. NSURLConnection for the most part is asynchronous anyway (though callbacks like didReceiveData will be routed through the UI thread) so you can use gcd only to do the parsing of the data when all the data has been retrieved. It depends on how asynchronous you want to be.
In addition to previous replies, why don't you use NSOperation and NSOperationQueue classes? These classes are abstractions under GCD and they are very simple to use.
I like NSOperation class since it allows to modularize code in apps I usually develop.
To set up a NSOperation you could just subclass it like
//.h
#interface MyOperation : NSOperation
#end
//.m
#implementation MyOperation()
// override the main method to perform the operation in a different thread...
- (void)main
{
// long running operation here...
}
Now in the main thread you can provide that operation to a queue like the following:
MyOperation *op = [[MyOperation alloc] initWithDocument:[self document]];
[[self someQueue] addOperation:op];
P.S. You cannot start an async operation in the main method of a NSOperation. When the main finishes, delegates linked with that operations will not be called. To say the the truth you can but this involves to deal with run loop or concurrent behaviour.
Here some links on how to use them.
http://www.cimgf.com/2008/02/16/cocoa-tutorial-nsoperation-and-nsoperationqueue/
https://developer.apple.com/cocoa/managingconcurrency.html
and obviously the class reference for NSOperation