Alamofire на примере

Пример использования библиотеки Alamofire

Задача: создать iOS приложение в среде разработки Xcode 9 на языке программирования Swift 4, которое посылает HTTP запрос, а в ответ получает данные, которые обрабатывает и отображает в виде таблицы. После разработки у нас получится такое приложение.

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

Добавление Alamofire в проект

Я не буду подробно останавливаться на процессе добавления сторонних библиотек при помощи Cocoapods, однако если читатели в комментариях укажут, что скриншотов, которые следуют ниже, не достаточно, то я сделаю отдельный пост.

По шагам это выглядит так:

  • Добавьте в проект пустой файл и назовите его Podfile
  • Добавьте содержимое во вновь созданный файл:
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '10.0'
use_frameworks!

target 'AlamofireExample' do // Тут название вашего проекта. У меня это AlamofireExample
pod 'Alamofire', '~> 4.5'
end
  • Закройте Xcode
  • Откройте Terminal и перейдите в каталог с проектом
  • Наберите команду pod install (Все должно получится, если у вас в системе установлен Cocoapods. Если нет, то на официальном https://cocoapods.org сайте можно найти инструкцию по установке
  • После завершения скачивания библиотеки Alamofire в каталоге с проекта появится файл с названием вашего проекта, но расширение у него будет xworkspace. Сейчас и далее для работы с проектом в Xcode  нужно запускать и загружать именно этот файл.

Добавление кнопки в проект

Из библиотеки объектов перетащите кнопку на контроллер, задайте этой кнопке нужные цвета, размеры и ограничения. Создайте для кнопки соответствующий метод в контроллере, который будет вызываться при нажатии на эту кнопку. У меня получилось так:

В класс ViewController добавьте строку с импортом Alamofire

//
// proSwift.ru
//
// Swift 4

import UIKit
import Alamofire

Теперь добавьте код в только что созданный метод:

//
// proSwift.ru
//
// Swift 4

    @IBAction func sendRequest(_ sender: UIButton) {
        Alamofire.request("https://jsonplaceholder.typicode.com/photos").responseJSON { response in
            guard response.result.isSuccess else {
                print("Ошибка при запросе данных \(String(describing: response.result.error))")
                return
            }
            print(response.value)
        }
    }

Внимательно посмотрите на этот код. По сути, вся работа с сетью уложилась в одну строку. Давайте разбирать по частям, а первую строку методы мы тоже раздробим для понимания. Итак, у класса Alamofire есть метод request, который в качестве параметров принимает адрес, по которому следует послать запрос. (Кстати, адрес ссылается на сервис, который генерирует ответы на запросы. Этот сервис создан специально для разработчиков. Он возвращает структурированные данные, с которыми можно работать.) Этот метод принимает еще четыре параметра, с помощью которых можно кастомизировать запрос (изменить метод запроса, параметры, заголовки и кодировку), но для нашей задачи нам это не нужно и мы не будем это использовать.

Однако следует сказать, что при таком создании запроса по умолчанию устанавливается метод GET. Эту строку можно было записать по-другому:

//
// proSwift.ru
//
// Swift 4

    @IBAction func sendRequest(_ sender: UIButton) {
        Alamofire.request("https://jsonplaceholder.typicode.com/photos", method: .get).responseJSON { response in
            guard response.result.isSuccess else {
                print("Ошибка при запросе данных \(String(describing: response.result.error))")
                return
            }
            print(response.value)
        }
    }

Эти два примера будут работать одинаково. Думаю, понятно как изменить метод, если понадобится.

Под капотом Alamofire этот метод возвращает объект DataRequest. Мы к нему моем применить метод responseJSON. Этот метод преобразует полученные данные в запросе в массив объектов, с которыми мы будем работать позже.

Еще одной особенностью Alamofire является то, что запросы выполняются в фоновом потоке. И это логично, ведь не стоит заставлять пользователя ждать и замораживать интерфейс на время, пока выполняется запрос. Если объем данных небольшой, а скорость интернета высока, то это не будет заметно, однако если произойдет какая-нибудь ошибка, то «зависание» интерфейса при выполнении запроса в основном потоке может сильно разочаровать пользователя. Еще раз повторюсь, т.к. это очень важно — запросы Alamofire посылает и обрабатывает ответ на них в фоновом потоке.

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

Я не сомневаюсь, что для новичков это сложно для понимания. Но осознание функционального программирования придет со временем. Если говорить простым языком, то в функцию или метод (надеюсь знаете в чем отличие) можно передавать параметры — мы это делаем каждый день — передаем целые или строки или объекты. Но также в качестве параметров можно передавать и другие функции. Причем в данном случае (запрос удаленных данных) даже понятно зачем это делать. Т.е. мы предаем ту функцию, которую мы хотим запустить, после получения данных из запроса. А функция эта будет оперировать данными, которые получили.

Надеюсь не запутал, а все-таки объяснил, что именно тут присходит. В любом случае жду комментарии с пожеланиями и вопросами.

Объект response, с которым мы работаем содержит ответ сервера на наш запрос. Для начала мы проверили, что запрос успешен и если нет, ты выдали описание ошибки в консоль и вышли из метода. Если запрос прошло успешно то просто вывели его в консоль.

Давайте наконец скомпилируем и запустим наш проект и посмотрим что получилось. После нажатия на кнопку в консоль должна вывалиться куча информации, похожая на это:

(
        {
        albumId = 1;
        id = 1;
        thumbnailUrl = "http://placehold.it/150/92c952";
        title = "accusamus beatae ad facilis cum similique qui sunt";
        url = "http://placehold.it/600/92c952";
    },
        {
        albumId = 1;
        id = 2;
        thumbnailUrl = "http://placehold.it/150/771796";
        title = "reprehenderit est deserunt velit ipsam";
        url = "http://placehold.it/600/771796";
    },
        {
        albumId = 1;
        id = 3;
        thumbnailUrl = "http://placehold.it/150/24f355";
        title = "officia porro iure quia iusto qui ipsa ut modi";
        url = "http://placehold.it/600/24f355";
    },
        {
        albumId = 1;
        id = 4;
        thumbnailUrl = "http://placehold.it/150/d32776";
        title = "culpa odio esse rerum omnis laboriosam voluptate repudiandae";
        url = "http://placehold.it/600/d32776";
    },
      ....

Если в консоле подобная информация, то все работает правильно.



Добавление дополнительных элементов

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

Таблица

Из библиотеки объектов перетащите таблицу на контроллер, разместите ее и задайте размеры и ограничения. Также создайте outlet — свойство для работы с этой таблицей.

 

Не забудьте создать связи между таблицей и контроллером, чтобы работали методы UITableViewDelegate и UITableViewDataSource. Для этого выполните ctrl-перетаскивание от таблицы к значку контроллера и отметьте нужны пункты:

Класс ячейки таблицы

Из библиотеки объектов перетащите ячеку в таблицу. Настройте высоту ячейки (я сделал 64), и разместите на этой ячейке нужные элементы — четыре надписи. Если есть желание, — измените цвета. Задайте размеры и ограничения.  ОБЯЗАТЕЛЬНО задайте ReuseIdentifier — я указал «Cell«

В новый файл или в файл с контроллером (ViewController.swift) добавьте определения класса ячейки таблицы:

// proSwift.ru
//
//
// Swift 4

class ItemCell: UITableViewCell {
    
}

После этого создайте outlet-свойства для всех надписей в ячейке. У меня проект выглядит так:

Класс ячейки у меня выглядит так:

// proSwift.ru
//
//
// Swift 4

class ItemCell: UITableViewCell {
    
    @IBOutlet weak var idLabel: UILabel!
    @IBOutlet weak var albumIdLabel: UILabel!
    @IBOutlet weak var urlLabel: UILabel!
    @IBOutlet weak var titleLabel: UILabel!
    
}

Класс структуры

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

// proSwift.ru
//
//
// Swift 4 


struct Item {

    let albimID: Int
    let id: Int
    let title: String
    let url: String

}

Методы протоколов UITableViewDataSource и UITableViewDelegate и свйоство.

В класс контроллера добавьте свойство для хранения элементов

// proSwift.ru
//
//
// Swift 4
    
fileprivate var items = [Item]()
    

Методы протоколов я добавил в расширении:

// proSwift.ru
//
//
// Swift 4

extension ViewController: UITableViewDataSource, UITableViewDelegate {
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return items.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! ItemCell
        configureCell(cell: cell, for: indexPath)
        return cell
    }
    
    private func configureCell(cell: ItemCell, for indexPath: IndexPath) {
        let item = items[indexPath.row]
        cell.idLabel.text = "\(item.id)"
        cell.albumIdLabel.text = "\(item.albimID)"
        cell.urlLabel.text = item.url
        cell.titleLabel.text = item.title
    }
}

Думаю, подробные объяснения не требуются — первый метод устанавливает количество строк по поличеосту элементов в массиве с объектами. Второй метод — метод который возвращает ячейку таблицы для нужной строки и третий метод — это метод заполнения ячейки нужными данными.

 

Обработка данных

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

Перейдите к методу, который срабатывает по нажатию на кнопку. Найдите ветку, в которой мы выводим в консоль данные, полученные при запросе и замените строку print() на следующий код:

// proSwift.ru
//
//
// Swift 4

    @IBAction func sendRequest(_ sender: UIButton) {
        Alamofire.request("https://jsonplaceholder.typicode.com/photos", method: .get).responseJSON { response in
            guard response.result.isSuccess else {
                print("Ошибка при запросе данных\(String(describing: response.result.error))")
                return
            }
            
            guard let arrayOfItems = response.result.value as? [[String:AnyObject]]
                else {
                    print("Не могу перевести в массив")
                    return
            }
            
            for itm in arrayOfItems {
                let item = Item(albimID: itm["albumId"] as! Int, id: itm["id"] as! Int, title: itm["title"] as! String, url: itm["url"] as! String)
                self.items.append(item)
            }
        }
    }

Давайте разбирать. Мы взяли данные из response.result.value и пробуем перевести их в массив словарей, с ключом типа String и значением типа AnyObject. Формат такой именно потому, что данные приходят именно так, — приходит множество словарей, каждый из которых описывает один элемент. Если по какой-либо причине данные пришли в другом формате, то в консоль выйдет сообщение с ошибкой и выполнение метода прекратится.

Затем цикл перебирает элементы по одному, на основании данных из них создает объекты Item и помещает эти объекты в массив (свойство для хранения элементов для таблицы).

Запустите проект, нажмите на кнопку…  и вы увидите, что ничего не произойдет:

 

Догадались почему?

Давайте вспомним все, что было написано выше немного по-рассуждаем… Таблица создается во время запуска приложения. В этот момент данных у нас нет. Данные появляются только после нажатия на кнопку. И то, нужно некоторое время на их получение, обработку и добавление в массив items.  Нужно запустить метод таблицы reloadData() для отображения полученных данных. Но этот метод нужно запустить уже после заполнения массива.  Не забудьте, что запустить его нужно в основном потоке, т.к. мы помним, что запросы и обработка данных выполняется в фоновом потоке, а все методы работы с интерфейсом должны выполняться только в основном. У нас есть функция, которая выполняется после выполнения запроса. Давайте туда и поместим вызов метода обновления данных для таблицы.

// proSwift.ru
//
//
// Swift 4

    @IBAction func sendRequest(_ sender: UIButton) {
        Alamofire.request("https://jsonplaceholder.typicode.com/photos", method: .get).responseJSON { response in
            guard response.result.isSuccess else {
                print("Ошибка при запросе данных\(String(describing: response.result.error))")
                return
            }
            
            guard let arrayOfItems = response.result.value as? [[String:AnyObject]]
                else {
                    print("Не могу перевести в массив")
                    return
            }
            
            for itm in arrayOfItems {
                let item = Item(albimID: itm["albumId"] as! Int, id: itm["id"] as! Int, title: itm["title"] as! String, url: itm["url"] as! String)
                self.items.append(item)
            }
            
            DispatchQueue.main.async {
                self.tableView.reloadData()
            }
        }
    }

Запускайте проект, нажимаетй на кнопку и наблюдайте результат.

Код всего файла ViewController.swift

//
// proSwift.ru
//
// Swift 4

import UIKit
import Alamofire

class ViewController: UIViewController {
    
    fileprivate var items = [Item]()

    @IBOutlet weak var tableView: UITableView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }

    @IBAction func sendRequest(_ sender: UIButton) {
        Alamofire.request("https://jsonplaceholder.typicode.com/photos", method: .get).responseJSON { response in
            guard response.result.isSuccess else {
                print("Ошибка при запросе данных\(String(describing: response.result.error))")
                return
            }
            
            guard let arrayOfItems = response.result.value as? [[String:AnyObject]]
                else {
                    print("Не могу перевести в JSON")
                    return
            }
            
            for itm in arrayOfItems {
                let item = Item(albimID: itm["albumId"] as! Int, id: itm["id"] as! Int, title: itm["title"] as! String, url: itm["url"] as! String)
                self.items.append(item)
            }
            
            DispatchQueue.main.async {
                self.tableView.reloadData()
            }
        }
    }
    
    
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }


}

extension ViewController: UITableViewDataSource, UITableViewDelegate {
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return items.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! ItemCell
        configureCell(cell: cell, for: indexPath)
        return cell
    }
    
    private func configureCell(cell: ItemCell, for indexPath: IndexPath) {
        let item = items[indexPath.row]
        cell.idLabel.text = "\(item.id)"
        cell.albumIdLabel.text = "\(item.albimID)"
        cell.urlLabel.text = item.url
        cell.titleLabel.text = item.title
    }
}

class ItemCell: UITableViewCell {
    
    @IBOutlet weak var idLabel: UILabel!
    @IBOutlet weak var albumIdLabel: UILabel!
    @IBOutlet weak var urlLabel: UILabel!
    @IBOutlet weak var titleLabel: UILabel!
    
}


struct Item {
    let albimID: Int
    let id: Int
    let title: String
    let url: String
}

Ссылка проекта на GitHub




14 Comments on “Alamofire на примере

  1. > Добавьте в проект пустой файл и назовите его Podfile
    Куда именно ? Методом проб и ошибок выявилось, что podfile должен лежать на одном уровне с .xcodeproj

    > Откройте Terminal и перейдите в каталог с проектом
    Опять же на какой уровень ? У меня начало работать — см. выше

    > После завершения скачивания библиотеки Alamofire
    Здесь нужно предупредить читателя, что процедура может занять до 1 часа, всё это время cocoapods безапеляционно выполняет следующие действия: скачивание, распаковка, проверка

    > появится файл с названием вашего проекта, но расширение у него будет xworkspace
    Хотелось бы получить содержимое этого файла с описанием, так как не представляется возможным проверить корректность этого файла (а, значит, установки библиотеки) без какого-либо понимания «что должно быть-то ?»

    И да, у меня все папки есть, а в проект подключить Alamofire не могу — не видит библиотеку

  2. И ААААбязатЕльнА нужно сказать, что после установки библиотеки требуется УДАЛИТЬ Podfile из проекта
    Иначе библиотеку XCode не увидит

    • Файл можно удалить, но дело в том, что Podfile является частью системы управления зависимостями. Удалять его точно не обязательно, особенно если Вы планируете обновлять сторонние модули и библиотеки, которые подключили к проекту. Например, если разработчик выпустит новую версию библиотеки (а Alamofire обновляется с завидной регулярностью), то без Podfile Вам придется обновлять исходники вручную.

  3. И это не всё ! После нужно выполнить Product -> Clean, затем Product -> Build
    вот тогда библиотека будет видна

    • Данный рецепт помогает во многих случаях, при которых Xcode глючит, не только тогда, когда не видит Alamofire

    • Это обычно не нужно если тип сборки проекта установлен Legacy, но вредным не будет, учитывая какие глюки бывают 😀

  4. Добрый день, я со своего сервера получаю данные Json в таком виде:

    {«ID»:[100,101],»MyName»:[«mnacord 0″,»mnacord 1″],»MyValue»:[«0″,»1»]}

    и эта часть кода не преобразовывает его в массив:
    guard let arrayOfItems = response1.result.value as? [[String: AnyObject]]

    скажите пожалуйста в чем может быть дело ? Спасибо.

    • Не совпадает структура данных в JSON
      Я вижу ключ и массив с данными по нему.
      В Вашем случае следует попробовать
      … as? [String: [AnyObject]]
      И соответственно код извлечения данных нужно будет изменить, т.е. получать массив по ключу и делать итерацию по его элементам.

      • Спасибо большое,получилось. Теперь стоит вопрос как из arrayOfItems вынуть данные и передать их структуре Item. И еще, данные от сервера я получаю на Русском, и они не читаются. Какой енкодинг нужно ставить ?

        • Это то, о чем я и писал,

          в цикле
          for itm in arrayOfItems {
          let item = Item(albimID: itm[«albumId»] as! Int, id: itm[«id»] as! Int, title: itm[«title»] as! String, url: itm[«url»] as! String)
          self.items.append(item)
          }

          Вам следует изменить строку let item = …
          Более того, если у вас item не содержит свойства, в которых помещается массив, то Вам следует делать цикл в цикле для получения и переприсвоения данных item.
          Мне сложно рассуждать, т.к. я не вижу структуру Вашего item.

          А что касается кодировки — то используйте ту, в которой отправляет сервер.

          • Дело в том,что я плохо представляю тип переменной arrayOfItems.

          • Помогите пожалуйста, никак не получается преобразовать это :guard let arrayOfItems = response1.result.value as? [[String: AnyObject]]

            в это
            struct Item {
            let MyName: String
            let MyValue: String
            }
            Спасибо.

Добавить комментарий для Юзер Отменить ответ

Ваш адрес email не будет опубликован.

*

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.