Использование NSURLProtocol в Swift

Original author: Zouhair Mahieddine
  • Translation
  • Tutorial
NSURLProtocol является своего рода волшебным ключом к URL. Он позволяет Вам переопределить работу системы загрузки URL, компания Apple определяет пользовательские схемы URL и переопределяет работу существующих схем URL.

Это кажется волшебным? Так и должно быть. Потому что, если Вы их поищете, то обнаружите что URL, практически как и любовь, вокруг нас. Что используют UIWebView или WKWebView? Они используют URL. Что используется для трансляции видео с MPMoviePlayer? Для этого используется URL. Как Вы направляете кого-то в свое приложение на iTunes, запускаете FaceTime или Skype, запускаете другое приложение в системе или даже вставка изображение в HTML файле? С помощью URL. Загляните на NSFileManager и обратите внимание на то, для какого количества методов обработки файлов необходимы URL.

В этой статье про NSURLProtocol Вы научитесь определять обработчика протокола, который модифицирует URL схемы. Он добавит необработанный и готовый прозрачный слой кэширования, сохраняя извлеченные ресурсы в Core Data. Запуская его в работу, обычный UIWebView может тогда взять на себя роль браузера, кэшируя загруженные страницы, чтобы можно было позже просмотреть их в оффлайн режиме.



Прежде чем Вы окунетесь в это с головой, Вам будет необходимо усвоить базовые сетевые концепции и ознакомиться с тем, как работает NSURLConnection. Если Вы на данный момент не знакомы с NSURLConnection, то я предлагаю Вам прочесть это обучающее руководство и/или документ составленный компанией Apple.

Ну а теперь готовы ли Вы изучить то, для чего нужен NSURLProtocol? Хорошо, пойдите сделайте себе чашку чаю и приготовьтесь к содержательному, развивающему ум обсуждению и пошаговому применению.

Начало

В контексте проекта этого обучающего руководства, Вы создадите элементарный мобильный вэб браузер, такой который Вы возможно добавили бы к своему следующему приложению. У него будет основной пользовательский интерфейс, который позволяет пользователю войти и открыть URL. Характерная особенность состоит в том, что Ваш браузер успешно кэширует полученные результаты. Таким образом, пользователь может в мгновение ока загружать раннее посещенные страницы, потому что страница будет загружаться не от запроса сети, а из локального кэша приложения.

Вы уже знаете, что если страницы загружаются быстро, пользователи довольны, то есть это является хорошим примером того, как NSURLProtocol может улучшить работу Вашего приложения.

Вам необходимо выполнить следующие шаги:
  • Использовать UIWebView для отображения веб-сайтов
  • Использовать базу данных Core Data для кэширования результатов.


Если Вы не знакомы с базой данных Core Data, Вы можете заглянуть в наше обучающее пособие. Однако программного кода в этом обучающем пособии должно быть достаточно, чтобы понять возможности NSURLProtocol. Использование базы данных Core Data является простым способом введения в действие локального кэша, таким образом, не столь важно изучить здесь что-то полезное.

Обзор cтартового gроекта

Вы можете загрузить стартовый проект здесь. Как только загрузка завершится, разархивируйте и откройте файл проекта.

Когда Вы откроете проект, Вы обнаружите два главных файла. Первый — это файл Main.storyboard. В нем находится UIViewController, необходимый для его внедрения. Обратите внимание на UITextField (для ввода URL), UIButton (для запуска веб-запросов) и UIWebView.

Откройте файл BrowserViewController.swift. Здесь Вы увидете основной режим работы, настроенный для компонентов UI. Этот UIViewController поддерживает протокол UITextFieldDelegate, и Вы можете запустить запрос, когда пользователь нажимает клавишу ввода. IBAction для кнопки задан таким образом, чтобы работать так же как клавиша ввода. Наконец, метод sendRequest () просто берет текст из текстового поля, создает объект NSURLRequest и вызывает метод loadRequest (_:) для UIWebView, чтобы загрузить его.

Как только Вы ознакомитесь с приложением, скомпилируйте и запустите его! Когда приложение запуститься, введите “http://raywenderlich.com” и нажмите кнопку “Go”. UIWebView загрузит ответ и отобразит результат в приложении. Довольно просто для начала. Теперь Ваш черед потянуть ваши пальцы. Далее… кодирование!

Перехват сетевых запросов

Ряд классов, известных как Система Загрузки URL, обрабатывает запросы URL на iOS. В основе Системы Загрузки URL — класс NSURL. Для сетевых запросов этот класс показывает какой хост пытается достигнуть Вашего приложения, а также показывает путь к ресурсу в данном хосте. Кроме того, объект NSURLRequest добавляет информацию, такую как заголовки HTTP, основную часть Вашего сообщения, и т.д. Система загрузки обеспечивает несколько различных классов, которые Вы можете использовать, чтобы обработать запрос, наиболее распространенными являются NSURLConnection и NSURLSession.

Теперь пора начать перехватывать все запросы NSURL, запущенные приложением. Для этого Вам будет необходимо создать свое собственное внедрение протокола NSURL.

Нажмите File\New\File …. Выберите iOS\Source\Cocoa Touch Class и нажмите клавишу Next. В поле Class войдите в MyURLProtocol и в поле Subclass of введите NSURLProtocol. Проверьте, чтобы язык был выбран Swift. Наконец, нажмите Next и затем Create, когда появится диалог.

Откройте файл 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 обработает его сама, используя режим по умолчанию.

Если Вы хотите выполнить новый протокол, такой как foo://, то именно здесь Вам необходимо проверить, чтобы запрос схемы URL был foo. Но в примере выше, Вам выдает ответ false, что указывает на то, что Ваше приложение не может обработать запрос. Подождите немного, Вы скоро начнете их обрабатывать!

Примечание: NSURLProtocol должен быть абстрактным классом. Вы создаете подкласс в индивидуальном режиме для NSURLProtocol, но Вы никогда не должны обрабатывать непосредственно сам NSURLProtocol.

Откройте AppDelegate.swift и замените метод.

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


Теперь, когда Ваше приложение запускается, оно зарегистрирует протокол в Системе Загрузки URL. Это означает, что оно сможет обработать каждый запрос полученный системой загрузки URL. Это включает кодирование, которое запрашивает систему загрузки напрямую, а также многие системные компоненты, которые основываются на структуре загрузки URL, такой как UIWebView.

Скомпилируйте проект и запускайте его. Вставьте строку «raywenderlich .com» как строку веб-сайта, нажмите клавишу 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. В них входит главный веб-сайт (.html) и все активы, такие как JPEGs и файлы CSS. Каждый раз, когда UIWebView должен запустить запрос, он регистрируется в консоле, прежде чем он запускается. Итог должен показать Вам гору запросов, вероятно более пятисот, из-за всех активов на веб-странице.

Таким образом, вот что Вам нужно делать: Ваш пользовательский класс получает извещение о каждом запросе URL, и тогда Вы можете делать что-то с каждым запросом!

Как Вы получаете данные?

“Мне нравиться, когда страницы загружаются целую вечность”, такого вы никогда не услышите ни от одного пользователя. То есть теперь Вы должны удостовериться, что Ваше приложение сможет обработать запросы. Как только Вы получаете ответ true в canInitWithRequest (_:), Ваш класс просто обязан полностью обработать этот запрос. Это означает, что Вы должны получить запрошенные данные и отправить их назад  в Систему Загрузки URL.

Как вы получаете данные?

Если Вы выполняете сетевой протокол нового приложения с нуля (например, добавляете протокол foo://), то как раз здесь Вы познаете радости внедрения сетевого протокола приложения. Но так как Ваша задача состоит в том, чтобы только вставить пользовательский слой кэширования, Вы можете просто получить данные при помощи NSURLConnection.

Ваш пользовательский подкласс NSURLProtocol возвращает данные через объект, который внедряет протокол NSURLProtocolClient. Чтобы не запутаться в названиях, запомните: NSURLProtocol – это класс, а NSURLProtocolClient — это протокол!

Через клиента Вы извещаете Систему Загрузки URL об обратной передаче изменений в режиме работе, ответов и данных.

В сущности, Вы просто перехватываете запрос и затем передаете его назад стандартной Системе Загрузки URL используя NSURLConnection.

Откройте файл MyURLProtocol.swift и добавьте следующую характеристику вверху определения класса MyURLProtocol:

var connection: NSURLConnection!


Затем, найдите canInitWithRequest (_:). Впишите true в поле ответа:

return true


Теперь добавьте еще четыре метода:

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
}


Ваш протокол сам определяет что означает “канонический запрос”, но как минимум он должен ответить входящему запросу тем же канонический запросом. Таким образом, если вводятся два семантически равных запроса (т.е. не обязательно = = =) для этого метода, то исходящие запросы должны также быть семантически равными. Например, если для Вашей пользовательской схемы URL регистр букв не имеет никакого значения, тогда все канонические URL в нижнем регистре.

Чтобы выполнить минимальный норматив, просто возвратите сам запрос. Как правило, это надежное компетентное решение, так как обычно изменять запрос не стоит. В конце концов, Вы доверяете разработчику, не так ли?! Что Вы могли бы здесь сделать, например, изменить запрос, добавив заголовок и возвратить новый запрос.

requestIsCacheEquivalent (_: toRequest:) — вот, где Вам не стоит торопиться, чтобы определить, когда два различных запроса пользовательской схемы URL (т.е. foo://) равны в отношении возможностей кэша. Если два запроса равны, то они должны использовать те же самые помещенные в кэш данные. Это касается собственной, встроенной системы кэширования Системы Загрузки URL, которую Вам следует игнорировать для этого обучающего пособия. Таким образом, для этого упражнения, просто полагайтесь на внедрение суперкласса по умолчанию.

Система загрузки использует startLoading () и stopLoading (), чтобы послать сигнал Вашему NSURLProtocol о начале и прекращении обработки запроса. Ваше начало внедрения ставит NSURLConnection образец, чтобы загрузить данные. Существует и метод остановки, чтобы загрузка URL могла быть отменена. В вышеупомянутом примере, это было разрешено отменой текущей связи и избавления от нее.

Ура! Вы установили интерфейс, которому требуется действенный пример 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 создаст образец Вашего протокола и вызовет startLoading. Тогда начинается внедрение и запускается ее NSURLConnection. Но она также вызывает Систему Загрузки URL. И что вы думаете? Так как Вы всегда получаете ответ true в методе canInitWithRequest (_:), он создает другую копию MyURLProtocol.

Эта новая копия приведет к созданию еще одной, и затем еще одной и затем бесконечного количества копий. Вот почему Ваше приложение ничего не загружает! Оно просто продолжает выделять больше памяти и показывает только один URL в консоле. Бедный браузер застревает в бесконечном цикле! Ваши пользователи могут настолько отчаяться, что даже могут причинить ущерб своим устройствам.

Очевидно, Вы не можете всегда получать ответ true в методе canInitWithRequest (_:). Вы должны иметь своего рода контроль, чтобы известить Систему Загрузки URL обрабатывать запрос только один раз. Решение проблемы находится в интерфейсе NSURLProtocol. Ищите метод класса, названный setProperty (_: forKey:inRequest:), который позволяет Вам добавлять пользовательские свойства к данному запросу URL. Таким образом, Вы можете 'пометить' его, прикрепив к нему свойство, и браузер будет знать, видел ли он уже его прежде.

Таким образом, Вы выводите браузер из бесконечного цыкла. Открываете файл 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 для данного запроса. Это означает что, когда в следующий раз вызывается canInitWithRequest (_:) для приведенного обьекта NSURLRequest, протокол может спросить, установлена ли то же самая проперти.

Если она установлено то вернет true, то это означает, что Вам не нужно больше обрабатывать этот запрос. Система Загрузки URL загрузит данные из сети. Так как Ваша копия MyURLProtocol передана для того запроса, он получит ответные вызовы от NSURLConnectionDelegate.

Скомпилируйте и запустите приложение. Приложение успешно отобразит веб-страницы в Вашем WebView. Сладкая победа! Консоль должена теперь выглядеть примерно так:

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 Вашего приложения, и Вы можете делать с ними что хотите. Пора начать кэшировать полученные данные Вашего приложения.

Внедрение локального кэша

Помните основное требование для этого приложения: для данного запроса данные из сети должны быть загружены только один раз, и затем их нужно закэшировать. Если тот же самый запрос в будущем будет запущен снова, то Ваш протокол вернет закэшированный ответ, не подгружая его из сети.

Примечание: стартовый проект уже включает в себя основную модель Core Data и стек. Вам не обязательно знать детали Core Data, Вы можете представлять себе это как темное хранилище данных; если Вам интересно, то загляните в Руководство по работе с Core Data от компании Apple

Пора сохранять ответы, которые Ваше приложение получает из сети, и извлекать их, когда появляются соответствующие кэшированные данные. Откройте файл MyURLProtocol.swift и добавьте следующий импорт вверху файла:

import CoreData


Затем, добавьте две переменных в определении класса:

var mutableData: NSMutableData!
var response: NSURLResponse!


Переменная response будет хранить ссылку на метаданные, которые Вам понадобятся при сохранении ответа от сервера. Переменная mutableData будет использоваться для хранения данных, которые получает в методе делегата connection(_: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. Получаете Core Data NSManagedObjectContext из экземпляра AppDelegate. Контекст упраляемого объекта — это Ваш интерфейс к Core Data.
  2. Создайте экземпляр класса NSManagedObject, чтобы соответствовать модели данных, как в файле .xcdatamodeld. Установите его свойства, основанные на ссылках на 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()
}


Вместо того, чтобы быть непосредственно переданные клиенту, ответные действия и данные теперь сохранены Вашим пользовательским классом протокола.

Скомпилируйте и запускайте приложение. В работе приложения ничего не поменялось, но помните, что теперь успешно полученные данные из веб-сервера сохраняются в локальной базе данных Вашего приложения.

Получение кэшированного ответа

Наконец, теперь время получить кэшированные данные и отправить их клиенту на 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. Берет контекст управляемого объекта Core Data, точно так же, как в saveCachedResponse ().
  2. Создает NSFetchRequest и указывая, что Вы хотите найти элемент под названием CachedURLResponse. Это тот элемент в модели управляемого объекта, который необходимо извлечь.
  3. Предикату для запроса на выборку необходимо получить объект CachedURLResponse, который соответствует загружаемому Вами URL. Этот кодекс его устанавливает.
  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. Если не было найдено никаких закэшированныз данных, то загрузите данные, как обычно.


Скомпилируйте и запускайте свой проект. Просмотрите несколько веб-сайтов и затем выйдите из приложения. Или переключите устройство в авиарежим (или, используя симулятор iOS, выключите Wi-Fi на Вашем компьютере / отключите кабель Ethernet) Попытайтесь загрузить любой веб-сайт, который Вы только что загрузили. Страницы должны загрузиться из кэшированных данных. Ура! Радуйтесь! У вас получилось!!!

Вы увидете много записей в консоле, которые выглядят таким образом:

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


Это запись свидетельствующая о том, что из вашего кэша возвращаются данные!

И это все. Теперь Ваше приложение успешно кэширует полученные данные и метаданные из запросов веб-страницы. Ваши пользователи будут наслаждаться более быстрыми загрузками страниц и превосходной работой!

Когда используется NSURLProtocol?

Как Вы можете использовать NSURLProtocol, чтобы сделать Ваше приложение интереснее, быстрее, сильнее и просто потрясающим? Вот несколько примеров:

Обеспечьте Ваши Сетевые Запросы Пользовательскими Уведомлениями:

Не имеет значения отправляете ли Вы запрос, используя UIWebView, NSURLConnection или даже пользуясь сторонней библиотекой (как например AFNetworking, MKNetworkKit, Вашей собственной, и т.д., поскольку они все построены над NSURLConnection). Вы можете обеспечить пользовательское уведомление и для метаданных и для данных. Вы могли бы использовать его также, если Вы, к примеру, хотите «погасить» уведомление о запросе в целях тестирования.

Пропустите Процесс Работы Сети и Обеспечьте Локальные Данные:

Иногда Вам может казаться, что нет необходимости запускать сетевой запрос чтобы предоставить приложению необходимые ему данные. NSURLProtocol может настроить Ваше приложение так, чтобы найти данные в локальном хранилище или в локальной базе данных.

Переправьте свои сетевые запросы:

Хотелось ли Вам когда-либо суметь перенаправить запросы на прокси сервер, доверяя пользователю, следующему определенным указаниям по установке iOS? Вы можете это сделать! NSURLProtocol дает Вам то, что Вы хотите – контроль над запросами. Вы можете настроить свое приложение так, чтобы перехватить и перенаправить их на другой сервер или прокси, или куда бы Вам хотелось. Речь ведь идет о контроле!!!

Измените пользователя-агента своих запросов:

Прежде, чем запустить любой сетевой запрос, Вы можете захотеть изменить его метаданные или данные. Например, Вы можете захотеть изменить пользователя-агента. Это может быть полезным, если Ваш сервер изменяет содержание в зависимости от пользователя-агента. Примером этого могут быть различия между содержанием, полученными данными для мобильного телефона по отношению к компьютеру, а также языка клиента.

Используйте свой собственный сетевой протокол:

У Вас может быть свой собственный сетевой протокол (например, что-то построенное над UDP). Вы можете внедрить его и в Вашем приложении Вы все еще можете продолжать пользоваться любой предпочтительной Вам сетевой библиотекой.

Само собой разумеется, возможностей много. Это было бы непрактично (но не невозможно) перечислять все возможности, которые Вы имеете с NSURLProtocol в этом обучающем пособии. Вы можете сделать все, что Вам необходимо с данным NSURLRequest, прежде чем он будет запущен, изменяя обозначенный NSURLResponse. Еще лучше просто создать свой собственный NSURLResponse. В конце концов, Вы разработчик.

В то время как NSURLProtocol является мощным, не забывайте, что это не сетевая библиотека. Это инструмент, который Вы можете использовать в дополнение к библиотеке, которой Вы уже пользуетесь. Короче говоря, Вы можете использовать в своих интересах преимущества NSURLProtocol, в то время как пользуетесь своей собственной библиотекой.

Что дальше?

Вот здесь Вы можете загрузить готовый проект для этого обучающего пособия.

Этот пример продемонстрировал простое использование NSURLProtocol, но не путайте его с абсолютным решением кэширования. Для внедрения высококачественного браузера кэширования требуется намного больше. Фактически, у системы подгрузки есть встроенные конфигурации кэширования, с которыми стоит ознакомиться. Задача этого обучающего пособия состоит в том, чтобы просто показать Вам возможности. Поскольку у NSURLProtocol есть доступ к данным входящим и исходящим из большого количества компонентов, это очень мощно! Нет почти никаких пределов тому, что Вы можете при внедрении метода startLoading.

В то время как RFC 3986 IETF может скромно определить URL как “… компактную последовательность знаков, которая определяет абстрактный или физический ресурс …” дело в том, что URL — это свой собственный мини-язык. Это определяемый доменым языком (DSL) для обозначения и расположения вещей. Это вероятно, самый распространенный определяемый доменый язык в мире, учитывая то, что URL вышли за пределы экрана и теперь транслируются по радио и в телевизионных рекламных объявлениях, печатаются в журналах и рисуются на знаках магазинов во всем мире.

NSURLProtocol – это язык, который может быть использован огромным количеством различных способов. Когда Твиттер хотел внедрить протокол SPDY на iOS, оптимизированном преемнике HTTP 1.1, они сделали это с помощью NSURLProtocol. То, для чего Вы используете его, ваше дело. NSURLProtocol дает Вам мощность и гибкость, но в то же время требует простого внедрения для достижения Ваших целей.
Share post

Similar posts

Comments 0

Only users with full accounts can post comments. Log in, please.