SwiftでNSURLProtocolを使用する

NSURLProtocolは一種の魔法のURLキーです。 これにより、URLローディングシステムの動作をオーバーライドできます。AppleはカスタムURLスキームを定義し、既存のURLスキームの動作を再定義します。



これは魔法のように見えますか? そうであるはずです。 なぜなら、あなたがそれらを探しているなら、あなたはほとんど愛のようなURLが私たちの周りにあることに気付くでしょう。 UIWebViewまたはWKWebViewを使用するものは何ですか? URLを使用します。 MPMoviePlayerからのビデオのブロードキャストには何が使用されますか? これにはURLが使用されます。 iTunesのアプリケーションに誰かを誘導したり、 FaceTimeまたはSkypeを起動したり、システムで別のアプリケーションを起動したり、HTMLファイルに画像を挿入したりするにはどうすればよいですか URLを使用します。 NSFileManagerを見て、URLを必要とするファイル処理メソッドの数を確認してください。



このNSURLProtocolの記事では、スキームのURLを変更するプロトコルハンドラーを定義する方法を学習します。 抽出されたリソースをコアデータに保存する、未加工の既製の透過キャッシングレイヤーを追加します。 起動すると、通常のUIWebViewがブラウザの役割を引き受け、ダウンロードしたページをキャッシュして、後でオフラインで表示できるようにします。







これに頭を突っ込む前に、基本的なネットワークの概念を学び、NSURLConnectionがどのように機能するかを理解する必要があります。 現在NSURLConnectionに慣れていない場合は、このチュートリアルやAppleによってコンパイルされたドキュメントを読むことをお勧めします。



さて、NSURLProtocolの目的を学ぶ準備はできましたか? さて、自分でお茶を入れて、意味のある、心を開発する議論とステップバイステップのアプリケーションの準備をしてください。



開始する



このチュートリアルのプロジェクトのコンテキストでは、次のアプリケーションに追加する可能性のある基本的なモバイルWebブラウザーを作成します。 ユーザーがログインしてURLを開くことができるメインユーザーインターフェイスがあります。 特徴的な機能は、ブラウザーが結果を正常にキャッシュすることです。 したがって、ページはネットワーク要求からではなく、ローカルアプリケーションキャッシュから読み込まれるため、ユーザーは以前にアクセスしたページを即座に読み込むことができます。



ページの読み込みが速い場合、ユーザーが満足すること、つまりNSURLProtocolがアプリケーションのパフォーマンスを向上させる方法の良い例であることは既に知っています。



次の手順に従う必要があります。





Core Dataデータベースに詳しくない場合は、 チュートリアルをご覧ください 。 ただし、このチュートリアルのコードは、NSURLProtocolの機能を理解するのに十分なはずです。 Core Dataデータベースを使用すると、ローカルキャッシュを簡単に展開できるため、ここで何か役に立つことを学ぶことはそれほど重要ではありません。



スタートアッププロジェクトの概要



ここからスタータープロジェクトをダウンロードできます。 ダウンロードが完了したら、プロジェクトファイルを解凍して開きます。



プロジェクトを開くと、2つのメインファイルが見つかります。 最初はMain.storyboardファイルです。 実装に必要なUIViewControllerが含まれています。 UITextField(URLの入力用)、UIButton(Web要求の実行用)、およびUIWebViewに注意してください。



BrowserViewController.swiftファイルを開きます。 ここでは、UIコンポーネント用に構成されたメイン操作モードが表示されます。 このUIViewControllerはUITextFieldDelegateプロトコルをサポートし、ユーザーがEnterキーを押したときにクエリを実行できます。 ボタンのIBActionは、Enterキーと同様に機能するように設定されています。 最後に、sendRequest()メソッドは、テキストフィールドからテキストを取得し、NSURLRequestオブジェクトを作成し、UIWebViewでloadRequest(_ :)メソッドを呼び出してそれをロードします。



アプリケーションに慣れたら、コンパイルして実行してください! アプリケーションが起動したら、「http://raywenderlich.com」と入力し、「実行」ボタンをクリックします。 UIWebViewは応答を読み込み、結果をアプリケーションに表示します。 始めるのはとても簡単です。 今度は、指を引く番です。 次...コーディング!



ネットワーク要求の傍受



URL Download Systemと呼ばれる多くのクラスが、iOSでURLリクエストを処理します。 URL Loading SystemはNSURLクラスに基づいています。 ネットワーク要求の場合、このクラスは、アプリケーションに到達しようとしているホストを示し、そのホスト内のリソースへのパスも示します。 さらに、NSURLRequestオブジェクトは、HTTPヘッダー、メッセージの大部分などの情報を追加します。 ブートシステムは、リクエストの処理に使用できるいくつかの異なるクラスを提供します。最も一般的なのはNSURLConnectionとNSURLSessionです。



次に、アプリケーションによって起動されたすべてのNSURLリクエストのインターセプトを開始します。 これを行うには、NSURLプロトコルの独自の実装を作成する必要があります。



ファイル\新規\ファイルをクリックします... iOS \ Source \ Cocoa Touch Classを選択し、Nextを押します。 「クラス」フィールドにMyURLProtocolと入力し、「サブクラス」フィールドにNSURLProtocolと入力します。 言語がSwiftによって選択されていることを確認します。 最後に、[次へ]をクリックし、ダイアログが表示されたら[作成]をクリックします。



MyURLProtocol.swiftファイルを開き、その内容を次のものに置き換えます。



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







URLダウンロードシステムは、URLをダウンロードする要求を受け取るたびに、登録されたプロトコルハンドラーを検索して要求を処理します。 各ハンドラーは、canInitWithRequest(_ :)メソッドでこの要求を処理できるかどうかをシステムに通知します。



このメソッドのパラメーターは、プロトコルで処理できるかどうかに関するクエリです。 メソッドがtrueを返す場合、ブートシステムはNSURLプロトコルのこのサブクラスに依存してリクエストを処理し、他のすべてのハンドラーを無視します。



登録された永続ハンドラーのいずれも要求を処理できない場合、URL Loading Systemはデフォルトモードを使用してそれ自体を処理します。



foo://などの新しいプロトコルを実行する場合は、URLスキームのリクエストがfooであることを確認する必要があります。 しかし、上記の例では、アプリケーションがリクエストを処理できないことを示す偽の応答を受け取ります。 少々お待ちください、すぐに処理を開始します!



注: NSURLProtocolは抽象クラスでなければなりません。 NSURLProtocolのサブクラスを個別に作成しますが、NSURLProtocolを直接処理しないでください。



AppDelegate.swiftを開き、メソッドを置き換えます。



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







これでアプリケーションが開始され、URLダウンロードシステムにプロトコルが登録されます。 これは、URLローディングシステムが受信したすべてのリクエストを処理できることを意味します。 これには、ロードシステムを直接要求するコーディングや、UIWebViewなどのURLロード構造に基づく多くのシステムコンポーネントが含まれます。



プロジェクトをコンパイルして実行します。 文字列「 raywenderlich .com」をWebサイトの文字列として貼り付け、 Goキーを押してXcodeコンソールを確認します。 これで、アプリケーションが満たす必要のある各要求に対して、URLローディングシステムはクラスを処理できる場合にクラスを要求します。



コンソールには、次のようなものが表示されるはずです。



 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 ...
      
      





現時点では、クラスはリクエストURLのシーケンシャルプレゼンテーションを登録し、falseを返すだけです。つまり、カスタムクラスはリクエストを処理できません。 ただし、ログを見ると、UIWebViewから送信されたすべてのリクエストが表示されます。 これらには、メインWebサイト(.html)と、JPEGやCSSファイルなどのすべてのアセットが含まれます。 UIWebViewは、要求を開始する必要があるたびに、開始する前にコンソールに登録します。 一番下の行には、Webページ上のすべてのアセットが原因で、おそらく500を超える大量のリクエストが表示されます。



そのため、ここで何をする必要があります:URLの各リクエストについてカスタムクラスに通知され、その後、各リクエストで何かを行うことができます!



どのようにしてデータを取得しますか?



「ページが長い間読み込まれると気に入っています」と、ユーザーからの連絡は一切ありません。 つまり、アプリケーションがリクエストを処理できることを確認する必要があります。 canInitWithRequest(_ :)で真の応答を取得すると、このリクエストを完全に処理するためにクラスが必要になります。 つまり、要求されたデータを受信して​​、URLダウンロードシステムに送り返す必要があります。



どのようにしてデータを取得しますか?



新しいアプリケーションのネットワークプロトコルを最初から実行している場合(たとえば、foo://プロトコルを追加する場合)、ここでアプリケーションのネットワークプロトコルを実装する喜びを理解できます。 ただし、タスクはカスタムキャッシングレイヤーを挿入するだけなので、NSURLConnectionを使用してデータを取得するだけです。



カスタムサブクラスNSURLProtocolは、NSURLProtocolClientプロトコルを実装するオブジェクトを介してデータを返します。 名前を混同しないように、NSURLProtocolはクラスであり、NSURLProtocolClientはプロトコルです。



クライアントを介して、動作モード、応答、およびデータの変更のポストバックをURLダウンロードシステムに通知します。



基本的には、リクエストをインターセプトし、NSURLConnectionを使用して標準のURLロードシステムにそれを返します。



MyURLProtocol.swiftファイルを開き、MyURLProtocolクラス定義の先頭に次の特性を追加します。



 var connection: NSURLConnection!
      
      







次に、canInitWithRequest(_ :)を見つけます。 応答フィールドにtrueと入力します。



 return true
      
      







さらに4つのメソッドを追加します。



 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 }
      
      







プロトコル自体が「標準リクエスト」の意味を決定しますが、少なくとも同じ標準リクエストで着信リクエストに応答する必要があります。 したがって、このメソッドに2つの意味的に等しい要求が導入された場合(つまり、必要ない= = =)、発信要求も意味的に等しい必要があります。 たとえば、カスタムURLスキームで大文字と小文字が区別されない場合、すべての正規URLは小文字です。



最低限の基準を満たすには、リクエスト自体を返すだけです。 通常、リクエストを変更する価値はないため、これは信頼できる有能なソリューションです。 結局のところ、あなたは開発者を信頼していますよね?! ここでできることは、たとえば、ヘッダーを追加してリクエストを変更し、新しいリクエストを返すことです。



requestIsCacheEquivalent(_:toRequest :)は、カスタムURLスキーム(つまりfoo://)の2つの異なる要求がキャッシュ機能に関して等しいかどうかを判断するために時間をかける必要がある場所です。 2つの要求が等しい場合、それらは同じキャッシュデータを使用する必要があります。 これは、このチュートリアルでは無視する必要がある独自の埋め込みURLダウンロードキャッシングシステムに適用されます。 したがって、この演習では、デフォルトのスーパークラス実装に依存するだけです。



ブートシステムは、startLoading()およびstopLoading()を使用して、NSURLProtocolに信号を送信し、要求の処理を開始および停止します。 NSURLConnectionサンプルは、データをダウンロードするためのデプロイメントを開始しています。 URLの読み込みをキャンセルできるように、stopメソッドもあります。 上記の例では、現在の接続をキャンセルして削除することでこれが許可されました。



やった! 有効なNSURLProtocolの例を必要とするインターフェイスをインストールしました。 さらに読みたい場合は、有効なNSURLProtocolサブクラスが実行できるメソッドの公式ドキュメントを参照してください。



しかし、コーディングはまだ完了していません! 作成したNSURLConnectionから転送されたコールバックを使用して行うリクエストをまだ処理していません。



MyURLProtocol.swiftファイルを開き、次のメソッドを追加します。



 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) }
      
      







これらはすべてNSURLConnectionデリゲートのメソッドです。 データをロードするために使用するNSURLConnectionサンプルがデータを持っているとき、ダウンロードが終了したとき、および障害が発生したときに応答するときに使用されます。 これらの各ケースでは、この情報をクライアントに渡す必要があります。



したがって、要約すると、MyURLProtocolハンドラーは独自のNSURLConnectionを作成し、リクエストの処理を要求します。 上記のNSURLConnectionフィードバックメソッドでは、プロトコルハンドラーがネットワークからURLダウンロードシステムにメッセージを送信します。 これらのメッセージは、ダウンロードの進行状況、プロセスの完了、またはエラーを示します。



NSURLConnectionDelegateとNSURLProtocolClientのメッセージシグネチャは非常によく似ています。どちらも非同期データロード用のAPIです。 また、MyURLProtocolがクライアント機能を使用してURLローディングシステムにメッセージを送り返す方法にも注目してください。



プロジェクトをコンパイルして実行します。 アプリケーションが起動したら、同じURLを入力してGoキーを押します。



ああ、ああ、ああ! あなたのブラウザは他に何もロードしていません! 動作中にトラブルシューティングナビゲーターを見ると、メモリ使用量が制御不能であることがわかります。 コンソールには、同じURLに対する無数のリクエストのレーシングリストが表示されます。 何が間違っているのでしょうか?



コンソールでは、行が常に次のように登録されていることがわかります。



 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/
      
      







問題に取り組む前に、Xcodeに戻ってアプリケーションを停止する必要があります。



タグを使用した無限ループ抑制



URLダウンロードとプロトコルロギングシステムについてもう一度考えてみてください。おそらく、これがなぜ起こっているのか理解できるでしょう。 UIWebViewがURLをロードする場合、URLダウンロードシステムはMyURLProtocolにこの特定の要求を処理できるかどうかを尋ねます。 クラスがtrueを返す場合、それはそれを処理できます。



このようにして、URL Loading Systemはプロトコルのサンプルを作成し、startLoadingを呼び出します。 次に、実装が開始され、NSURLConnectionが開始されます。 しかし、彼女はURLダウンロードシステムも呼び出します。 そして、あなたはどう思いますか? canInitWithRequest(_ :)メソッドで常に真の応答を取得するため、MyURLProtocolの別のコピーが作成されます。



この新しいコピーは、別のコピーを作成し、次に別のコピーを作成してから、無限のコピーを作成します。 そのため、アプリケーションは何もダウンロードしません。 さらにメモリを割り当て続け、コンソールに1つのURLのみを表示します。 貧弱なブラウザが無限ループに陥っています! ユーザーは必死になってデバイスを損傷する可能性さえあります。



明らかに、canInitWithRequest(_ :)メソッドで常に真の応答を取得できるとは限りません。 要求を1回だけ処理するようにURLローディングシステムに通知するには、何らかの制御が必要です。 この問題の解決策は、NSURLProtocolインターフェイスにあります。 このURLリクエストにカスタムプロパティを追加できるsetProperty(_:forKey:inRequest :)というクラスメソッドを探します。 そうすれば、プロパティを添付して「タグ」を付けることができ、ブラウザはそれを以前に見たことがあるかどうかを知ることができます。



したがって、ブラウザを無限ループから解放します。 MyURLProtocol.swiftファイルを開きます。 次のようにstartLoading()およびcanInitWithRequest(_ :)メソッドを置き換えます。



 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) }
      
      







現在、startLoading()メソッドで、キー「MyURLProtocolHandledKey」に関連付けられている属性を設定し、このリクエストに対してtrueに設定します。 これは、指定されたNSURLRequestオブジェクトに対してcanInitWithRequest(_ :)が次回呼び出されたときに、プロトコルが同じプロパティがインストールされているかどうかを尋ねることを意味します。



設定されている場合、trueを返します。これは、このリクエストを処理する必要がなくなったことを意味します。 URLダウンロードシステムは、ネットワークからデータをダウンロードします。 MyURLProtocolのコピーはそのリクエストに対して送信されるため、NSURLConnectionDelegateからコールバックを受け取ります。



アプリケーションをコンパイルして実行します。 アプリケーションは、WebViewにWebページを正常に表示します。 甘い勝利! コンソールは次のようになります。



 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 ...
      
      







アプリケーションを起動したときと同じように動作させるために、なぜこれをすべて行ったのかと疑問に思うかもしれません。 楽しい部分を準備する必要があるためです! これで、アプリケーションのURLからデータを完全に制御できるようになり、それを使って何でもできます。 アプリケーションの受信データのキャッシュを開始します。



ローカルキャッシュの実装



このアプリケーションの基本的な要件を覚えておいてください。このリクエストでは、ネットワークからのデータを一度だけダウンロードしてから、キャッシュする必要があります。 同じリクエストが将来再び開始されると、プロトコルはネットワークからロードせずにキャッシュされたレスポンスを返します。



注: スタータープロジェクトには、コアのコアデータモデルとスタックが既に含まれています。 コアデータの詳細を知る必要はありません。コアデータウェアハウスと考えることができます。 興味のある方は、 Appleのコアデータガイドをご覧ください



アプリケーションがネットワークから受信した回答を保存し、対応するキャッシュデータが表示されたときにそれらを取得します。 MyURLProtocol.swiftファイルを開き、ファイルの先頭に次のインポートを追加します。



 import CoreData
      
      







次に、クラス定義に2つの変数を追加します。



 var mutableData: NSMutableData! var response: NSURLResponse!
      
      







応答変数には、サーバーからの応答を保存するときに必要になるメタデータへのリンクが格納されます。 mutableData変数は、デリゲートメソッド接続(_:didReceiveData :)で受信したデータを格納するために使用されます。 回答が届いたら、データとメタデータをキャッシュできます。



次に、次のメソッドをクラスに追加します。



 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") } }
      
      







このメソッドの機能は次のとおりです。

  1. AppDelegateインスタンスからコアデータNSManagedObjectContextを取得します。 管理対象オブジェクトのコンテキストは、Core Dataへのインターフェースです。
  2. .xcdatamodeldファイルのように、データモデルに一致するNSManagedObjectクラスのインスタンスを作成します。 保存したNSURLResponseおよびNSMutableDataへのリンクに基づいてプロパティを設定します。
  3. Core Data管理対象エンティティのコンテキストを保存します。




データを保存する方法ができたので、どこかからこのメソッドを呼び出す必要があります。 同じMyURLProtocol.swiftで、NSURLConnectionのデリゲートメソッドを次のように変更します。



 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() }
      
      







クライアントに直接渡されるのではなく、応答アクションとデータがカスタムプロトコルクラスによって保存されるようになりました。



アプリケーションをコンパイルして実行します。 アプリケーションでは何も変更されていませんが、Webサーバーから正常に受信したデータがアプリケーションのローカルデータベースに保存されることに注意してください。



キャッシュされた応答の取得



最後に、キャッシュされたデータを取得し、NSURLProtocolでクライアントに送信します。 MyURLProtocol.swiftファイルを開きます。 次のメソッドを追加します。



 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 }
      
      







彼がすることは次のとおりです。

  1. saveCachedResponse()のように、コアデータ管理対象エンティティのコンテキストを取得します。
  2. NSFetchRequestを作成し、CachedURLResponseというアイテムを検索することを示します。 これは、取得する必要がある管理対象エンティティモデルの要素です。
  3. 選択のクエリの述語は、読み込むURLに対応するCachedURLResponseオブジェクトを取得する必要があります。 このコードはそれを設定します。
  4. 選択のためのクエリを実行します。
  5. 結果がある場合は、最初の結果を開きます。




ここで、startLoading()メソッドの実装を振り返ります。ネットワークからすべてをダウンロードする代わりに、まずキャッシュされていないデータのURLを確認する必要があります。現在のstartLoadingメソッドを見つけて、次のものに置き換えます。



 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) } }
      
      







結果は次のとおりです。



  1. まず、現在のリクエストのキャッシュデータがあるかどうかを確認する必要があります。
  2. 存在する場合、キャッシュされたオブジェクトからすべての関連データを取得します。
  3. 保存されたデータからNSURLResponseオブジェクトを作成します。
  4. 顧客に応答とデータを通知します。クライアントのストレージシステムをNotAllowedモードに設定します。これは、クライアントが自分の仕事であるため、クライアント自体をキャッシュしたくないためです。その後、すぐにURLProtocolDidFinishLoadingを呼び出して、ダウンロードが終了したことを通知できます。ネットワーク呼び出しはありません、それだけです!
  5. キャッシュされたデータが見つからなかった場合、通常どおりデータをダウンロードします。




プロジェクトをコンパイルして実行します。複数のWebサイトを参照してから、アプリケーションを終了します。または、デバイスを機内モードに切り替えます(または、iOSシミュレーターを使用して、コンピューターのWi-Fiをオフにし、イーサネットケーブルを取り外します)ダウンロードしたばかりのWebサイトをダウンロードしてみます。ページはキャッシュされたデータからロードする必要があります。やった!喜ぶ!やった!



コンソールには、次のような多くのエントリが表示されます。



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







これは、データがキャッシュから返されていることを示すレコードです!



そしてそれだけです。これで、アプリケーションはWebページリクエストから受信したデータとメタデータを正常にキャッシュします。ユーザーはより高速なページ読み込みと優れたパフォーマンスを享受できます!



NSURLProtocolはいつ使用されますか?



NSURLProtocolを使用して、アプリケーションをより面白く、より速く、より強く、単に驚くべきものにするにはどうすればよいですか?以下に例を示します。



ネットワークリクエストにユーザー通知を提供する:



UIWebView、NSURLConnectionを使用してリクエストを送信するか、サードパーティライブラリ(AFNetworking、MKNetworkKit、独自のものなど)を使用してリクエストを送信するかは関係ありません。メタデータとデータの両方にカスタム通知を提供できます。たとえば、テスト目的でリクエストの通知を「支払い」たい場合にも使用できます。



ネットワークプロセスをスキップし、ローカルデータを提供します。



時々、アプリケーションに必要なデータを提供するためにネットワーク要求を実行する必要がないように思われるかもしれません。 NSURLProtocolは、ローカルストレージまたはローカルデータベースでデータを検索するようにアプリケーションを構成できます。



ネットワークリクエストを転送する:



iOSをインストールするための特定の手順に従ってユーザーを信頼し、リクエストをプロキシサーバーにリダイレクトできるようにしたいことがありますか?できます! NSURLProtocolはあなたが望むものを提供します-要求制御。インターセプトして別のサーバーまたはプロキシにリダイレクトするように、または希望する場所にアプリケーションを設定できます。それはコントロールについてです!!!



リクエストのエージェントユーザーを変更します。



ネットワーク要求を開始する前に、メタデータまたはデータを変更することができます。たとえば、ユーザーエージェントを変更する場合があります。これは、サーバーがユーザーエージェントに応じてコンテンツを変更する場合に役立ちます。この例としては、コンピューターに関する携帯電話のデータが受信するコンテンツと、クライアントの言語の違いがあります。



独自のネットワークプロトコルを使用する:



独自のネットワークプロトコル(たとえば、UDP上に構築されたもの)を使用できます。あなたはそれを実装することができ、あなたのアプリケーションでは、あなたが選んだネットワークライブラリを使い続けることができます。



言うまでもなく、多くの可能性があります。このチュートリアルでNSURLProtocolを使用する可能性をすべてリストすることは非現実的です(不可能ではありません)。指定されたNSURLResponseを変更することにより、このNSURLRequestを開始する前に必要なことを行うことができます。さらに、独自のNSURLResponseを作成するだけです。結局のところ、あなたは開発者です。



NSURLProtocolは強力ですが、ネットワークライブラリではないことに注意してください。これは、既に使用しているライブラリに加えて使用できるツールです。つまり、独自のライブラリを使用しながら、NSURLProtocolの利点を活用できます。



次は?



ここでは、ここであなたは、このトレーニングマニュアルの完成したプロジェクトをダウンロードすることができます。



この例では、NSURLProtocolの簡単な使用方法を示しましたが、絶対的なキャッシュソリューションと混同しないでください。高品質のキャッシュブラウザを実装するには、さらに多くのことが必要です。実際、ロードシステムには、使い慣れた組み込みのキャッシュ構成があります。このチュートリアルの目的は、単に可能性を示すことです。 NSURLProtocolは、多数のコンポーネントから送受信されるデータにアクセスできるため、非常に強力です。 startLoadingメソッドを実装するときにできることにはほとんど制限はありません。



しばらくRFC 3986 IETFは、URLを「...抽象的なリソースまたは物理リソースを定義するコンパクトな文字列...」として控えめに定義できます。実際、URLは独自のミニ言語です。これは、物事を指定および配置するためのドメイン固有言語(DSL)です。これはおそらく、URLが画面外に出て、ラジオやテレビ広告で放送され、雑誌に印刷され、世界中の店舗の看板に描かれていることを考えると、おそらく世界で最も一般的なドメイン固有のドメイン言語です。



NSURLProtocolは、さまざまな方法で使用できる言語です。 TwitterがiOSにSPDYプロトコルを実装したいとき、HTTP 1.1の後継として最適化され、NSURLProtocolでこれを行いました。あなたがそれを使用するかどうかはあなた次第です。NSURLProtocolはパワーと柔軟性を提供しますが、同時に目標を達成するにはシンプルな実装が必要です。



All Articles