Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Accelerate loading of WKWebView

As explained here, WKWebView has a bug whereby apps that bundle a local webpage have to copy the bundle into the tmp directory. My code for copying the bundle into tmp is:

// Clear tmp directory
NSArray* temporaryDirectory = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:NSTemporaryDirectory() error:NULL];
for (NSString *file in temporaryDirectory) {
    [[NSFileManager defaultManager] removeItemAtPath:[NSString stringWithFormat:@"%@%@", NSTemporaryDirectory(), file] error:NULL];
}

NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *sourcePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"build"];
NSString *temporaryPath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"build"];
NSError *error = nil;

// Copy directory
if(![fileManager copyItemAtPath:sourcePath toPath:temporaryPath error:&error]) {
    [self logError:@"Could not copy directory" :error];
}

I have timed this particular fragment of code, and it takes up 50% of my app's startup time! (~0.5s of a total of ~1s.)

Is there a way I can accelerate (or avoid completely) this particular fragment of code under iOS 8? Could threading help?

like image 800
Randomblue Avatar asked Apr 18 '15 23:04

Randomblue


People also ask

Is WKWebView deprecated?

Apple is replacing UIWebView (and WebView) with WKWebView, an updated version, as UIWebView has been deprecated.

What is WKWebView in Swift?

A WKWebView object is a platform-native view that you use to incorporate web content seamlessly into your app's UI. A web view supports a full web-browsing experience, and presents HTML, CSS, and JavaScript content alongside your app's native views.

How do I migrate from UIWebView to WKWebView?

Step-1: Firstly, create a new project with a Single View App. Step-2: Then, open “Main. storyboard” and on your ViewController's view drag “Webkit View”. Select a WKWebView and place it on your view of a view controller.


2 Answers

Given that your assets are large enough to take 0.5s to get copied, my suggestion would be to take a slightly different approach to quicken the (apparent) startup time:

  1. Create a plain old HTML page with placeholder divs for loading the assets (if you like, you can include small assets (e.g. load indicator images) in base64)
  2. Once that HTML loads (maybe on webView:didFinishNavigation:), start the copy to the build folder
  3. Once the copy completes, populate the placeholder divs with your assets using javascript (with evaluateJavaScript:completionHandler:)

While I realize this doesn't directly address the question, I think a slight change of approach would be best in this case.

like image 85
roop Avatar answered Sep 21 '22 13:09

roop


NOTE: This technique works well with UIKit's UIWebView and AppKit's WebView, but does not work for the new WKWebView, which appears to ignore the URL loading system. See comments.

Use NSURLProtocol to handle local file reads as though they were remote requests. For a full example, see the one in PandoraBoy called ResourceURLProtocol. I'll walk through a slightly simplified version of it here. We'll read http://.RESOURCE./path/to/file as if it were <resources>/path/to/file.

Every NSURLProtocol will be asked if it can handle every request that comes up in the system. It need to answer whether it can in +canInitWithRequest:. We'll say if the host is .RESOURCE., then we can handle it. .RESOURCE. is an illegal DNS name, so it can't conflict with any real host (but it's a legal hostname for URL-purposes).

NSString *ResourceHost = @".RESOURCE.";

+ (BOOL)canInitWithRequest:(NSURLRequest *)request {
    return ( [[[request URL] scheme] isEqualToString:@"http"] &&
             [[[request URL] host] isEqualToString:ResourceHost] );
}

Then we need a couple of bookkeeping methods. Nothing much to see here.

+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
    return request;
}

-(void)stopLoading {
    return;
}

Now we get to the meat of it. startLoading is where you're going to do whatever you want to do with the request.

-(void)startLoading {
    NSBundle *thisBundle = [NSBundle bundleForClass:[self class]];
    NSString *notifierPath = [[thisBundle resourcePath] stringByAppendingPathComponent:[[[self request] URL] path]];
    NSError *err;
    NSData *data = [NSData dataWithContentsOfFile:notifierPath
                                          options:NSUncachedRead // Assuming you only need to read this once
                                            error:&err];
    if( data != nil ) {
        // Assuming you're only reading HTML. 
        // If you need other things, you'll need to work out the correct MIME type
        NSURLResponse *response = [[NSURLResponse alloc] initWithURL:[[self request] URL]
                                                            MIMEType:@"text/html"
                                               expectedContentLength:[data length]
                                                    textEncodingName:nil];

        // And we just pass it to the caller
        [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageAllowed];
        [[self client] URLProtocol:self didLoadData:data];
        [[self client] URLProtocolDidFinishLoading:self];
    } else {
        NSLog(@"BUG:Unable to load resource:%@:%@", notifierPath, [err description]);
        [[self client] URLProtocol:self didFailWithError:err];
    }
}

I also find a little ResourceURL wrapper helpful:

@implementation ResourceURL
+ (ResourceURL*) resourceURLWithPath:(NSString *)path {
    return [[[NSURL alloc] initWithScheme:@"http"
                                     host:ResourceHost
                                     path:path] autorelease];
}    
@end

To use it, you just need to first register your protocol handler:

[NSURLProtocol registerClass:[ResourceURLProtocol class]];

Then you can create a "resource URL" and load it:

ResourceURL *resource = [ResourceURL resourceURLWithPath:...];
[webView loadRequest:[NSURLRequest requestWithURL:resource]];

For more details on NSURLProtocol, as well as a more complicated caching example, see Drop-in Offline Caching for UIWebView (and NSURLProtocol).

PandoraBoy is full of NSURLProtocol examples (look for all the classes with Protocol in their names). You can hijack, spy on, redirect, or manipulate just about anything coming through the URL loading system this way.

like image 22
Rob Napier Avatar answered Sep 21 '22 13:09

Rob Napier