Swift код к статьe Tony DiPasquale “Эффективный JSON с функциональными коцепциями и дженериками в Swift”

В этом посте представлен Swift код, который сопровождал изучение и перевод статей Tony DiPasquale . Статьи переведены на русский и представлены на этом сайте.

Вот эти статьи:

  1. “Эффективный JSON с функциональными концепциями и дженериками в Swift”  (перевод) – оригинал  Efficient JSON in Swift with Functional Concepts and Generics. Код Swift, сопровождающий изучение этой статьи , представлен в этом посте.
  2.  “Реальный мир парсинга JSON в Swift” (перевод) – оригинал Real World JSON Parsing with Swift . Код Swift, сопровождающий изучение этой статьи , представлен в этом посте.
  3. “Парсинг вложенных JSON  и массивов (Arrays) в Swift.” (перевод) – оригинал Parsing Embedded JSON and Arrays in Swift.  . Код Swift, сопровождающий изучение этой статьи , представлен в этом посте.

Автор предлагает Swift код в самих статьях, но это, как правило, только результирующий вариант с очень малым количеством примеров. Кроме того, хотелось отследить сам процесс превращения функции парсинга JSON-данных в Модель из тривиального и громоздкого "if-let" варианта в компактный  вариант “функциональных концепций”. Последний вариант представлен в виде цепочки трансформаций, начинающихся с запроса в “сеть” и заканчивающихся заполнением Модели данными. Он выдает на выходе не просто Модель, а Result <Модель>, включающий в себя помимо самой Модели, всевозможные ошибки на различных этапах трансформации.

Хотелось более рельефно увидеть как справляется вариант “функциональных концепций” с этими ошибками, потому что иногда в  статьях с целью краткости и простоты изложения приводится лишь набросок кода, иллюстрирующий ту или иную идею. Он не всегда компилируется в Swift. Кроме того Swift – еще молодой язык и имеет некоторые ошибки, о которых известно, и которые в дальнейшем будут устранены. Но сейчас, чтобы сделать код работающим, требуются некоторые дополнительные усилия.

Эксперименты с алгоритмами Тони ДеПаскуале проводились в Swift 1.1, и представлены в репозитории на Github, который включает в себя различные  Playground файлы и полноценное небольшое приложение для работы с данными с сайта Flickr.com.

Как говорилось на странице “Изучаем Swft” при тестировании алгоритмов, представленных в статьях, мы вначале следуем всем примерам, которые представлены в самих статьях, а затем проверяем возможности алгоритмов на двух “контрольных” данных:

Первый тип тестовых данных приведен в статье Chris Eidhof  “Parsing JSON in Swift. Safe and easy” и представляет собой массив из двух блогов:

 {
    "stat": "ok",
    "blogs": {
        "blog": [
            {
                "id" : 73,
                "name" : "Bloxus test",
                "needspassword" : true,
                "url" : "http://remote.bloxus.com/"
            },
            {
                "id" : 74,
                "name" : "Manila Test",
                "needspassword" : false,
                "url" : "http://flickrtest1.userland.com/"
            }
        ]
    }
}

Второй тип тестовых данных находится на Flickr.com и представляет собой данные в формате JSON  о 100 топ местах, в которых сделаны фотографии, размещенные на Flickr.com:

{
    places =     {
        "date_start" = 1414108800;
        "date_stop" = 1414195199;
        place =         [
                        {
                "_content" = "London, England, United Kingdom";
                latitude = "51.506";
                longitude = "-0.127";
                "photo_count" = 1661;
                "place_id" = "hP_s5s9VVr5Qcg";
                "place_type" = locality;
                "place_type_id" = 7;
                "place_url" = "/United+Kingdom/England/London";
                timezone = "Europe/London";
                "woe_name" = London;
                woeid = 44418;
            },
                        {
                "_content" = "Sao Paulo, SP, Brazil";
                latitude = "-23.562";
                longitude = "-46.654";
                "photo_count" = 119;
                "place_id" = 0RylrWxVV79p49E;
                "place_type" = locality;
                "place_type_id" = 7;
                "place_url" = "/Brazil/Sao+Paulo/S%C3%A3o+Paulo/in-S%C3%A3o+Paulo";
                timezone = "America/Sao_Paulo";
                "woe_name" = "Sao Paulo";
                woeid = 455827;
            },
..........................

Для считывания данных с Flickr.com использовался Flickr API, представленный в курсе Стэнфордского Университета “Stanford CS 193P iOS 7”, написанный на Objective-C, так что попутно нам пришлось осуществить доступ к Objective-C классам из Swift.

У нас 3 статьи и на 5 Playgrounds  представлены Swift коды.

Начнем по порядку. Результаты изучения статьи “Эффективный JSON с функциональными концепциями и дженериками в Swift” представлены в файле  EfficientJSON.playground. Это тестирование проходит строго в соответствии с тем, как выдвигаются идеи в статье.

В этой статье рассматривается очень простая Модель данных, в которую необходимо преобразовать текстовые JSON-данные:

struct User {
    let id: Int = 0
    let name: String = ""
    let email: String = ""
}

Под простотой Модели подразумевается, что в ней нет “контейнеров” (словарей  и массивов), содержащих вложенные JSON-данные, а также Optional атрибутов.

Поскольку в статье поставлена задача провести это преобразование с отслеживанием “ошибок” на всех этапах преобразования, то для проверки алгоритмов проводилась имитация трех вариантов JSON-данных:
1. Правильные данные

//      ----- Тест 1 - правильные данные -----

let jsonString: String =
        "{ \"id\": 1, \"name\":\"Cool user\",  \"email\": \"u.cool@example.com\" }"

let jsonData: NSData? = jsonString.dataUsingEncoding(NSUTF8StringEncoding,
                                              allowLossyConversion: true)

2. Неправильные данные ( лишняя фигурная скобка )

//      ----- Тест 2 - неправильные данные (лишняя фигурная скобка) -----

let jsonString3: String =
        "{ {\"id\": 1, \"name\":\"Cool user\",\"email\": \"u.cool@example.com\" }"

let jsonData3: NSData? = jsonString3.dataUsingEncoding(NSUTF8StringEncoding,
                                                 allowLossyConversion: true)

3. Неправильные данные ( вместо “id” “id1” )

//      ----- Тест 3 - неправильные данные ( вместо "id" "id1") -----

let jsonString4: String =
        "{ \"id1\": 1, \"name\":\"Cool user\",\"email\": \"u.cool@example.com\" }"

let jsonData4: NSData? = jsonString4.dataUsingEncoding(NSUTF8StringEncoding,
                                                allowLossyConversion: true)

Для упрощения работы с ошибками создали удобный инициализатор в “расширении” класса NSError (в статье его нет):

// ------------ Возврат ошибки NSError ----
// Для упрощения работы с классом NSError создаем "удобный" инициализатор
//  в расширении класса

extension NSError {
    convenience init(localizedDescription: String) {
        self.init(domain: "", code: 0,
            userInfo: [NSLocalizedDescriptionKey: localizedDescription])
    }
}

Заставим структуру User подтвердить протокол Printable и сделаем ее печатаемой:

// ~~~~~~~~~~~~~~ Модель User ~~~~~~~~~~~~~~~~~
struct User: Printable {
    let id: Int = 0
    let name: String = ""
    let email: String = ""

    var description : String {
        return "User { id = \(id), name = \(name), email = \(email)}"
    }
}

Первоначально перед нами предстает примитивный “if-let" вариантScreen Shot 2014-10-30 at 6.40.12 PMи его соответствующий вызов для правильных тестовых данных:

//      ----- Тест 1 правильные данные------

getUser0(jsonData){ user in
    println("\(user.description)")
    return // для удаления ошибки closure с одной строкой
}

Результат печати:

User { id = 1, name = Cool user, email = u.cool@example.com}

Затем добавляем “управление ошибками”.

Для этого используется первая концепция функционального программирования – тип данных Eithert<A,B>  , который в нашем случае, когда нужно обрабатывать ошибки, превращается в тип  Result<A, E>, который используется для возврата и дальнейшего распространения по цепочке преобразований ошибок. Тип Result<A,E> -это перечисление enum c двумя возможными и взаимоисключающими состояниями :  Ok(A), представляющему “успех” (success) и содержащему значение value, и Err(E), представляющему “ошибку” ( error ) и содержащему ошибочное значение error. В Swift “ошибочное” состояние моделируется с помощью фиксированного класса NSError ( ), поэтому остается один “дженерик” и приходим к перечислению  enum Result<A>.

В настоящий момент в Swift перечисления enum не могут быть дженериками (generic) на самом топовом уровне, но,  как было сказано в статье, могут быть представлены как generic, если их обернуть в “постоянный” class  box:

//~~~~~~~~~~~~~~~~ Управление ошибками с помощью enum Result<A> ~~~~~~~

enum Result<A> {
    case Error(NSError)
    case Value(Box<A>)

    init(_ error: NSError?, _ value: A) {
        if let err = error {
            self = .Error(err)
        } else {
            self = .Value(Box(value))
        }
    }
}

final class Box<A> {
    let value: A

    init(_ value: A) {
        self.value = value
    }
}

Мы получаем второй вариант парсинга JSON в Модель User, при котором возвращается не User , а Result<User> (что нам и нужно):Screen Shot 2014-10-30 at 8.56.28 PM

Нам захочется проверить, как  функция getUser1 (...)  работает с ошибками, поэтому попробуем 3 приведенных выше типа тестовых данных:

//      ----- Тест 1 - правильные данные -----

let jsonString2: String =
        "{ \"id\": 1, \"name\":\"Cool user\",\"email\": \"u.cool@example.com\" }"

let jsonData2: NSData? = jsonString2.dataUsingEncoding(NSUTF8StringEncoding,
                                                 allowLossyConversion: true)

getUser1(jsonData2 ){ user in
    let a = stringResult(user)
    println("\(a)")
}

Результат печати:

User { id = 1, name = Cool user, email = u.cool@example.com}
//      ----- Тест 2 - неправильные данные (лишняя фигурная скобка) -----

let jsonString3: String =
        "{ {\"id\": 1, \"name\":\"Cool user\",\"email\": \"u.cool@example.com\" }"

let jsonData3: NSData? = jsonString3.dataUsingEncoding(NSUTF8StringEncoding,
                                                 allowLossyConversion: true)

getUser1(jsonData3 ){ user in
    let a = stringResult(user)
    println("\(a)")
}

Результат печати:

The operation couldn’t be completed. (Cocoa error 3840.)
//      ----- Тест 3 - неправильные данные ( вместо "id" "id1") -----

let jsonString4: String =
        "{ \"id1\": 1, \"name\":\"Cool user\",\"email\": \"u.cool@example.com\" }"

let jsonData4: NSData? = jsonString4.dataUsingEncoding(NSUTF8StringEncoding,
                                                allowLossyConversion: true)

getUser1(jsonData4 ){ user in
    let a = stringResult(user)
    println("\(a)")
}

Результат печати:

Отсутствуют компоненты User

Теперь убираем дерево проверки типов.

Для этого создаем отдельные парсеры для определенных типов данных используем оператор >>> ( bind )

//~~~~~~~~~~~~~~~~ Уничтожаем проверку типов ~~~~~~~

typealias JSON = AnyObject
typealias JSONDictionary = Dictionary<String, JSON>
typealias JSONArray = Array<JSON>

infix operator >>> { associativity left precedence 150 }

func >>><A, B>(a: A?, f: A -> B?) -> B? {
    if let x = a {
        return f(x)
    } else {
        return .None
    }
}

func JSONString(object: JSON) -> String? {
    return object as? String
}

func JSONInt(object: JSON) -> Int? {
    return object as? Int
}

func JSONObject(object: JSON) -> JSONDictionary? {
    return object as? JSONDictionary
}

Получаем третий вариант парсинга JSON в Модель User.

Screen Shot 2014-10-30 at 10.03.11 PM

Вводим новые операторы функционального программирования <^> (Applicative’s apply)  и  <*> (Functor’s fmap, обычно  <$>)

//--------------------- Новые операторы <^>   и  <*>  ---

infix operator <^> { associativity left } // Functor's fmap (usually <$>)
infix operator <*> { associativity left } // Applicative's apply

func <^><A, B>(f: A -> B, a: A?) -> B? {
    if let x = a {
        return f(x)
    } else {
        return .None
    }
}

func <*><A, B>(f: (A -> B)?, a: A?) -> B? {
    if let x = a {
        if let fx = f {
            return fx(x)
        }
    }
    return .None
}

 и используем “каррированную” ( curried ) "обертку”, функцию  func create ...,  инициализатора структуры struct User. 

// ~~~~~~~~~~~~~~ Модель User ~~~~~~~~~~~~~~~~~
struct User: Printable {
    let id: Int = 0
    let name: String = ""
    let email: String = ""

    var description : String {
        return "User { id = \(id), name = \(name), email = \(email)}"
    }

    static func create(id: Int)(name: String)(email: String) -> User {
        return User(id: id, name: name, email: email)
    }
}

Обратите внимание, что “каррирование ” в  Swift достигается представлением входных параметров функции в отдельных круглых скобках для каждого параметра:Получаем третий вариант парсинга JSON в Модель User:

Screen Shot 2014-11-01 at 3.01.07 PM

Уничтожаем многочисленные “return” (которых целых 3 в предыдущем варианте парсинга JSON). Идея заключается в том, что мы имеем цепочку  преобразований, и на каждом этапе в качестве выходного значения получаем либо Result <A>, либо Optional.  Если бы они все были Optional, то мы могли бы использовать Optional Chaining ( цепочку преобразований для типа Optional со знаком .? ). Но мы поставили задачу получить на выходе Result <User>,  и нам для этого во-первых, необходимо уметь преобразовывать Optional в Result<A> (своего рода лифтинг), а во-вторых, распространить действие оператора >>> (bind) на Result<A>:

//~~~~~~~~~~~~~~~~ Уничтожаем многочисленные callback ~~~~~~~

func resultFromOptional<A>(optional: A?, error: NSError) -> Result<A> {
    if let a = optional {
        return .Value(Box(a))
    } else {
        return .Error(error)
    }
}

//--------------------- Оператор >>> для Result---

func >>><A, B>(a: Result<A>, f: A -> Result<B>) -> Result<B> {
    switch a {
    case let .Value(x):     return f(x.value)
    case let .Error(error): return .Error(error)
    }
}

Наша цепочка преобразований состоит из трех этапов:

  1. парсинг реакции из “сети”,
  2. парсинг полученных из “сети” данных типа NSData? в JSON,
  3. парсинг JSON в  Модель User.

Учитывая, что пока мы имеем дело с имитационными данными, напишем соответствующие функции только для 2-го и 3-его этапов (  1-ый этап рассмотрим позже, при работе с реальными данными из сети) :

//------------ NSData -> Result<JSON> --------

func decodeJSON(data: NSData) -> Result<JSON> {
    var jsonErrorOptional: NSError?
    let jsonOptional: JSON! =
                        NSJSONSerialization.JSONObjectWithData(data,
                                    options: NSJSONReadingOptions(0),
                                          error: &jsonErrorOptional)
    if let err = jsonErrorOptional {
        return resultFromOptional(jsonOptional,
                    NSError (localizedDescription: err.localizedDescription ))
    } else {

        return resultFromOptional(jsonOptional, NSError ())
    }
}

extension User {
    static func decode(json: JSON) -> Result<User> {
        let user = JSONObject(json) >>> { dict in
            User.create
                <^> dict["id"]    >>> JSONInt
                <*> dict["name"]  >>> JSONString
                <*> dict["email"] >>> JSONString
        }
        return resultFromOptional(user,
            NSError(localizedDescription: "Отсутствуют компоненты User"))
    }
}

Все это позволит нам составить “цепочку преобразований” для типа Result<A>:

Screen Shot 2014-11-01 at 10.57.00 PM

Делаем код более универсальным, рассчитанным на любой тип Модели с помощью дженериков (generics).

Вводим протокол JSONDecodable, которому должна следовать любая Модель, которая хочет получить JSON-данные,  и  функцию для преобразования JSON -> Result<Модель>:

//~~~~~~~~~~~~~~~~~~~~~ Используем Generic ~~~~~~~~~~~~~~

protocol JSONDecodable {
    class func decode(json: JSON) -> Self?
}

//------------ JSON -> Result<A> --------

func decodeObject<A: JSONDecodable>(json: JSON) -> Result<A> {
    return resultFromOptional(A.decode(json),
                   NSError(localizedDescription: "Отсутствуют компоненты Модели"))
}

Наша Модель, struct User1,  должна подтверждать протокол JSONDecodable :

//--------- Модель User1 ------------------------------

struct User1: JSONDecodable, Printable {
    let id: Int = 0
    let name: String = ""
    let email: String = ""

    var description : String {
        return "User1 { id = \(id), name = \(name), email = \(email)}"
    }
    static func create(id: Int)(name: String)(email: String) -> User1 {
        return User1(id: id, name: name, email: email)
    }

    static func decode(json: JSON) -> User1? {
        return JSONObject(json) >>> { d in
            User1.create
                <^> d["id"]    >>> JSONInt
                <*> d["name"]  >>> JSONString
                <*> d["email"] >>> JSONString
        }
    }
}

Тогда функция парсинга JSON становится очень компактной и универсальной:Screen Shot 2014-11-01 at 11.29.39 PM
Результат печати:

User { id = 1, name = Cool user, email = u.cool@example.com}

Итак, получился очень простой алгоритм : пишем Модель ( подобную struct User1) с функцией decode (json:JSON?) -> Self?, нужной для протокола JSONDecodable, и очень короткую функцию парсинга, подобную getUser5( ... ) c выходом callback:(Result<Модель> -> ()).

Если ошибки вас не интересуют, и вам не нужен Result<Модель>, а всего лишь Модель? , то можно “перегрузить” (overload) функции decodeJSON и decodeObject для Optionals:

//------------------ Для Optionals JSON? -----

func decodeJSON(data: NSData?) -> JSON? {
    let jsonOptional: JSON? = NSJSONSerialization.JSONObjectWithData(data!,
                                          options: NSJSONReadingOptions(0),
                                          error: nil)
    if let json: JSON = jsonOptional {
        return json
    } else {
        return .None
    }
}
//------------- JSON -> A? -------------

func decodeObject<A: JSONDecodable>(json: JSON) -> A? {
    return A.decode(json)
}

Функция парсинга JSON не изменит свой вид:

Screen Shot 2014-11-02 at 5.47.26 PMРезультат печати:

Optional(
"User1 { id = 1, name = Cool user, email = u.cool@example.com}")
nil

Типы  Result<A>  и Optional очень похожи и дополняют друг друга: они часто используются, чтобы показать отсутствие “возвращаемого” значения; и они тривиально просто конвертируются друг в друга, так мы видели как в функции resultFromOptional тип Optional преобразуется в Result<A>:

func resultFromOptional<A>(optional: A?, error: NSError) -> Result<A> {
    if let a = optional {
        return .Value(Box(a))
    } else {
        return .Error(error)
    }
}

Но в то время, как Optional только показывают отсутствие значения, Result<A> специально создан для того, чтобы рапортовать об ошибках и нести в себе значение этой ошибки.
“Контрольные” тестовые данные 

Самое интересное, что алгоритм, представленный в этой статье подходит и для более сложных структур и это мы можем увидеть на тестовых данных № 1. Первый тип тестовых данных приведен в статье Chris Eidhof  “Parsing JSON in Swift. Safe and easy” и представляет собой массив из двух блогов:

    "stat": "ok",
    "blogs": {
        "blog": [
            {
                "id" : 73,
                "name" : "Bloxus test",
                "needspassword" : true,
                "url" : "http://remote.bloxus.com/"
            },
            {
                "id" : 74,
                "name" : "Manila Test",
                "needspassword" : false,
                "url" : "http://flickrtest1.userland.com/"
            }
        ]
    }
}

Все эксперименты с этим типом JSON-данных представлены в файле EfficientJSONBLOG.playground и повторяют процесс превращения функции парсинга JSON-данных в Модель из тривиального и громоздкого "if-let" варианта в компактный  вариант “функциональных концепций”.

Остановимся на окончательном варианте “функциональных концепций”. Функция парсинга getBlogs11 (...)  будет точно такой же, как и для User:

Screen Shot 2014-11-02 at 6.10.44 PMВсе дело в Модели. А в нашем случае их две: одна для одного блога Blog, а вторая – для массива структур Blog. C Моделью Blog все просто – она похожа на Модель User:Screen Shot 2014-11-02 at 6.34.30 PMЗаметьте, что  в структуре Blog две функции с именем decode, c одинаковым входом и разными выходами ( Result<Blog> и Blog? соответственно), которые будут перегружаться в зависимости от контекста.  В принципе первую, с выходом Result<Blog>, можно убрать, но она участвует в наших экспериментах по превращению "if-let" варианта в компактный  вариант “функциональных концепций”.

Вторая Модель, структура Blogs, немного сложнее, так как требует извлечения словаря (dictionary) и массива (array) из JSON-данных по ключу. В первой своей статье  Tony DiPasquale не дает такой возможности ( в своей третьей статье он предложит для этого алгоритм), поэтому мы воспользуемся очень простыми функциями из статьи: “Работа с JSON в Swift. Безопасно и просто.” (перевод) – оригинал Chris Eidhof  “Parsing JSON in Swift. Safe and easy”:

//------- Вынимаем словари и массивы из словарей по ключам ------

func dictionary(input: JSONDictionary, key: String)-> JSONDictionary? {
    return input[key] >>> { $0 as? JSONDictionary }
}

func array(input: JSONDictionary, key: String) ->  JSONArray? {
    return input[key] >>> { $0 as?  JSONArray}
}

public func flatten<A>(array: [A?]) -> [A] {
    var list: [A] = []
    for item in array {
        if let i = item {
            list.append(i)
        }
    }
    return list
}

Это дает нам возможность создать Модель Blogs:Screen Shot 2014-11-02 at 7.05.17 PM

Все, кроме этих моделей больше ничего не надо, и функция getBlog11 ( ...) работает и дает результат:

Screen Shot 2014-11-02 at 7.08.29 PMЭто фантастика !

Второй тип тестовых данных находится на Flickr.com и представляет собой данные в формате JSON  о 100 топ местах, в которых сделаны фотографии, размещенные на Flickr.com. Здесь нам придется получить данные из сети по нужному URL. Это первый этап трансформации данных, который мы пропустили на  предыдущих тестах, и который связан с обработкой реакции сети на запрос JSON-данных.

При работе с сетью вся логика сосредоточена в функции
performRequest<A: JSONDecodable>(request: NSURLRequest, callback: (Result<A>) -> ()), на вход которой поступает URL запрос, а возвращается callback с Result<Модель>:Screen Shot 2014-11-03 at 10.07.06 PM

Внутри этой функции анализируется информация, поступившая из сети: data, urlResponse и error. Задачей этого этапа является превратить полученную из сети информацию в Result<NSData>, с которой мы уже знаем как обращаться. Это делается с помощью структуры Response, в которой соединяются data и urlResponse:Screen Shot 2014-11-02 at 9.20.01 PM

и функции  parseResponse(response: Response) -> Result<NSData> преобразующей реакцию сети в Result<NSData>:Screen Shot 2014-11-02 at 9.21.07 PMА далее в функции func parseResult<A: JSONDecodable> ( ... ) применяем уже привычные нам decodeJSON и decodeObject:Screen Shot 2014-11-02 at 9.51.52 PMТеперь нужно сформировать URL запрос к сайту Flickr.com. Для считывания данных с Flickr.com использовался Flickr API, представленный в курсе Стэнфордского Университета “Stanford CS 193P iOS 7”, написанный на Objective-C.

Итак, добавляем Flickr папку в наш проект, добавляем файл EfficientJSONRus-Bridging-Header.h моста между Swift и Objective-C , в котором указываем  FlickrFetcher.h  файл API Flickr

Screen Shot 2014-11-03 at 11.06.25 PMScreen Shot 2014-11-03 at 11.14.54 PM

и прописываем его в Targets / Build Settings /Swift Compiler – Code Generation / Objective-C Bridging Header:Screen Shot 2014-11-03 at 11.25.20 PMТак что попутно нам пришлось осуществить доступ к Objective-C классам из Swift.Теперь мы можем обращаться к классам Objective-C из Swift.

Открываем файл FlickrFetcher.hScreen Shot 2014-11-02 at 10.10.22 PMи видим, что нам нужно обратиться к функции + (NSURL *)URLforTopPlaces;

В результате в ViewController сформировался следующий код:

import UIKit

class ViewController: UITableViewController {

    var places :[Place]?

    override func viewDidLoad() {
        super.viewDidLoad()

//-------- URL для places из Flickr.com ------

        let urlPlaces  = NSURLRequest( URL: FlickrFetcher.URLforTopPlaces())

        performRequest(urlPlaces ) { (places: Result<Places>) in
            switch places {
            case let .Error(err):
                println ("\(err.localizedDescription)")
            case let .Value(pls):
                self.places = pls.value.places
            }

            dispatch_async(dispatch_get_main_queue()) {

                self.tableView.reloadData()

            }
        }
    }
 }

Функция performRequest(urlPlaces ) полностью универсальна (generic) и единственное место, где нам пришлось указать тип выходного результата – это параметр (places: Result) в терминальном блоке (closure). В самом терминальном блоке мы в зависимости от результата либо распечатываем “ошибку” при .Error(err), либо заполняем массив self.places.

Мы можем отобразить результаты self.places в Table ViewController. Для этого разместим на Main.storyboard Navigation ViewController и Table ViewController

Screen Shot 2014-10-26 at 9.26.17 PM

, а в файле ViewController.swift разместим функции TableViewDataSource

 // MARK: - TableViewDataSource

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        var cellIdentifier = "PlaceCell"
        var cell:UITableViewCell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as UITableViewCell
        cell.textLabel.text = self.places![indexPath.row].content
        cell.detailTextLabel!.text = "\(self.places![indexPath.row].photoCount)"
        return cell
    }

    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.places?.count ?? 0
    }

Не забудем сделать на Main.storyboard привязку Table ViewController к классу и файлу ViewController.swift

Screen Shot 2014-10-26 at 9.34.31 PM

Конечно, это не работает без Модели Places, которая также как и Blogs состоит из двух: Модели одинарного Place:Screen Shot 2014-11-03 at 11.51.53 PMи Модели массива [Place]Screen Shot 2014-11-04 at 3.54.22 PM

Полученные данные выводились в таблицу:

Screen Shot 2014-11-04 at 3.50.52 PM

Код находится на GitHub.

 

Leave a Reply

Your email address will not be published. Required fields are marked *