iOSで通信を監視しよう

# 通信を管理する仕組み

iOSでの通信の管理にはNSURLSessionとNSURLProtocolを使おうという話。

アプリ内の通信をすべて共通の仕組みでデバッグして、APIへのリクエスト・webviewでのリクエスト・・・すべての通信を監視してユーザーの体験を変えたりすることもできますよって話。

## [NSURLProtocol](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSURLProtocol_Class/)

```
An NSURLProtocol object handles the loading of protocol-specific URL data. The NSURLProtocol class itself is an abstract class that provides the infrastructure for processing URLs with a specific URL scheme. You create subclasses for any custom protocols or URL schemes that your app supports.
```

何をするのかというとアプリ内にプロトコルを新しく定義してアプリケーション全体の通信をフックして処理をしたりする。webviewアプリでは**特定ドメインへのアクセスを制限**したり、**クエリによってコンテンツを切り替えたり**という時に便利。

### 実装

今回は自分の使っているパケットキャプチャの実装の一部を見てみる。

```swift
//CaptureProtocol.swift
class CaptureProtocol: NSURLProtocol ,NSURLSessionDelegate, NSURLSessionDataDelegate{
    
    
    override class func canInitWithRequest(request: NSURLRequest) -> Bool {
        // ここでtrueを返した場合、そのリクエストをこのプロトコルで行う
        return CaptureManager.sharedInstance.isNeedCheckRequest(request)
    }
    
    override class func canonicalRequestForRequest (request: NSURLRequest) -> NSURLRequest {
        return request;
    }
    
    override func startLoading() {
        
        // リクエストのタスクはこのプロトコルに移るのでここでリクエストを投げる
        let session:NSURLSession = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration(), delegate: self, delegateQueue: nil)

        session.dataTaskWithRequest(request) { (data, response, error) -> Void in
            if error != nil{
                self.client?.URLProtocol(self, didFailWithError: error!)
                return
            }
            
            // クライアントに渡すところも実装してあげないとリダイレクトをしくじることがある
            self.client?.URLProtocol(self, didReceiveResponse: response!, cacheStoragePolicy: NSURLCacheStoragePolicy.Allowed)
            self.client?.URLProtocol(self, didLoadData: data!)
            self.client?.URLProtocolDidFinishLoading(self)
        }.resume()
    }
    
    override func stopLoading() {}
    
    
    func URLSession(session: NSURLSession, task: NSURLSessionTask, willPerformHTTPRedirection response: NSHTTPURLResponse, newRequest request: NSURLRequest, completionHandler: (NSURLRequest?) -> Void) {
        
        self.client?.URLProtocol(self, wasRedirectedToRequest: request, redirectResponse: response)
        
    }
    
    
    func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
        self.client?.URLProtocol(self, didLoadData: data)
    }
    
    func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveResponse response: NSURLResponse, completionHandler: (NSURLSessionResponseDisposition) -> Void) {
        self.client?.URLProtocol(self, didReceiveResponse: response, cacheStoragePolicy: NSURLCacheStoragePolicy.Allowed)
    }
    
    func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
        if error != nil{
            self.client?.URLProtocol(self, didFailWithError: error!)
        }else{
            self.client?.URLProtocolDidFinishLoading(self)
        }
    }
}

```

上記のプロトコルに処理させるための実装

```swift
// 有効化
NSURLProtocol.registerClass(CaptureProtocol)

// NSURLSessionから利用
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
configuration.protocolClasses = [CaptureProtocol.self]
```

この実装さえしておけばこのコンフィギュレーションを使ってNSURLSessionからリクエストを送れば監視できる。

ちょっと無理やり感もあるが、webviewでのリクエストをフックしたいというときにいちいちdelegateにいろいろ書いていると親WebViewに手を加えなければいけなかったり、もともと使っている実装には非常に面倒だ。
このやり方の場合、**NSURLProtocolを継承したクラスを作って、どっかで有効化してやればいいので既存のコードは汚さないでいい。**

使いどこは選ぶが、使いようによっては便利だと思う。