September 24, 2015

Custom NSURLProtocol for iOS App Development

NSURLProtocol is a class from the Foundation framework which handles all of the requests made by your iOS app. Even if you don’t create a custom class by extending NSURLProtocol, the OS will use a default one. When making your app, if you don’t create either instances of NSURLProtocol or of your custom class that extends NSURLProtocol, the OS will do this for you. When you extend NSURLProtocol, you have to override some methods where you put your code, which we’ll outline in the article below.

Why do you want to create a class that extends NSURLProtocol?

Well, suppose you want all of your app requests to have a certain header in them, or that you want to intercept some requests before they are sent, or that you want to have a more elaborate cache system than the one that is provided to you by the frameworks within the SDK. By extending NSURLProtocol, you open up a multitude of possibilities to improve your app.

In this post, we will outline how to make our own class that extends NSURLProtocol, allowing it to intercept all requests and save the content locally to use when there is no internet connection.

When extending NSURLProtocol, you have to implement a couple of methods in order to make the new class work.

override class func canInitWithRequest(request: NSURLRequest) -> Bool

This method tells the OS that you will handle the execution of the request yourself. The reason for this is because you can have multiple protocol classes registered in your app. The OS will take all of the registered NSURLProtocol classes and call canInitWithRequest. The first request that returns with “true” will be used to make an instance of that class, and will be used for that particular request.

The next method that you want to override is:

override class func canonicalRequestForRequest(request: NSURLRequest) -> NSURLRequest

The true meaning of canonical request in relation to your protocol class is up to you to decide. For our example, I returned the request that I received as a parameter.

Next we will override:

override func startLoading()

In this method, you will add your code to handle the request. Here is where you want to set that key for the request that you handle, which can be done with:

NSURLProtocol.setProperty("true", forKey: "myCustomKey", inRequest: request)

You want to set this property because in the method callInitWithRequest, you will not always receive a “true.” If you did, you would have an infinite loop. Instead, here in startLoading, we will tell the OS that we will handle this particular request ourselves, so that it does not create another instance of NSURLProtocol for this request.

The last method you need to override is this:

override func stopLoading()

Now, let’s get to work.

Create a new single view application in Xcode: File -> New Project, then select “single view.” For this project, I used Swift. In the view controller form the main storyboard, I added a UIWebView, a UItextField, and two buttons to move backward and forward. If you don’t want to create this project step-by-step, you can download the completed version here.

In the ViewController.swift file, we set the delegate from the web view and from the text field.

func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
  return true

func webViewDidStartLoad(webView: UIWebView) {
  UIApplication.sharedApplication().networkActivityIndicatorVisible = true
func webViewDidFinishLoad(webView: UIWebView) {
  UIApplication.sharedApplication().networkActivityIndicatorVisible = false
func webView(webView: UIWebView, didFailLoadWithError error: NSError) {
  if error.code != NSURLErrorCancelled {
    UIApplication.sharedApplication().networkActivityIndicatorVisible = false
    let alert = UIAlertView(title: "Error", message: 
       error.localizedDescription, delegate: nil, cancelButtonTitle: "OK")

In the delegate for the text field, we are trying to load the URL entered by the user.

func textFieldShouldReturn(textField: UITextField) -> Bool {
  let url = NSURL(string:textField.text)
  if let url = url {
    let request = NSURLRequest(URL: url)
  return true

The buttons in the UI each have one method for going backward and forward, if possible.

@IBAction func onBackTouchUpInside(sender: UIButton) {
  if self.gWebView.canGoBack {
@IBAction func onForwardTouchUpInside(sender: UIButton) {
  if self.gWebView.canGoForward {

To detect if we have an internet connection, I used AFNetworking. This is a great library for working with requests; in this post, I used only its reachability portion. We use the following code to make the text field background red to indicate when there is no internet connection.

AFNetworkReachabilityManager.sharedManager().setReachabilityStatusChangeBlock { (status:AFNetworkReachabilityStatus) -> Void in
  var color:UIColor
  if status == AFNetworkReachabilityStatus.ReachableViaWiFi || 
     status == AFNetworkReachabilityStatus.ReachableViaWWAN {
    color = UIColor.clearColor()
  } else {
    color = UIColor(red: 1, green: 0, blue: 0, alpha: 0.2)
  self.txtAddress.backgroundColor = color

AFNetworking is written in Objective-C, with which Swift can work. To do this, drag and drop the AFNetworkReachabilityManager.h and AFNetworkReachabilityManager.m files into your project. Xcode will prompt you to make a bridging file, Bridging-Header.h which you should agree to by clicking “yes.” In this file, import any Objective-C files you want to use in Swift, such as #import "AFNetworkReachabilityManager.h". Now you can use AFNetworkReachabilityManager in Swift as well.

Next, we will make a class that extends NSURLProtocol and we will override the methods mentioned above. To do this, we want to handle the request in the startLoading method from our custom URLProtocol class. In case we have an internet connection, we ill set our key and execute the request using NSURLSession and NSURLSessionDataTask. The code will look like this:

override func startLoading() {
  if AFNetworkReachabilityManager.sharedManager().reachable {
    var newRequest = self.request.mutableCopy() as! NSMutableURLRequest
    NSURLProtocol.setProperty("true", forKey: "myCustomKey", inRequest: newRequest)
    let defaultConfigObj = NSURLSessionConfiguration.defaultSessionConfiguration()
    let defaultSession = NSURLSession(configuration: defaultConfigObj, delegate: self, delegateQueue: nil)        
    self.dataTask = defaultSession.dataTaskWithRequest(newRequest)

If we do not have an internet connection, then we will load the response from local storage (if there is one). If no response is available, then we will create a failed response and pass it along to the client property from NSURLProtocol.

let httpVersion = "1.1"
if let localResponse = cachedResponseForCurrentRequest(), data = {
  var headerFields:[NSObject : AnyObject] = [:]
  headerFields["Content-Length"] = NSString(format:"%d", data.length)
  if let mimeType = localResponse.mimeType {
    headerFields["Content-Type"] = mimeType
  headerFields["Content-Encoding"] = localResponse.encoding!
  let okResponse = NSHTTPURLResponse(URL: self.request.URL!, statusCode: 200, HTTPVersion: httpVersion, headerFields: headerFields)
  self.client?.URLProtocol(self, didReceiveResponse: okResponse!, cacheStoragePolicy: .NotAllowed)
  self.client?.URLProtocol(self, didLoadData: data)
} else {
  let failedResponse = NSHTTPURLResponse(URL: self.request.URL!, statusCode: 0, HTTPVersion: httpVersion, headerFields: nil)
  self.client?.URLProtocol(self, didReceiveResponse: failedResponse!, cacheStoragePolicy: .NotAllowed)

In this, we have called our methods from the client the property of NSURLProtocol. This is very important because we want to pass the data and execution flow to the OS and to other objects, like a web view or a connection, that made the actual request.

Next, we want to implement the methods from the NSURLSessionDataDelegate and NSURLSessionTaskDelegate protocols:

func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask,
       didReceiveResponse response: NSURLResponse,
        completionHandler: (NSURLSessionResponseDisposition) -> Void) {
  self.client?.URLProtocol(self, didReceiveResponse: response, cacheStoragePolicy: .NotAllowed)
  self.urlResponse = response
  self.receivedData = NSMutableData()
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
  self.client?.URLProtocol(self, didLoadData: data)
// MARK: NSURLSessionTaskDelegate
func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
  if error != nil && error!.code != NSURLErrorCancelled {
    self.client?.URLProtocol(self, didFailWithError: error!)
  } else {

For local storage, we will use an alternative to CoreData and SQLite altogether called Realm. I chose this approach because everything in the custom URL protocol class is on a background thread. This means that the UI will not suffer from any delay in responsiveness, and therefore user experience will not suffer.

In the saveCachedResponse method, we verify if there is a local response for the current request using self.request.URL!.absoluteString!. If we find a local response for the current request URL, then we will continue updating that response. If there is no response found, then we will create a new one. This approach will optimize the records stored locally by removing redundancy.

Now that the custom URL protocol class is completed, we need to register it. We do this in the first line from the method:

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool

by calling the registerClass method from NSURLProtocol:


Before we run the app, we need to tell the AFNetworkReachabilityManager that it should start monitoring for a network status change. I did this on the method:

func applicationDidBecomeActive(application: UIApplication)

by calling the startMonitoring method:


Now we can run the app and navigate to some pages. After doing this, we can cut off the internet connection, which should still allow us to navigate to the previous pages we visited.

This is not the only possibility in this example. We can also implement a cache for online cases by taking into account the time stamps from local response. The CachedResponse class is very simple:

import Foundation
import Realm

class CachedResponse: RLMObject {
  dynamic var data:NSData?
  dynamic var encoding:String?
  dynamic var mimeType:NSString?
  dynamic var url:String?
  dynamic var timestamp:NSDate?
  override init!() {
    data        = NSData()
    encoding    = "utf-8"
    mimeType    = ""
    url         = ""
    timestamp   = NSDate()

And there it is. You have successfully implemented a custom NSURLProtocol. The complete project can be downloaded in full here.

This project was created with Xcode 6.4 and Swift 1.2. Apple recently released the new iOS 9 and updated the development tool to Xcode 7. To complete this post with Xcode 7 and Swift 2.0, follow this link for a .zip download.

This post was originally published on the author’s personal site, which can be found here.