Quantcast
Channel: Kodeco | High quality programming tutorials: iOS, Android, Swift, Kotlin, Unity, and more
Viewing all articles
Browse latest Browse all 4370

Using NSURLProtocol with Swift

$
0
0
NSURLProtocol Tutorial

Learn how to harness the power of NSURLProtocol!

Update note: This tutorial was updated for iOS 8 and Swift by Zouhair Mahieddine, checked against Xcode 6 beta 7. Original tutorial by Rocir Santiago.

NSURLProtocol is like a magic key to the URL. It lets you redefine how Apple’s URL Loading System operates, by defining custom URL schemes and redefining the behavior of existing URL schemes.

Does that sound magical? It should. Because, if you look for it, I’ve got a sneaky feeling you’ll find URLs — much like love — are all around us. What do UIWebView and WKWebView use? URLs. What’s used for video streaming with MPMoviePlayer? URLs. How do you send someone to your app on iTunes, initiate FaceTime or Skype, launch another app on the system, or even embed an image in an HTML file? With URLs. Have a peek at NSFileManager and notice how many of its file-manipulation methods require and return — URLs.

In this NSURLProtocol tutorial, you’ll learn how to define a protocol handler that modifies URL schemes. It will add a rough and ready transparent caching layer, by storing retrieved resources in Core Data. By enabling it, an ordinary UIWebView can then take on the role of a browser by caching downloaded pages for offline viewing at a later time.

Before you dive in head first, you’ll need a basic understanding of networking concepts and familiarity with how NSURLConnection works. If you are not currently familiar with NSURLConnection then I suggest reading this tutorial and/or this document by Apple.

So are you ready to learn what you can do with NSURLProtocol? Good, go pour yourself a cuppa something and settle in for a meaty, mind-broadening discussion and step-by step exercise.

Getting Started

For this tutorial’s project, you’ll build an elementary mobile web browser, such as one that you might add to your next app. It will have a basic user interface that lets the user enter and go to a URL. The twist is that your browser will cache successfully retrieved results. This way the user can load pages already visited in the blink of an eye, because the page won’t load from a network request, but from the app’s local cache.

You already know that fast page loads == happy users, so this is a good example of how NSURLProtocol can improve your app’s performances.

These are the steps you’re going to go through:

  • Use a UIWebView for displaying the websites
  • Use Core Data for caching the results.

If you’re not familiar with Core Data, you can take a look into our tutorial. However, the code in this tutorial should be enough to understand the possibilities of NSURLProtocol. Using Core Data is just a simple way to implement the local cache, so it’s not essential to learn something useful here.

Starter Project Overview

You can download the starter project here. As soon as the download is finished, unzip it and open the project file.

When you open the project, there are two main files. The first one is the Main.storyboard file. It has the UIViewController set up the way you need for implementation. Notice the UITextField (for URL input), UIButton (for firing the web requests) and UIWebView.

Open BrowserViewController.swift. Here you’ll see the basic behavior set up for the UI components. This UIViewController implements the UITextFieldDelegate protocol, so you can fire the request when the user taps the return key. The IBAction for the button is pre-set to behave the same way as the return key. Last, the sendRequest() method just takes the text from the textfield, creates a NSURLRequest object and calls the loadRequest(_:) method from UIWebView to load it.

Once you’re familiarized with the app, build and run! When the app opens, enter “http://raywenderlich.com” and press the “Go” button. The UIWebView will load the response and display the results in the app. Pretty simple for a starting point. Now it’s time for you to stretch those finger muscles. Up next….coding!

Intercepting network requests

A set of classes known as the URL Loading System handles URL requests on iOS. At the heart of the URL Loading System is the NSURL class. For network requests, this class tells what host your app is trying to reach and path to the resource at that host. In addition the NSURLRequest object adds information like HTTP headers, the body of your message, etc.. The loading system provides a few different classes you can use to process the request, the most common being NSURLConnection and NSURLSession.

Now it’s time to start intercepting all NSURLRequest’s fired by the app. For that, you’ll need to create your own NSURLProtocol implementation.

Click File\New\File…. Select iOS\Source\Cocoa Touch Class and hit the Next button. In the Class field, enter MyURLProtocol and in the Subclass of field, enter NSURLProtocol. Check that the language is set to Swift. Finally, press Next and then Create when the dialog appears.

Open MyURLProtocol.swift and replace its content with the following:

import UIKit
 
var requestCount = 0
 
class MyURLProtocol: NSURLProtocol {
  override class func canInitWithRequest(request: NSURLRequest) -> Bool {
    println("Request #\(requestCount++): URL = \(request.URL.absoluteString)")
    return false
  } 
}

Every time the URL Loading System receives a request to load a URL, it searches for a registered protocol handler to handle the request. Each handler tells the system whether it can handle a given request via its canInitWithRequest(_:) method.

The parameter to this method is the request that the protocol is being asked if it can handle. If the method returns true, then the loading system will rely on this NSURLProtocol subclass to handle the request, and ignore all other handlers.

If none of the custom registered handlers can handle the request, then the URL Loading System will handle it by itself, using the system’s default behavior.

If you want to implement a new protocol, like foo://, then this is where you should check to see if the request’s URL scheme was foo. But in the example above, you’re simply returning false, which tells you your app cannot handle the request. Just hold on a minute, you’ll start handling them soon!

Note: NSURLProtocol is meant to be an abstract class. You create subclasses with the custom behavior for a URL protocol, but you never instantiate NSURLProtocol directly.

Open AppDelegate.swift and replace the application(_:didFinishLaunchingWithOptions:) method with this one:

func application(application: UIApplication,
                 didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool {
    NSURLProtocol.registerClass(MyURLProtocol)
    return true
}

Now when your app launches, it will register the protocol with the URL Loading System. That means it will have the opportunity to handle every request delivered to the URL Loading system. This includes code which calls the loading system directly, as well as many system components that rely on the URL loading framework, such as UIWebView.

Build and run the project. Insert http://raywenderlich.com as the website, tap on Go and check the Xcode console. Now, for every request the app needs to perform, the URL Loading System asks your class if it can handle it.

In the console you should see something like this:

Request #0: URL = http://raywenderlich.com/
Request #1: URL = http://raywenderlich.com/
Request #2: URL = http://raywenderlich.com/
Request #3: URL = http://raywenderlich.com/
Request #4: URL = http://raywenderlich.com/
Request #5: URL = http://raywenderlich.com/
Request #6: URL = http://www.raywenderlich.com/
Request #7: URL = http://www.raywenderlich.com/
Request #8: URL = http://www.raywenderlich.com/
Request #9: URL = http://www.raywenderlich.com/
Request #10: URL = http://www.raywenderlich.com/
Request #11: URL = http://www.raywenderlich.com/
Request #12: URL = http://raywenderlich.com/
Request #13: URL = http://cdn3.raywenderlich.com/wp-content/themes/raywenderlich/style.min.css?ver=1402962842
Request #14: URL = http://cdn3.raywenderlich.com/wp-content/plugins/swiftype-search/assets/autocomplete.css?ver=3.9.1
Request #15: URL = http://cdn4.raywenderlich.com/wp-content/plugins/videojs-html5-video-player-for-wordpress/plugin-styles.css?ver=3.9.1
Request #16: URL = http://vjs.zencdn.net/4.5/video-js.css?ver=3.9.1
Request #17: URL = http://cdn3.raywenderlich.com/wp-content/themes/raywenderlich/style.min.css?ver=1402962842
...

For now, your class is just logging the string representation of the request’s URL and returning false, which means your custom class cannot handle the request. But if you look into the logs, you’ll see all the requests made from the UIWebView. It includes the main website (.html) and all the assets, such as JPEGs and CSS files. Every time the UIWebView needs to fire a request, it’s logged to the console before it’s actually fired. The count should show you a mountain of requests — likely over five hundred — because of all the assets on the web page.

So this is your way in: your custom class is being notified for every URL request, and next you can do something about each request!

Custom URL Loading

“I love it when pages take forever to load” said no user, ever. So now you need to make sure your app can actually handle the requests. As soon as you return true in canInitWithRequest(_:), it’s entirely your class’s responsibility to handle everything about that request. This means you need to get the requested data and provide it back to the URL Loading System.

How do you get the data?

If you’re implementing a new application networking protocol from scratch (e.g. adding a foo:// protocol), then here is where you embrace the harsh joys of application network protocol implementation. But since your goal is just to insert a custom caching layer, you can just get the data by using NSURLConnection.

Effectively you’re just going to intercept the request and then pass it back off to the standard URL Loading System through using NSURLConnection.

Your custom NSURLProtocol subclass returns data through an object that implements the NSURLProtocolClient protocol. There’s a bit of confusing naming to keep straight in your head: NSURLProtocol is a class, and NSURLProtocolClient is a protocol!

Through the client, you communicate to the URL Loading System to pass back state changes, responses and data.

Open MyURLProtocol.swift and add the following property at the top of the MyURLProtocol class definition:

var connection: NSURLConnection!

Next, find canInitWithRequest(_:). Change the return line to return true:

return true

Now add four more methods:

override class func canonicalRequestForRequest(request: NSURLRequest) -> NSURLRequest {
    return request
}
 
override class func requestIsCacheEquivalent(aRequest: NSURLRequest,
                                   toRequest bRequest: NSURLRequest) -> Bool {
    return super.requestIsCacheEquivalent(aRequest, toRequest:bRequest)
}
 
override func startLoading() {
    self.connection = NSURLConnection(request: self.request, delegate: self)
}
 
override func stopLoading() {
    if self.connection != nil {
        self.connection.cancel()
    }
    self.connection = nil
}

It’s up to your protocol to define what a “canonical request” means, but at a minimum it should return the same canonical request for the same input request. So if two semantically equal (i.e. not necessarily ===) are input to this method, the output requests should also be semantically equal. For example, if your custom URL scheme is case insensitive then you might decide that canonical URLs are all lower case.

To meet this bare minimum, just return the request itself. Usually, this is a reliable go-to solution, because you usually don’t want to change the request. After all, you trust the developer, right?! An example of something you might do here is to change the request by adding a header and return the new request.

requestIsCacheEquivalent(_:toRequest:) is where you could take the time to define when two distinct requests of a custom URL scheme (i.e foo://) are equal, in terms of cache-ability. If two requests are equal, then they should use the same cached data. This concerns URL Loading System’s own, built-in caching system, which you’re ignoring for this tutorial. So for this exercise, just rely on the default superclass implementation.

The loading system uses startLoading() and stopLoading() to tell your NSURLProtocol to start and stop handling a request. Your start implementation sets up the NSURLConnection instance to load the data. The stop method exists so that URL loading can be cancelled. This is handled in the above example by cancelling the current connection and getting rid of it.

Woo-hoo! You’ve implemented the interface required of a valid NSURLProtocol instance. Checkout the official documentation describing what methods a valid NSURLProtocol subclass can implement, if you want to read more.

But your coding isn’t done yet! You still need to do the actual work of processing the request, which you do by handling the delegate callbacks from the NSURLConnection you created.

Open MyURLProtocol.swift and add the following methods:

func connection(connection: NSURLConnection!, didReceiveResponse response: NSURLResponse!) {
    self.client!.URLProtocol(self, didReceiveResponse: response, cacheStoragePolicy: .NotAllowed)
}
 
func connection(connection: NSURLConnection!, didReceiveData data: NSData!) {
    self.client!.URLProtocol(self, didLoadData: data)
}
 
func connectionDidFinishLoading(connection: NSURLConnection!) {
    self.client!.URLProtocolDidFinishLoading(self)
}
 
func connection(connection: NSURLConnection!, didFailWithError error: NSError!) {
    self.client!.URLProtocol(self, didFailWithError: error)
}

These are all NSURLConnection delegate methods. They are called when the NSURLConnection instance you’re using to load the data has a response, when it has data, when it finishes loading and when it fails. In each of these cases, you’re going to need to hand this information off to the client.

So to recap, your MyURLProtocol handler creates its own NSURLConnection and asks that connection to process the request. In the NSURLConnection delegate callbacks methods above, the protocol handler is relaying messages from the connection back to the URL Loading System. These messages talk about loading progress, completion, and errors.

Look and you’ll see the close family resemblance in message signatures for the NSURLConnectionDelegate and the NSURLProtocolClient — they are both APIs for asynchronous data loading. Also notice how MyURLProtocol uses its client property to send messages back to the URL Loading system.

Build and run the project. When the app opens, enter the same URL and hit Go.

Uh-oh! Your browser isn’t loading anything anymore! If you look at the Debug Navigator while it’s running, you’ll see memory usage is out of control. The console log should show a racing scroll of innumerable requests for the same URL. What could be wrong?

In the console you should see lines being logged forever and ever like this:

Request #0: URL = http://raywenderlich.com/
Request #1: URL = http://raywenderlich.com/
Request #2: URL = http://raywenderlich.com/
Request #3: URL = http://raywenderlich.com/
Request #4: URL = http://raywenderlich.com/
Request #5: URL = http://raywenderlich.com/
Request #6: URL = http://raywenderlich.com/
Request #7: URL = http://raywenderlich.com/
Request #8: URL = http://raywenderlich.com/
Request #9: URL = http://raywenderlich.com/
Request #10: URL = http://raywenderlich.com/
...
Request #1000: URL = http://raywenderlich.com/
Request #1001: URL = http://raywenderlich.com/
...

You’ll need to return to Xcode and stop the app from there before diving into the problem.

Squashing the Infinite Loop with Tags

Think again about the URL Loading System and protocol registration, and you might have a notion about why this is happening. When the UIWebView wants to load the URL, the URL Loading System asks MyURLProtocol if it can handle that specific request. Your class says true, it can handle it.

So the URL Loading System will create an instance of your protocol and call startLoading. Your implementation then creates and fires its NSURLConnection. But this also calls the URL Loading System. Guess what? Since you’re always returning true in the canInitWithRequest(_:) method, it creates another MyURLProtocol instance.

This new instance will lead to the creation of one more, and then one more and then an infinite number of instances. That’s why your app doesn’t load anything! It just keeps allocating more memory, and shows only one URL in the console. The poor browser is stuck in an infinite loop! Your users could be frustrated to the point of inflicting damage on their devices.

Obviously you can’t just always return true in the canInitWithRequest(_:) method. You need to have some sort of control to tell the URL Loading System to handle that request only once. The solution is in the NSURLProtocol interface. Look for the class method called setProperty(_:forKey:inRequest:) that allows you to add custom properties to a given URL request. This way, you can ‘tag’ it by attaching a property to it, and the browser will know if it’s already seen it before.

So here’s how you break the browser out of infinite instance insanity. Open MyURLProtocol.swift. Then change the startLoading() and the canInitWithRequest(_:) methods as follows:

override class func canInitWithRequest(request: NSURLRequest!) -> Bool {
    println("Request #\(requestCount++): URL = \(request.URL.absoluteString)")
 
    if NSURLProtocol.propertyForKey("MyURLProtocolHandledKey", inRequest: request) != nil {
      return false
    }
 
    return true
}
 
override func startLoading() {
    var newRequest = self.request.copy() as NSMutableURLRequest
    NSURLProtocol.setProperty(true, forKey: "MyURLProtocolHandledKey", inRequest: newRequest)
 
    self.connection = NSURLConnection(request: newRequest, delegate: self)
}

Now startLoading() sets the property associated with the key "MyURLProtocolHandledKey" to true for a given request. It means the next time it calls canInitWithRequest(_:) for a given NSURLRequest instance, the protocol can ask if this same property is set.

If it is set, and it’s set to true, then it means that you don’t need to handle that request anymore. The URL Loading System will load the data from the web. Since your MyURLProtocol instance is the delegate for that request, it will receive the callbacks from NSURLConnectionDelegate.

Build and run. When you try it now, the app will successfully display web pages in your web view. Sweet victory! The console should now look something like this:

Request #0: URL = http://raywenderlich.com/
Request #1: URL = http://raywenderlich.com/
Request #2: URL = http://raywenderlich.com/
Request #3: URL = http://raywenderlich.com/
Request #4: URL = http://raywenderlich.com/
Request #5: URL = http://raywenderlich.com/
Request #6: URL = http://raywenderlich.com/
Request #7: URL = http://raywenderlich.com/
Request #8: URL = http://raywenderlich.com/
Request #9: URL = http://www.raywenderlich.com/
Request #10: URL = http://www.raywenderlich.com/
Request #11: URL = http://www.raywenderlich.com/
Request #12: URL = http://raywenderlich.com/
Request #13: URL = http://cdn3.raywenderlich.com/wp-content/themes/raywenderlich/style.min.css?ver=1402962842
Request #14: URL = http://cdn3.raywenderlich.com/wp-content/plugins/swiftype-search/assets/autocomplete.css?ver=3.9.1
Request #15: URL = http://cdn4.raywenderlich.com/wp-content/pluginRse/qvidueeosjts -#h1t6m:l URL = ht5t-pv:i/d/ecodn3.raywenderlich.com/-wppl-acyoenrtent/themes/raywenderlich/-sftoyrl-ew.omridnp.css?vreers=s1/4p0l2u9g6i2n8-4s2t
yles.css?ver=3.9.1
Request #17: URL = http://cdn3.raywenderlich.com/wp-content/themes/raywenderlich/style.min.css?ver=1402962842
Request #18: URL = http://vjs.zencdn.net/4.5/video-js.css?ver=3.9.1
Request #19: URL = http://www.raywenderlich.com/wp-content/plugins/wp-polls/polls-css.css?ver=2.63
Request #20: URL = http://cdn3.raywenderlich.com/wp-content/plugins/swiftype-search/assets/autocomplete.css?ver=3.9.1
Request #21: URL = http://cdn3.raywenderlich.com/wp-content/plugins/swiftype-search/assets/autocomplete.css?ver=3.9.1
Request #22: URL = http://cdn4.raywenderlich.com/wp-content/plugins/powerpress/player.min.js?ver=3.9.1
Request #23: URL = http://cdn4.raywenderlich.com/wp-content/plugins/videojs-html5-video-player-for-wordpress/plugin-styles.css?ver=3.9.1
Request #24: URL = http://cdn4.raywenderlich.com/wp-content/plugins/videojs-html5-video-player-for-wordpress/plugin-styles.css?ver=3.9.1
Request #25: URL = http://cdn3.raywenderlich.com/wp-content/themes/raywenderlich/style.min.css?ver=1402962842
Request #26: URL = http://cdn3.raywenderlich.com/wp-content/plugins/swiftype-search/assets/autocomplete.css?ver=3.9.1
...

You might be wondering why you did all of this just to get the app to behave just like it was when you started. Well, because you need to prepare for the fun part! Now you have all the control of the URL data of your app and you can do whatever you want with it. It’s time to start caching your app’s URL data.

Implementing the Local Cache

Remember the basic requirement for this app: for a given request, it should load the data from the web just once, and then cache it. If the same request is fired again in the future, your protocol will serve the cached response without reloading it from the web.

Note: The starter project already includes a basic Core Data model and stack. You don’t need to know the details of Core Data and can just think of it as an opaque data store; if you’re interested, check out Apple’s Core Data Programming Guide.

It’s time to save the responses your app receives from the web, and retrieve them whenever it has matching cached data. Open MyURLProtocol.swift and add the following import to the top of the file:

import CoreData

Next, add two properties inside the class definition:

var mutableData: NSMutableData!
var response: NSURLResponse!

The response property will keep the reference to the metadata you’ll need when saving the response from a server. The mutableData property will be used to hold the data that the connection receives in the connection(_:didReceiveData:) delegate method. Whenever the connection finishes, you can cache the response (data and metadata).

Then add the following method to the class:

func saveCachedResponse () {
    println("Saving cached response")
 
    // 1
    let delegate = UIApplication.sharedApplication().delegate as AppDelegate
    let context = delegate.managedObjectContext!
 
    // 2
    let cachedResponse = NSEntityDescription.insertNewObjectForEntityForName("CachedURLResponse", inManagedObjectContext: context) as NSManagedObject
 
    cachedResponse.setValue(self.mutableData, forKey: "data")
    cachedResponse.setValue(self.request.URL.absoluteString, forKey: "url")
    cachedResponse.setValue(NSDate(), forKey: "timestamp")
    cachedResponse.setValue(self.response.MIMEType, forKey: "mimeType")
    cachedResponse.setValue(self.response.textEncodingName, forKey: "encoding")
 
    // 3
    var error: NSError?
    let success = context.save(&error)
    if !success {
        println("Could not cache the response")
    }
}

Here’s what this method does:

  1. Obtain the Core Data NSManagedObjectContext from the AppDelegate instance. The managed object context is your interface to Core Data.
  2. Create an instance of NSManagedObject to match the data model you saw in the .xcdatamodeld file. Set its properties based on the references to the NSURLResponse and NSMutableData that you kept.
  3. Save the Core Data managed object context.

Now that you have a way to store the data, you need to call this method from somewhere. Still in MyURLProtocol.swift, change the NSURLConnection delegate methods to the following implementations:

func connection(connection: NSURLConnection!, didReceiveResponse response: NSURLResponse!) {
    self.client!.URLProtocol(self, didReceiveResponse: response, cacheStoragePolicy: .NotAllowed)
 
    self.response = response
    self.mutableData = NSMutableData()
}
 
func connection(connection: NSURLConnection!, didReceiveData data: NSData!) {
    self.client!.URLProtocol(self, didLoadData: data)
    self.mutableData.appendData(data)
}
 
func connectionDidFinishLoading(connection: NSURLConnection!) {
    self.client!.URLProtocolDidFinishLoading(self)
    self.saveCachedResponse()
}

Instead of directly handing off to the client, the response and data are stored by your custom protocol class now.

Build and run. Nothing changes in the app’s behavior, but remember that now successfully retrieved responses from the web server save to your app’s local database.

Retrieving the Cached Response

Finally, now it’s time to retrieve cached responses and send them to the NSURLProtocol‘s client. Open MyURLProtocol.swift. Then add the following method:

func cachedResponseForCurrentRequest() -> NSManagedObject? {
    // 1
    let delegate = UIApplication.sharedApplication().delegate as AppDelegate
    let context = delegate.managedObjectContext!
 
    // 2
    let fetchRequest = NSFetchRequest()
    let entity = NSEntityDescription.entityForName("CachedURLResponse", inManagedObjectContext: context)
    fetchRequest.entity = entity
 
    // 3
    let predicate = NSPredicate(format:"url == %@", self.request.URL.absoluteString!)
    fetchRequest.predicate = predicate
 
    // 4
    var error: NSError?
    let possibleResult = context.executeFetchRequest(fetchRequest, error: &error) as Array<NSManagedObject>?
 
    // 5
    if let result = possibleResult {
        if !result.isEmpty {
            return result[0]
        }
    }
 
    return nil
}

Here’s what this does:

  1. Grab the Core Data managed object context, just like in saveCachedResponse().
  2. Create an NSFetchRequest saying that you want to find entities called CachedURLResponse. This is the entity in the managed object model that you want to retrieve.
  3. The predicate for the fetch request needs to obtain the CachedURLResponse object that relates to the URL that you’re trying to load. This code sets that up.
  4. Execute the fetch request is.
  5. If there are any results, return the first result.

Now it’s time to look back at the startLoading() implementation. Rather than just load everything from the web, it needs to check for a cached response for the URL first. Find the current implementation and replace it with the following:

override func startLoading() {
    // 1
    let possibleCachedResponse = self.cachedResponseForCurrentRequest()
    if let cachedResponse = possibleCachedResponse {
        println("Serving response from cache")
 
        // 2
        let data = cachedResponse.valueForKey("data") as NSData!
        let mimeType = cachedResponse.valueForKey("mimeType") as String!
        let encoding = cachedResponse.valueForKey("encoding") as String!
 
        // 3
        let response = NSURLResponse(URL: self.request.URL, MIMEType: mimeType, expectedContentLength: data.length, textEncodingName: encoding)
 
        // 4
        self.client!.URLProtocol(self, didReceiveResponse: response, cacheStoragePolicy: .NotAllowed)
        self.client!.URLProtocol(self, didLoadData: data)
        self.client!.URLProtocolDidFinishLoading(self)
    } else {
        // 5
        println("Serving response from NSURLConnection")
 
        var newRequest = self.request.copy() as NSMutableURLRequest
        NSURLProtocol.setProperty(true, forKey: "MyURLProtocolHandledKey", inRequest: newRequest)
        self.connection = NSURLConnection(request: newRequest, delegate: self)
    }
}

Here’s what that does:

  1. First, you need to find out if there’s a cached response for the current request.
  2. If there is, then pull all the relevant data out of the cached object.
  3. Create an NSURLResponse object from the saved data.
  4. Tell the client about the response and data. You set the client’s cache storage policy to .NotAllowed since you don’t want the client to do any caching of its own since that’s your job. Then you can call URLProtocolDidFinishLoading right away to signal that it has finished loading. No network calls – that’s it!
  5. If there was no cached response, then load the data as usual.

Build and run your project again. Browse a couple of web sites and then quit the app. Switch your device to Airplane mode (or, if using the iOS simulator, turn your computer’s Wi-Fi off / unplug the Ethernet cable) and run it again. Try to load any website you just loaded. It should load the pages from the cached data. Woo hoo! Rejoice! You did it!!!

You should see lots of entries in the console that look like this:

Request #22: URL = http://vjs.zencdn.net/4.5/video-js.css?ver=3.9.1
Serving response from cache

That’s the log saying that the response is coming from your cache!

And that’s that. Now your app successfully caches retrieved data and metadata from web page requests. Your users will enjoy faster page loads and superior performance! :]

When To Use NSURLProtocol?

How can you use NSURLProtocol to make your app cooler, faster, stronger and jaw-droppingly awesome? Here are a few examples:

Provide Custom Responses For Your Network Requests:

It doesn’t matter if you’re making a request using a UIWebView, NSURLConnection or even using a third-party library (like AFNetworking, MKNetworkKit, your own, etc, as these are all built on top of NSURLConnection). You can provide a custom response, both for metadata and for data. You might use this if you want to stub out the response of a request for testing purposes, for example.

Skip Network Activity and Provide Local Data:

Sometimes you may think it’s unnecessary to fire a network request to provide the app whatever data it needs. NSURLProtocol can set your app up to find data on local storage or in a local database.

Redirect Your Network Requests:

Have you ever wished you could redirect requests to a proxy server — without trusting the user to follow specific iOS setup directions? Well, you can! NSURLProtocol gives you what you want — control over requests. You can set up your app to intercept and redirect them to another server or proxy, or wherever you want to. Talk about control!!

Change the User-agent of Your Requests:

Before firing any network request, you can decide to change its metadata or data. For instance, you may want to change the user-agent. This could be useful if your server changes content based on the user-agent. An example of this would be differences between the content returned for mobile versus desktop, or the client’s language.

Use Your Own Networking Protocol:

You may have your own networking protocol (for instance, something built on top of UDP). You can implement it and, in your application, you still can can keep using any networking library you prefer.

Needless to say, the possibilities are many. It would be impractical (but not impossible) to list all the possibilities you have with NSURLProtocol in this tutorial. You can do anything you need with a given NSURLRequest before it’s fired by changing the designated NSURLResponse. Better yet, just create your own NSURLResponse. You’re the developer, after all.

While NSURLProtocol is powerful, remember that it’s not a networking library. It’s a tool you can use in addition to the library you already use. In short, you can take advantage of NSURLProtocol‘s benefits while you use your own library.

Where To Go From Here

Here is where you can download the final code for this tutorial.

This example covered a simple usage of NSURLProtocol, but don’t mistake it as a complete solution for caching. There is a lot more to implementing a production-quality caching browser. In fact, the loading system has built-in caching configurations, which are worth getting to know. The goal of this tutorial is simply to show you the possibilities. Because NSURLProtocol has access to the data going in and out of so many components, it’s very powerful! There are almost no limits to what you can do implementing the -startLoading method.

While IETF’s RFC 3986 may modestly define URLs as a “…compact sequence of characters that identifies an abstract or physical resource…” the truth is that the URL is its own mini language. It’s the domain-specific language (DSL) for naming and locating things. It’s probably the most pervasive domain-specific language in the world, considering that URLs have crawled out of the screen and are now broadcast in radio and TV advertisements, printed in magazines and splashed on shop signs all over the world.

NSURLProtocol is a language you can use in a myriad of ways. When Twitter wanted to implement the SPDY protocol on iOS, an optimized successor to HTTP 1.1, they did it with NSURLProtocol. What you use it for, is up to you. NSURLProtocol gives you power and flexibility at the same time requires a simple implementation to accomplish your goals.

Please, feel free to leave any questions or suggestions about this tutorial in our forum discussion. It’s right below!

Using NSURLProtocol with Swift is a post from: Ray Wenderlich

The post Using NSURLProtocol with Swift appeared first on Ray Wenderlich.


Viewing all articles
Browse latest Browse all 4370

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>