Alamofire что это
Перейти к содержимому

Alamofire что это

  • автор:

Дебаг для Alamofire запросов

Alamofire — библиотека, для работы с HTTP написанная на свифте. Она упрощает жизнь разработчикам в разы.

Когда пишешь сетевой код, часто возникает вопрос как его подебажить, посмотреть параметры, заголовки и тд. С Alamofire это просто.

 1import Foundation 2import Alamofire 3 4extension Request < 5 public func debug() -> Self < 6 #if DEBUG 7 debugPrint(self) 8 #endif 9 return self 10 > 11> 

B при запросе воспользуемся им.

Alamofire.request(url).debug() .responseJSON( completionHandler: < response in debugPrint(response) >) 

И в консоле буде ваш запрос в виде cURL.

$ curl -v \ -b "__cfduid=dd963d192e96b1eb5a7eca3f0af0ba8051589034780" \ -H "Accept-Encoding: gzip;q=1.0, compress;q=0.5" \ -H "Accept-Language: en;q=1.0, ru-RU;q=0.9" \ -H "User-Agent: AlamofireMagic/1.0 (ru.4gophers.AlamofireMagic; build:1; iOS 13.4.1) Alamofire/4.9.1" \ "https://jsonplaceholder.typicode.com/posts/1" 

На самом деле, все еще проще. С версии 5 в библиотеке уже есть встроенный обработчик. Достаточно написать так:

1AF.request("https://jsonplaceholder.typicode.com/posts/1") 2 .cURLDescription < description in 3 print(description) 4 > 5 .responseJSON( completionHandler: < response in 6 debugPrint(response) 7 >) 

И результат будет точно таким же

Ссылки

  • Документация по Alamofire
  • Отличный туториал по Alamofire на raywenderlich.com

Работа с сервером с помощью Alamofire на Swift

Сразу хочу сказать, данная статья предназначена прежде всего для новичков. Здесь не будет best practice, создание сервисов, репозиториев и прочей оптимизации кода. Расскажу про основы работы с запросами и покажу применение на примерах.

Зачем

Итак, у нас возникла необходимость обрабатывать данные с сервера. Данная задача присутствует почти во всех мобильных приложениях.

Существует нативный инструмент для этого — URLSession, но работать с ним немного сложнее, чем хотелось бы. Для облегчения этого процесса существует framework Alamofire — это обвертка над URLSession, которая сильно упрощает жизнь при работе с сервером.

Установка

Воспользуемся CocoaPods т.к. с ним очень легко и быстро работать.

Добавим в Podfile:

pod 'Alamofire'

Для использования Alamofire версии 4+ необходимы следующие требования:

  • iOS 9.0+ / macOS 10.11+ / tvOS 9.0+ / watchOS 2.0+
  • Xcode 8.0+
  • Swift 3.0+
  • CocoaPods 1.1.0+

Так же нам необходимо добавить use_frameworks! .

Так будет выглядеть минимальный Podfile:

platform :ios, '9.0' use_frameworks! target 'Networking' do pod 'Alamofire' end

Настройка доступа HTTP

По умолчанию в приложении закрыт доступ к HTTP соединениям, доступны только HTTPS. Но пока еще очень много сайтов не перешли на https.

Мы будем работать с сервером http://jsonplaceholder.typicode.com, а он работает по http. Поэтому нам надо открыть доступ для него.

Для тренировки мы откроем доступ для всех сайтов. Открытие для одного сайта в данной статье не буду рассматривать.

Открываем Info.plist и добавляем в него App Transport Security Settings и внутрь этого параметра необходимо добавить Allow Arbitrary Loads, со значением YES.

Выглядеть это должно следующим образом:

![Info.plist](/Users/zdaecqzezdaecq/Downloads/Работа с запросам с помощью Alamofire/info_plist.png)

Или вот Source code, который необходимо добавить:

правой кнопкой мыши на Info.plist -> Open as -> Source code

NSAppTransportSecurity NSAllowsArbitraryLoads 

Первый минимальный запрос

Не забудьте, что нам нужно открыть Networking.xcworkspace, а не Networking.xcodeproj, который создался после pod install

Открываем файл ViewController.swift и заменяем его код на следующий:

import UIKit import Alamofire class ViewController: UIViewController < override func viewDidLoad() < super.viewDidLoad() request("http://jsonplaceholder.typicode.com/posts").responseJSON < response in print(response) >print("viewDidLoad ended") > >

Запускайте проект.
В консоли выведится:

viewDidLoad ended SUCCESS: ( < body = "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"; title = "sunt aut facere repellat provident occaecati excepturi optio reprehenderit"; userId = 1; >, . 

Поздравляю! Вы сделали первый запрос на сервер и получили от него ответ с результатом.

Подробнее о минимуме

Нам необходимо получить доступ к новым функция, т.к. это не наши файлы, а отдельная библиотека:

Собственно сам метод запроса:

Далее первым параметром передается URL, по которому будет производится запрос:

Метод responseJSON говорит о том, что ответ от сервера нам нужен в JSON формате.

Далее в клоужере мы получаем ответ от сервера и выводим его в консоль:

Важно заметить, что код в этом клоужере происходит асинхронно и выполнится после выхода из viewDidLoad, тем самым строка viewDidLoad ended в консоль выводится раньше.

Методы HTTP

На самом деле мы сделали GET запрос, но нигде этого не указывали. Начиная с Alamofire 4 по умолчанию выполняется GET запрос. Мы может его явно указать, заменив соответствующий код на следующий:

request("http://jsonplaceholder.typicode.com/posts", method: .get)

Как Вы уже поняли в параметре method: передается метод запроса и от него зависит, как мы будем общаться с сервером. Чаще всего мы будем:

  1. получать (GET)
  2. изменять (PUT)
  3. отправлять, создавать (POST)
  4. удалять (DELETE)

данные с сервера.

Подробнее про эти и другие методы HTTP можете почитать на википедии:

Alamofire.request

Функция request — глобальная функция, поэтому мы можем ее вызывать через Alamofire.request или просто request .

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

request(URLConvertible, method: HTTPMethod, parameters: Parameters?, encoding: ParameterEncoding, headers: HTTPHeaders?)
URLConvertible

Первым параметром является путь запросу и он принимает URLConvertible . (Ваш КЭП)

Если мы посмотрим на его реализацию, то увидим, что это протокол с одной функцией:

public protocol URLConvertible < func asURL() throws ->URL >

и он уже реализован для следующих типов данных:

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

HTTPMethod

Это enum, со всеми возможными типами запросов:

public enum HTTPMethod: String

Как мы уже выяснили: по умолчанию .get
Тут ничего сложного, идем дальше.

Parameters

Это простой Dictionary:

public typealias Parameters = [String: Any]

Через параметры мы будем передавать данные на сервер (например, для изменения или создания объектов).

ParameterEncoding

Это тоже протокол с одной функцией:

public protocol ParameterEncoding < func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws ->URLRequest >

Он необходим для определения в каком виде нам закодировать наши параметры. Разные серверы и запросы требуют определенной кодировки.

Этот протокол реализуют:

  • URLEncoding
  • JSONEncoding
  • PropertyListEncoding

По умолчанию у нас URLEncoding.default .

В основном этот параметр не используется, но иногда бывает нужен, в частности JSONEncoding.default для кодировки в JSON формате и PropertyListEncoding.default в XML.

Я заметил, что Int не отправляется без JSONEncoding.default , но возможно это было в Alamofire 3, а может из-за сервера. Просто имейте это ввиду.

HTTPHeaders

Это также Dictionary, но другой типизации:

public typealias HTTPHeaders = [String: String]

Headers(заголовки) нам будут необходимы в основном для авторизации.

Подробнее про заголовки на википедии:

DataRequest

На выходе мы получаем объект типа DataRequest — сам запрос. Его мы можем сохранить, передать, как параметр в другую функцию при необходимости, донастроить и отправить. Об этом далее.

Обработка ответа

Ответ от сервера может прийти, как с результатом, так и с ошибкой. Для того, чтобы их различать у ответа есть такие параметры, как statusCode и contentType .

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

Ручная обработка ответа

Если мы не настраивали валидацию, то в

у нас будет статус код ответа, а в

будет результат, если ответ пришел без ошибки, и в

request("http://jsonplaceholder.typicode.com/posts").responseJSON < responseJSON in guard let statusCode = responseJSON.response?.statusCode else < return >print("statusCode: ", statusCode) if (200.. <300).contains(statusCode) < let value = responseJSON.result.value print("value: ", value ?? "nil") >else < print("error") >>

Подробнее про коды состояний на википедии:

Настройка запроса

Для этого у DataRequest есть 4 метода:

  1. validate(statusCode: _ )
  2. validate(contentType: _ )
  3. validate(клоужер для ручной валидации)
  4. validate()

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

Взглянем на его реализацию:

public func validate() -> Self

Видим, что он состоит из двух других валидаций:

  1. self.acceptableStatusCodes — возвращает массив статус кодов(Int) из range 200..
  2. self.acceptableContentTypes — возвращает массив допустимых хедеров(String)

У DataResponse есть параметр result , который может сказать нам, пришел ответ с ошибкой или с результатом.

Итак, применим валидацию для запроса:

request("http://jsonplaceholder.typicode.com/posts").validate().responseJSON < responseJSON in switch responseJSON.result < case .success(let value): print(value) case .failure(let error): print(error) >>

Если у нас не будет вылидации запроса ( validate() ), то result всегда будет равен .success , за исключением ошибки из-за отсутствия интернета.

Можно обрабатывать ответ обоими способами, но я настоятельно рекомендую пользоваться настройкой валидации запроса — будет меньше ошибок!

Обработка результата ответа

Ответ от сервера чаще всего бывает в виде одного объекта или массива объектов.

Если мы посмотрим на тип результата ответа, то увидим тип Any . Чтобы из него что-то достать — нам надо его привести к нужному формату.

В логах мы замечали, что у нас приходит массив Dictionary, поэтому к нему и будем приводить:

request("http://jsonplaceholder.typicode.com/posts").responseJSON < responseJSON in switch responseJSON.result < case .success(let value): print("value", value) guard let jsonArray = responseJSON.result.value as? [[String: Any]] else < return >print("array: ", jsonArray) print("1 object: ", jsonArray[0]) print("id: ", jsonArray[0]["id"]!) case .failure(let error): print(error) > >

После этого, как показано выше, мы можем делать что угодно, например, создать объект и сохранить его, чтобы потом было удобнее работать с данными.

В отдельном файле создадим структуру Post:

struct Post

Так будет выглядеть парсинг в массив объектов:

request("http://jsonplaceholder.typicode.com/posts").responseJSON < responseJSON in switch responseJSON.result < case .success(let value): guard let jsonArray = value as? Array<[String: Any]>else < return >var posts: [Post] = [] for jsonObject in jsonArray < guard let as? Int, let title = jsonObject["title"] as? String, let body = jsonObject["body"] as? String, let userId = jsonObject["userId"] as? String else < return >let post = Post(id: id, title: title, body: body, userId: userId) posts.append(post) > print(posts) case .failure(let error): print(error) > >

Парсинг объекта внутри запроса выглядит очень плохо + нам придется всегда копировать эти строки для каждого запроса. Чтобы от этого избавиться создадим конструктор init?(json: [String: Any]) :

init?(json: [String: Any]) < guard let as? Int, let title = json["title"] as? String, let body = json["body"] as? String, let userId = json["userId"] as? String else < return nil >self.id = id self.title = title self.body = body self.userId = userId >

Он может вернуть nil, если сервер нам что-то не вернул

И тогда метод запроса выглядит на много понятнее и приятнее:

request("http://jsonplaceholder.typicode.com/posts").responseJSON < responseJSON in switch responseJSON.result < case .success(let value): guard let jsonArray = value as? Array<[String: Any]>else < return >var posts: [Post] = [] for jsonObject in jsonArray < guard let post = Post(json: jsonObject) else < return >posts.append(post) > print(posts) case .failure(let error): print(error) > >

Пойдем еще дальше и в Post добавим метод обработки массива:

static func getArray(from jsonArray: Any) -> [Post]? < guard let jsonArray = jsonArray as? Array<[String: Any]>else < return nil >var posts: [Post] = [] for jsonObject in jsonArray < if let post = Post(json: jsonObject) < posts.append(post) >> return posts >

Тогда метод запроса примет следующий вид:

request("http://jsonplaceholder.typicode.com/posts").responseJSON < responseJSON in switch responseJSON.result < case .success(let value): guard let posts = Post.getArray(from: value) else < return >print(posts) case .failure(let error): print(error) > >

Конечный вариант файла Post.swift:

import Foundation struct Post < var id: Int var title: String var body: String var userId: String init?(json: [String: Any]) < guard let as? Int, let title = json["title"] as? String, let body = json["body"] as? String, let userId = json["userId"] as? String else < return nil >self.id = id self.title = title self.body = body self.userId = userId > static func getArray(from jsonArray: Any) -> [Post]? < guard let jsonArray = jsonArray as? Array<[String: Any]>else < return nil >var posts: [Post] = [] for jsonObject in jsonArray < if let post = Post(json: jsonObject) < posts.append(post) >> return posts > >

Для тех кто уже разобрался в работе с flatMap, то функцию getArray можно написать так:

 static func getArray(from jsonArray: Any) -> [Post]? < guard let jsonArray = jsonArray as? Array<[String: Any]>else < return nil >return jsonArray.flatMap < Post(json: $0) >>

Разные типы ответов

responseJSON

Как отправлять запрос и получать ответ в виде JSON с помощью responseJSON мы научились. Теперь разберем в каком еще виде можем получить ответ.

responseData

Ответ нам придет в виде Data. Зачастую так приходят картинки, но даже наш предыдущий запрос мы можем получть в виде Data:

request("http://jsonplaceholder.typicode.com/posts").responseData < responseData in switch responseData.result < case .success(let value): guard let string = String(data: value, encoding: .utf8) else < return >print(string) case .failure(let error): print(error) > >

В примере мы получает ответ и преобразовываем его в строку. Из нее неудобно получать данные, как из Dictionary, но есть парсеры, которые сделают из стоки объект.

responseString

Здесь все просто. Ответ придет в виде JSON строки. По факту он делает, то, что мы написали выше в responseData:

request("http://jsonplaceholder.typicode.com/posts").responseString < responseString in switch responseString.result < case .success(let value): print(value) case .failure(let error): print(error) >>
response

Можно сказать это базовый метод. Он никак не обрабатывает данные от сервера, выдает их в том виде, в каком они пришли. У него нету свойства result и поэтому конструкция вида switch response.result здесь не сработает. Все придется делать вручную. Он нам редко понадобится, но знать о нем надо.

request("http://jsonplaceholder.typicode.com/posts").response < response in guard let data = response.data, let string = String(data: data, encoding: .utf8) else < return >print(string) >

Выведется строка, если ответ пришел без ошибки.

responsePropertyList

Существует еще метод .responsePropertyList . Он нужен для получения распарсенного plist файла. Я им еще не пользовался и не нашел тестого сервера, чтобы привести пример. Просто знайте, что он есть или можете сами с ним разобраться по аналогии с другими.

Прогресс загрузки

Иногда мы можем получать большой ответ от сервера, например, когда скачиваем фотографию, и нам необходимо отображать прогресс загрузки. Для этого у request есть метод downloadProgress :

Вместо https://s-media-cache-ak0.pinimg.com/originals/ef/6f/8a/ef6f8ac3c1d9038cad7f072261ffc841.jpg можете вставить любую ссылку на фотографию. Желательно большую, чтобы запрос не выполнился моментально и вы увидели сам процесс.

request("https://s-media-cache-ak0.pinimg.com/originals/ef/6f/8a/ef6f8ac3c1d9038cad7f072261ffc841.jpg") .validate() .downloadProgress < progress in print("totalUnitCount:\n", progress.totalUnitCount) print("completedUnitCount:\n", progress.completedUnitCount) print("fractionCompleted:\n", progress.fractionCompleted) print("localizedDescription:\n", progress.localizedDescription) print("---------------------------------------------") >.response < response in guard let data = response.data, let image = UIImage(data: data) else < return >print(image) >

Класс Progress — это класс стандартной библиотеки.

В логах будет выводиться прогресс в виде блоков:

totalUnitCount: 2113789 completedUnitCount: 2096902 fractionCompleted: 0.992011028536907 localizedDescription: 99% completed

Мы можем поделить completedUnitCount на totalUnitCount и получим число от 0 до 1, которое будет использоваться в UIProgressView, но за нас это уже сделали в свойстве fractionCompleted.

Чтобы увидеть саму картинку, поставьте breakpoint на строку с print(image) и нажмите на Quick Look (кнопка с глазом) в дебаг панели:

![Debug console](/Users/zdaecqzezdaecq/Downloads/Работа с запросам с помощью Alamofire/image_quick_look.png)

Примеры

Создание объекта (POST)

Самое простое создание объекта на сервере выглядит так:

let params: [String: Any] = [ "title": "new post", "body": "some news", "userId": 10 ] request("http://jsonplaceholder.typicode.com/posts", method: .post, parameters: params).validate().responseJSON < responseJSON in switch responseJSON.result < case .success(let value): guard let jsonObject = value as? [String: Any], let post = Post(json: jsonObject) else < return >print(post) case .failure(let error): print(error) > >

id не передаем т.к. сервер должен сам его назначить. А вообще для создания каждого объекта в документации должны прописываться необходимые параметры.

Обновление объекта (PUT)

При обновлении объекта, его id зачастую прописывается не в параметре, а в пути запроса ( ~/posts/1 ):

let params: [String: Any] = [ "title": "new post", "body": "some news", "userId": 10 ] request("http://jsonplaceholder.typicode.com/posts/1", method: .put, parameters: params).validate().responseJSON < responseJSON in switch responseJSON.result < case .success(let value): guard let jsonObject = value as? [String: Any], let post = Post(json: jsonObject) else < return >print(post) case .failure(let error): print(error) > >

Конечно, могут сделать и через параметр, но это будет не по REST. Подробнее про REST в статье на хабре:

Загрузка фотографии на сервер (multipartFormData)

Так выглядит загрузка фотографии на сервер:

let image = UIImage(named: "some_photo")! let data = UIImagePNGRepresentation(image)! let httpHeaders = ["Authorization": "Basic YWNjXzE4MTM2ZmRhOW*****A=="] upload(multipartFormData: < multipartFormData in multipartFormData.append(data, withName: "imagefile", fileName: "image.jpg", mimeType: "image/jpeg") >, to: "https://api.imagga.com/v1/content", headers: httpHeaders, encodingCompletion: < encodingResult in switch encodingResult < case .success(let uploadRequest, let streamingFromDisk, let streamFileURL): print(uploadRequest) print(streamingFromDisk) print(streamFileURL ?? "streamFileURL is NIL") uploadRequest.validate().responseJSON() < responseJSON in switch responseJSON.result < case .success(let value): print(value) case .failure(let error): print(error) >> case .failure(let error): print(error) > >)

Ужасно не правда ли?

Давайте разберем, что за что отвечает.

Я закинул фотографию с именем some_photo в Assets.xcassets

Создаем объект картинки и преобразуем ее в Data:

let image = UIImage(named: "some_photo")! let data = UIImagePNGRepresentation(image)!

Создаем словарь для передачи токена авторизации:

let httpHeaders = ["Authorization": "Basic YWNjXzE4MTM2ZmRhOW*****A= https://imagga.com/profile/dashboard">https://imagga.com/profile/dashboard

До этого мы использовали метод request. Сдесь же используется метод upload. Первым параметром идет клоужер для присоединения нашей картинки:


upload(multipartFormData: < multipartFormData in multipartFormData.append(data, withName: "imagefile", fileName: "image.jpg", mimeType: "image/jpeg") >

Следующими параметрами идут URL и headers:

to: "https://api.imagga.com/v1/content", headers: httpHeaders

Дальше идет клоужер с закодированным запросом:

encodingCompletion: < encodingResult in switch encodingResult < case .success(let uploadRequest, let streamingFromDisk, let streamFileURL): print(uploadRequest) print(streamingFromDisk) print(streamFileURL ?? "streamFileURL is NIL") . case .failure(let error): print(error) >>)

Из него мы можем получить запрос (uploadRequest), и две переменные необходимые для потока(stream) файлов.

Про потоки говорить не буду, достаточно редкая штука. Пока вы просто увидите, что эти две переменные равны false и nil соответственно.

Дальше мы должны отправить запрос в привычной для нас форме:

uploadRequest.validate().responseJSON() < responseJSON in switch responseJSON.result < case .success(let value): print(value) case .failure(let error): print(error) >>

Когда вы получите свой токен, вставите свою фотографию и выполните запрос, то результат будет следующим:

filename может не отличаться, а id будут.

Итог

Мы познакомились с фреймворком Alamofire, разобрались с методом request, отправкой запросов, обработкой ответа, парснгом положительного ответа, получением информации о прогрессе запроса. Сделали несколько простых запросов и научились загружать фотографии на сервер с авторизацией.

  • Разработка под iOS
  • Разработка мобильных приложений
  • Swift

Alamofire: Часть 2 (Xcode 6.3)

Этот туториал обновлен до версии Xcode 6.3 и Swift версии 1.2.

С возвращением во вторую и последнюю часть нашего туториала посвященного Alamofire!

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

В этом туториале вы создадите приложение, которое будет представлять из себя галерею, а так же узнаете, как добавить следующую функциональность к вашему уже существующему приложению Photomania:

  • Просмотр фотографий
  • Возможность просматривать комментарии и другие детали
  • Возможность скачивать фотографии с симпатичным прогресс баром
  • Оптимизированные сетевые вызовы и кэширование изображений
  • И возможность обновления

Поехали!

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

Если вы не работали с нами в первой части, то не забудьте получить conusmer key на сайте 500px.com и заменить его в Five100px.swift. Инструкции, как получить этот ключ и, как его заменить упакованы в первой части.

Запустите ваш проект и освежите память:] Вы можете просматривать фотографии в галерее, но при нажатии на конкретную фотографию она не будет отображаться. Это и есть наша первая проблема, которую мы вскоре решим! ;]

Создаем просмотр фотографий (Photo Viewer)

Давайте будем честны — универсальные шаблоны (или джинерики) — одна из самых мощных особенностей любого современного языка, включая Swift. По этому, будет неправильно, если мы не используем ее в нашем проекте.

Откройте Five100px.swift и добавьте следующий код, рядом с верхней строкой, сразу после import Alamofire:

@objc public protocol ResponseObjectSerializable < init?(response: NSHTTPURLResponse, representation: AnyObject) >extension Alamofire.Request < public func responseObject(completionHandler: (NSURLRequest, NSHTTPURLResponse?, T?, NSError?) -> Void) -> Self < let responseSerializer = GenericResponseSerializer < request, response, data in let JSONResponseSerializer = Request.JSONResponseSerializer(options: .AllowFragments) let (JSON: AnyObject?, serializationError) = JSONResponseSerializer.serializeResponse(request, response, data) if let response = response, JSON: AnyObject = JSON < return (T(response: response, representation: JSON), nil) >else < return (nil, serializationError) >> return response(responseSerializer: responseSerializer, completionHandler: completionHandler) > >

В коде выше вы снова расширяете Alamofire для того, чтобы добавить новый сериализатор ответа. В этот раз вы добавили функцию .responseObject() в качестве универсальной функции. Она может сериализовать любые объекты и данные, которые подписаны под ResponseObjectSerializable (тот, что вы определили ранее).

Это значит, что если вы определили новый класс, который имеет форму инициализатора init(response:representation:), Alamofire может автоматически возвращать объекты этого типа от сервера. Вы инкапсулировали логику сериализации прямо внутри самого пользовательского класса, и это самая настоящая элегантность объектно-ориентированного проектирования!

Просмотр фотографий (photo viewer) использует класс PhotoInfo, который уже соответствует протоколу ResponseObjectSerializable(он реализует необходимый метод). Но вам нужно сделать это официально, указав, что класс подписан на протокол ResponseObjectSerializable.

Откройте Five100px.swift и укажите в объявлении класса PhotoInfo, что он соответствует протоколу ResponseObjectSerializable:

class PhotoInfo: NSObject, ResponseObjectSerializable

Заметка

Если вам интересно, как параметр representation сериализован в объект PhotoInfo, то вы можете посмотреть required init(response:representation:).

Откройте PhotoViewerViewController.swift (но не PhotoBrowserCollectionViewController.swift. ), и добавьте библиотечный реквизин вверх файла:

import Alamofire

Затем добавьте следующий код в viewDidLoad():

loadPhoto()

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

Добавьте следующий код перед setupView() в том же файле:

func loadPhoto() < Alamofire.request(Five100px.Router.PhotoInfo(self.photoID, .Large)).validate().responseObject() < (_, _, photoInfo: PhotoInfo?, error) in if error == nil < self.photoInfo = photoInfo dispatch_async(dispatch_get_main_queue()) < self.addButtomBar() self.title = photoInfo!.name >Alamofire.request(.GET, photoInfo!.url).validate().responseImage() < (_, _, image, error) in if error == nil && image != nil < self.imageView.image = image self.imageView.frame = self.centerFrameFromImage(image) self.spinner.stopAnimating() self.centerScrollViewContents() >> > > >

В этот раз вы создали Alamofire — запрос внутри другого обработчика результата запроса Alamofire. Первый запрос получает ответ JSON и использует новый универсальный сериализатор ответа, для создания экземпляра PhotoInfo из этого ответа.

(_, _, photoInfo: PhotoInfo?, error) in — отображает параметры обработчика завершения (complition hanlder parameters). Первые два знака подчеркивания (“_”) означают, что нам не нужны первые два параметра, и нам не к чему явно указывать их имена, например, как request и response.

Третий параметр явно указан как экземпляр PhotoInfo, так что универсальный сериализатор автоматически инициализирует его, и возвращает объект этого типа, который содержит URL фотографии. Второй запрос Alamofire использует сериализатор изображения, который вы создали ранее, для конвертирования NSData в UIImage, который вы отобразите в image view.

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

Вызов функции .validate() до запроса объекта ответа — другая простая для использования особенность Alamofire. Сцепление его между запросом и ответом подтверждает, что ответ имеет код состояния по умолчанию в допустимом диапазоне от 200 до 299. Если проверка не пройдена, обработчик ответа будет иметь соответствующую ошибку, которую вы можете обработать в завершающем обработчике.

Даже если у вас ошибка, ваш обработчик ошибки все равно будет вызван. Четвертый параметр error является экземпляром NSError. Он содержит значение, которое позволяет вам реагировать на ваши ошибки совершенно любым образом.

Запустите ваш проект и нажмите на одну из картинок. Вы увидите, как эта картинка заполнит весь экран:

Ура! Мы реализовали просмотр фотографий! Вы можете выполнить двойное нажатие на картинке, а затем перемещаться по ней.

Когда ваш типо-безопасный универсальный сериализатор ответа инициализирует PhotoInfo, вы устанавливаете не только id или url свойства. На самом деле, есть еще несколько свойств, которые вы пока что не видели.

Щелкните по кнопке Menu в левом нижнем углу приложения и вы увидите некоторые подробные детали фотографии:

Нажмите в любую точку экрана, чтобы выйти из просмотра деталей.

Если вы знакомы с 500px.com, то вы знаете, что пользователи любят комментировать самые интересные фотографии. Теперь вы можете просматривать фотографии по отдельности. Давайте теперь реализуем сериализатор для комментариев.

Создаем сериализатор для коллекции комментариев

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

Откройте Five100px.swift и добавьте следующий код, под строкой import Alamofire:

@objc public protocol ResponseCollectionSerializable < static func collection(#response: NSHTTPURLResponse, representation: AnyObject) ->[Self] > extension Alamofire.Request < public func responseCollection(completionHandler: (NSURLRequest, NSHTTPURLResponse?, [T]?, NSError?) -> Void) -> Self < let responseSerializer = GenericResponseSerializer < request, response, data in let JSONResponseSerializer = Request.JSONResponseSerializer(options: .AllowFragments) let (JSON: AnyObject?, serializationError) = JSONResponseSerializer.serializeResponse(request, response, data) if let response = response, JSON: AnyObject = JSON < return (T.collection(response: response, representation: JSON), nil) >else < return (nil, serializationError) >> return response(responseSerializer: responseSerializer, completionHandler: completionHandler) > >

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

Единственное отличие в том, что этот протокол определяет функцию класса, которая возвращает коллекцию (а не один элемент), в нашем случае [Self]. Завершающий обработчик имеет коллекцию в качестве его третьего параметра — [T] и вызывает collection на тип, вместо инициализатора.

Продолжаем работать в нашем файле, замените класс Comment следующим кодом:

final class Comment: ResponseCollectionSerializable < @objc static func collection(#response: NSHTTPURLResponse, representation: AnyObject) ->[Comment] < var comments = [Comment]() for comment in representation.valueForKeyPath("comments") as! [NSDictionary] < comments.append(Comment(JSON: comment)) >return comments > let userFullname: String let userPictureURL: String let commentBody: String init(JSON: AnyObject) < userFullname = JSON.valueForKeyPath("user.fullname") as! String userPictureURL = JSON.valueForKeyPath("user.userpic_url") as! String commentBody = JSON.valueForKeyPath("body") as! String >>

Это заставляет Comment соответствовать протоколу ResponseCollectionSerializable так, что он работает с сериализатором ответа.

Теперь, все что вам нужно сделать — это использовать его. Откройте PhotoCommentsViewController.swift и добавьте вверх файла импорт:

import Alamofire

Теперь, добавьте следующий код в конец viewDidLoad():

Alamofire.request(Five100px.Router.Comments(photoID, 1)).validate().responseCollection() < (_, _, comments: [Comment]?, error) in if error == nil < self.comments = comments self.tableView.reloadData() >>

Это использует ваш новый сериализатор ответа для десериализации ответа NSData в коллекцию Comments, сохраняет комментарии в свойство и перезагружает table view.

Затем, добавьте следующий код в tableView(_:cellForRowAtIndexPath), прямо над строкой return cell:

cell.userFullnameLabel.text = comments![indexPath.row].userFullname cell.commentLabel.text = comments![indexPath.row].commentBody cell.userImageView.image = nil let imageURL = comments![indexPath.row].userPictureURL Alamofire.request(.GET, imageURL).validate().responseImage() < (request, _, image, error) in if error == nil < if request.URLString.isEqual(imageURL) < cell.userImageView.image = image >> >

Это позволяет отображать информацию из комментария в ячейке table view, а так же выстреливает второй запрос Alamofire, для загрузки изображения(аналогично тому, что вы делали в первой части).

Запустите ваше приложение, просмотрите фотографии и найдите ту, что имеет комментарии. Вы узнаете ее по цифре, отображаемой рядом с иконкой комменатриев. Нажмите на Comments и вы увидите, что комментарии к этой фотографии отображаются примерно так:

Отображение процесса загрузки

Ваше окно для просмотра фотографии имеет активную кнопку, по середине нижней планки. Она отображает UIActionSheet, который должен позволить вам скачать фотографию, но сейчас ничего не происходит. До этого момента вы могли загружать фотографии с 500px.com в вашу память, но как насчет того, чтобы у вас была возможность скачивать фотографии на ваше устройство?

Откройте PhotoViewerViewController.swift и замените пустой downloadPhoto() на следующий код:

func downloadPhoto() < // 1 Alamofire.request(Five100px.Router.PhotoInfo(photoInfo!.id, .XLarge)).validate().responseJSON() < (_, _, JSON, error) in if error == nil < let jsonDictionary = (JSON as! NSDictionary) let imageURL = jsonDictionary.valueForKeyPath("photo.image_url") as! String // 2 let destination = Alamofire.Request.suggestedDownloadDestination(directory: .DocumentDirectory, domain: .UserDomainMask) // 3 Alamofire.download(.GET, imageURL, destination: destination) >> >

Давайте разберем его по кусочкам и узнаем, что там внутри:

  • Сначала вы запрашиваете новый PhotoInfo, только на этот раз, вы спрашиваете размер XLarge этого изображения.
  • Получаете дефолтное расположение для сохранения файлов, что в нашем случае, будет поддерикторией Documents данного приложения. Имя файла будет таким же, как нам и предлагет сервер. destination является замыканием, но подробнее об этом поговорим чуть позже.
  • Alamofire.download(_:_:_) немного отличается от Alamofire.request(_:_), который не требует обработчика ответа или сериализатора, для определения действий с объектом данных. А все потому, что он уже знает как с ним поступить — конечно же сохранить на диск! Замыкание destination возвращает местоположение сохраненного изображения.

Запустите ваше приложение, найдите понравившееся фото, нажмите на него, а затем нажмите на кнопку Save. Не видите никакого результата? Просто пройдите на главный экран приложения и нажмите на вкладку Downloads.

Вы можете спросить: “Почему просто не передать постоянное положение для файла?”. А потому, что вы можете и не знать имени файла на момент его загрузки. В случае с 500px.com сервер всегда предлагает варианты 1.jpg, 2.jpg, 3.jpg, 4.jpg, 5.jpg, основываясь на размере файла. Вы не сможете сохранить фотографии с одним и тем же именем.

Вместо того, чтобы передавать постоянное положение файла в качестве строкового значения, вы передаете замыкание в качестве третьего параметра Alamofire.download. Затем Alamofire вызывает это замыкание в подходящее время, передавая temporaryURL и NSHTTPURLResponse, и ожидает экземпляр NSURL, который укажет на точное положение места хранения, куда есть доступ для записи.

Сохранили несколько фотографий, но посмотрели в Downloads, а там находится всего одна. Что же происходит?

Получается, что файлы не имеют уникальных имен, так что нам нужно реализовать свою логику именования файлов. Замените строку, которая идет сразу после “//2” в downloadPhoto() следующим кодом:

let destination: (NSURL, NSHTTPURLResponse) -> (NSURL) = < (temporaryURL, response) in if let directoryURL = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0] as? NSURL < return directoryURL.URLByAppendingPathComponent("\(self.photoInfo!.id).\(response.suggestedFilename)") >return temporaryURL >

И снова, let destination является замыканием. Но в этот раз вы снова реализовали свою логику именования файлов. Теперь вы используете id фотографии, которое захватывает замыкание из окружающего скопа и соединяете его с предложенным сервером-именем, разделяя их “.” между ними.

Запустите приложение: теперь вы можете хранить несколько фоток:

Загрузка фотографий на диск функционирует нормально, но будет еще удобнее, если во время загрузки у нас будет отображаться полоса прогресса. Alamofire позволяет очень просто реализовать нашу задумку.

Замените строку под комментарием “//3” в downloadPhoto() следующим кодом:

// 4 let progressIndicatorView = UIProgressView(frame: CGRect(x: 0.0, y: 80.0, width: self.view.bounds.width, height: 10.0)) progressIndicatorView.tintColor = UIColor.blueColor() self.view.addSubview(progressIndicatorView) // 5 Alamofire.download(.GET, imageURL, destination: destination).progress < (_, totalBytesRead, totalBytesExpectedToRead) in dispatch_async(dispatch_get_main_queue()) < // 6 progressIndicatorView.setProgress(Float(totalBytesRead) / Float(totalBytesExpectedToRead), animated: true) // 7 if totalBytesRead == totalBytesExpectedToRead < progressIndicatorView.removeFromSuperview() >> >

Давайте рассмотрим каждый пронумерованный комментарий по отдельности:

  • Вы используете стандартный UIProgressView для отображения прогресса загрузки фото. Настройте его и добавьте в свою схему документа.
  • С Alamofire вы можете соединить .progress(), который принимает замыкание, которое в свою очередь, периодически вызывается с тремя параметрами bytesRead, totalBytesRead и totalBytesExpectedToRead.
  • Разделите totalBytesRead на totalBytesExpectedToRead, и вы получите значение между 0 и 1, которое будет отображать ваш прогресс загрузки. Замыкание может выполняться множество раз. Если ваше время загрузки не слишком короткое, то вы получите отображаемый обновляемый процесс загрузки на экране.
  • После того, как весь процесс прошел, просто уберите полосу загрузки с экрана.

Запустите ваше приложение, найдите другое фото для сохранения и попробуйте его сохранить. У вас должно получиться вот так:

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

Обратите внимание, что downloadPhoto все еще используется .responseJSON в секции №1. Вот вам такой челендж, чтобы понять на сколько вы хорошо усвоили работу сериализаторов ответа: усовершенствуйте код выше, чтобы в нем использовался универсальный сериализатор, вместо responseObject(). Если вы хотите проверить ваше решение, то вы можете посмотреть на код ниже. Первые строки downloadPhoto() должны быть заменены на следующие:

Alamofire.request(Five100px.Router.PhotoInfo(photoInfo!.id, .XLarge)).validate().responseObject() < (_, _, photoInfo: PhotoInfo?, error) in if error == nil && photoInfo != nil < let imageURL = photoInfo!.url . . .

Таким образом вы переключили код на использование вашего пользовательского сериализатора ответа, вместо того, чтобы вручную парсить JSON, что делает ваш код чище.

Оптимизация и обновление

Хорошо! Самое время реализовать возможность обновления.

Откройте PhotoBrowserCollectionViewController.swift и замените func handleRefresh() следующим кодом:

func handleRefresh()

Этот код просто освобождает вашу модель self.photos и сбрасывает currentPage, а так же обновляет ваш UI.

Запустите приложение. Теперь потяните на себя пальцем и ваш UI обновится, таким образом вы увидите самые новые картинки подгруженные на 500px.com:

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

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

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

Откройте PhotoBrowserCollectionViewController.swift и добавьте следующий код прямо перед let refreshControl:

let imageCache = NSCache()

Это создает объект NSCache, который вы будете использовать для кэширования ваших изображений.

Затем, замените collectionView(_:cellForItemAtIndexPath:) следующим кодом:

override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell < let cell = collectionView.dequeueReusableCellWithReuseIdentifier(PhotoBrowserCellIdentifier, forIndexPath: indexPath) as! PhotoBrowserCollectionViewCell let imageURL = (photos.objectAtIndex(indexPath.row) as! PhotoInfo).url // 1 cell.request?.cancel() // 2 if let image = self.imageCache.objectForKey(imageURL) as? UIImage < cell.imageView.image = image >else < // 3 cell.imageView.image = nil // 4 cell.request = Alamofire.request(.GET, imageURL).validate(contentType: ["image/*"]).responseImage() < (request, _, image, error) in if error == nil && image != nil < // 5 self.imageCache.setObject(image!, forKey: request.URLString) // 6 cell.imageView.image = image >else < /* If the cell went off-screen before the image was downloaded, we cancel it and an NSURLErrorDomain (-999: cancelled) is returned. This is a normal behavior. */ >> > return cell >

Давайте пройдемся по коду от комментария к комментарию:

  • Ячейка, которая уже вышла из очереди, вполне возможно, уже имеет какой-либо request от Alamofire. Вы можете просто отменить его, так как он больше уже не валиден для новой созданной ею ячейки.
  • Опциональная привязка нужна для проверки наличия фотографии в кэше, перед тем, как просто закачать ее снова.
  • Если у вас нет версии фотографии в кэше, то вы просто ее скачиваете. Однако, если ваша ячейка, вышедшая из очереди, имеет некоторое изображение, то просто присвойте ей nil, чтобы она стала пустой, в то время как необходимое изображение будет загружаться.
  • Скачайте фотографию с сервера, но на этот раз проверьте content-type вернувшегося ответа. Если ответ не является изображением, то error будет содержать значение, и таким образом вы ничего не будете делать с потенциально невалидными ответом изображения. Смысл в том, что вы храните объект Alamofire request в ячейке для того, чтобы использовать его, когда ваш асинхронный вызов вернется.
  • Если вы не получили ошибки и загрузили нужное вам фото, то сохраните его в кэше.
  • Установите изображение ячеек соответственно.

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

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

Вот конечный вариант второй части туториала по Alamofire. В нем вы найдете другие особенности, которые не были раскрыты в этом туториале.

Если вы прошли с нами обе части обучения работе с Alamofire, то у вас должно было сложиться хорошее понимание основ работы с ним. Вы разобрались как создавать змеевидные методы запросов/ответов, ваши собственные сериализаторы ответа, роутеры и шифрованные URL-адреса с кодировкой параметра URL, скачивание файлов на диск, и как использовать замыкание прогресса, и проверенные ответы. Это уже достаточно приличный список ваших достижений! ;]

Так же, Alamofire может проверять подлинность серверов, используя различные схемы. Закачивать файлы и потоки, чего не было продемонстрировано в этих туториалах. Но с вашими новыми знаниями по Alamofire вы можете самостоятельно изучить эти аспекты.

На данный момент Alamofire не такой продвинутый как AFNetworking, но если вы создаете новый проект, используя Alamofire, то это только доставит вам удовольствие, потому что его можно использовать во множестве типичных случаях. Расширение UIKit — является одним из самых популярных расширений AFNetworking, которого так не хватает в Alamofire. Но никто вам не говорил, что эти две библиотеки не уживаются мирно в одном проекте!

Если вы использовали AFNetworking до этого и не можете жить без набора setImageWithURL методов категории для UIKit, то вы можете продолжать использовать AFNetworking в ваших проектах. Например, вы можете использовать Alamofire для вызовов серверных API, а затем использовать AFNetworking для асинхронного отображения изображений. AFNetworking имеет общий кэш, так что вам не нужно вручную кэшировать или отменять ваши запросы.

Что дальше?

Дальше, вы можете продолжить изучать наши туториалы по мере их появления, а также, параллельно читать перевод официальной книги по языку программирования Swift. И, для более подробного изучения языка, вы можете пройти наши курсы!

Урок подготовил: Иван Акулов

Alamofire что это

Alamofire — сетевая библиотека HTTP, написанная на Swift. Ее назначение — упростить работу с API в клиент-серверных приложениях для iOS и Mac OS X. Alamofire включает набор готовых методов запросов/ответов, инструменты для сериализации, аутентификации и других функций, которые нужны вашему приложению для взаимодействия с внешними службами, от облачных хранилищ до социальных сетей и рекламных сервисов.

Технология ALAMOFIRE.

Три причины использовать Alamofire для своего проекта
Простое готовое решение

В Swift есть URLSession и другие нативные классы для работы с HTTP, их можно применять напрямую, но с Alamofire вашим разработчикам будет нужно написать в десятки раз меньше кода. Это не только быстрее и удобнее, но также снижает вероятность случайных ошибок, в том числе благодаря встроенным инструментам проверки, и в целом упрощает совместную работу нескольких специалистов.

Для всех запросов

Alamofire даст вашим разработчикам возможность просто организовывать разные типы взаимодействий. Это как стандартные запросы Get, Post, Put для получения, создания или обновления данных соответственно, так и более сложные функции для выгрузки и загрузки файлов разными методами. Также Alamofire позволяет просто создавать .request — любые нужные вашему проекту HTTP-запросы не связанные с передачей файлов.

Асинхронность

Alamofire соответствует принципам REST — общепринятым правилам архитектуры веб-API. Соблюдение этого стандарта не просто хорошая практика, но и гарантия того, что все внешние интеграции приложения можно будет организовать просто и без лишних действий, как например, отслеживание данных по запросам.

Пример использования технологии ALAMOFIRE.

Заключение

Alamofire — очень полезная библиотека для клиент-серверных приложений на iOS. Она написана на Swift и учитывает все особенности проектов на этом языке. Чтобы работать с ней не нужно специфических знаний, достаточно разбираться в сетевых классах Apple, что умеют все хорошие iOS разработчики.

Alamofire избавляет ваших программистов от необходимости вручную прописывать HTTP, JSON и REST для веб-сервисов приложения. Эта библиотека рекомендована даже проектам с небольшим количеством взаимодействий с внешними сервисами. Alamofire — удобная оболочка для нативных инструментов обработки данных от сервера, и де-факто стандарт iOS-разработки. Его использование в любом случае ускорит разработку и тестирование, а также стабильность и производительность готового приложения.

Разработка мобильных приложений
Разработка мобильных приложений

Разрабатываем приложения, которые решают бизнес-задачи клиентов и упрощают жизнь пользователей. Даже если эти задачи — со звёздочкой. Доставляем их в каждую ладошку и карман.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *