Статьи

Начало работы с JSONModel

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

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

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

JSONModel — это библиотека с открытым исходным кодом, написанная на Objective-C, которая помогает вам извлекать JSON с сервера, анализировать его и инициализировать классы модели с данными. Он также проверяет данные JSON, каскады через вложенные модели и многое другое.

«Но ждать!» Вы можете подумать: «Я уже написал приложение для iPhone, которое получает JSON и показывает его на экране. Это было довольно легко!»

Ну, это отчасти правда. NSJSONSerialization была доступна с iOS 5, поэтому действительно довольно легко преобразовать JSON-ответ в объект NSDictionary . Это хорошо работает для простых приложений, но поверьте мне, когда я говорю, что это не очень хорошая идея для сложного приложения со сложной моделью данных. Давайте посмотрим, как JSONModel может сохранить ваш бекон.

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

В этом разделе я кратко остановлюсь и рассмотрю основные функции библиотеки. Если вам не терпится погрузиться в код, перейдите к следующему разделу, «Привет, Чак, приложение» .

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

1
2
3
self.firstName = [json objectForKey:@»firstName»];
self.familyName = [json objectForKey:@»familyName»];
self.age = [json objectForKey:@»age»];

С JSONModel вам не нужно писать этот тип шаблонного кода. JSONModel автоматически сопоставляет JSON со свойствами класса модели.

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

Кроме того, модель проверяет, соответствуют ли данные JSON типам, определенным классом модели. Например, если вместо строки вы получаете массив, данные JSON считаются недействительными.

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

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

JSONModel позволяет вам определить преобразования для типов данных один раз и использовать их в своих моделях. Например, если в ответе JSON указана дата в виде временной метки в виде целого числа, вам нужно только указать JSONModel, как преобразовать целое число в объект NSDate один раз. Вы узнаете больше о преобразованиях данных во второй части этой серии.

Чаще всего ответ JSON имеет сложную структуру. Например, объект может содержать один или несколько других объектов. Посмотрите на следующий объект JSON.

1
2
3
4
5
6
7
{
    «id»: 10,
    «more»: {
        «text»: «ABC»,
        «count»: 20
    }
}

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

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

Теперь, когда у вас есть базовое представление о том, что делает JSONModel, вы разработаете простое приложение, которое извлекает JSON-ленту шуток Чака Норриса и показывает их пользователю один за другим. Когда вы закончите, приложение будет выглядеть примерно так:

Запустите Xcode 5, создайте новый проект, выбрав « Создать»> «Проект …» в меню « Файл» , и выберите шаблон приложения « Один вид» из списка шаблонов приложений iOS .

Назовите проект HelloChuck , сообщите Xcode, где вы хотите его сохранить, и нажмите « Создать» . Нет необходимости ставить проект под контроль версий.

Затем загрузите последнюю версию библиотеки JSONModel с GitHub , разархивируйте архив и получите пик внутри.

Архив содержит демонстрационные приложения для iOS и OSX, модульные тесты и многое другое. Вас интересует только папка с именем JSONModel . Перетащите его в свой проект Xcode. Установка еще проще, если вы используете CocoaPods .

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

1
2
3
4
5
6
7
8
{
    «id»: 7,
    «text»: «There used to be a street named after Chuck Norris but it was changed because nobody crosses Chuck Norris and lives»,
    «tags»: [
        { «id»:1, «tag»:»lethal» },
        { «id»:2, «tag»:»new» }
    ]
}

Давайте начнем с создания классов моделей, соответствующих данным JSON. Создайте новый класс JokeModel и сделайте его наследником от JSONModel . Добавьте свойства id и text для соответствия ключам в данных JSON следующим образом:

1
2
3
4
5
6
@interface JokeModel : JSONModel
 
@property (assign, nonatomic) int id;
@property (strong, nonatomic) NSString* text;
 
@end

Библиотека JSONModel автоматически преобразует числа в соответствии с типом свойства.

Вам также необходимо создать класс для объектов тега в данных JSON. Создайте новый класс TagModel и сделайте так, чтобы он наследовал JSONModel . Объявите два свойства id и tag типа NSString . Класс TagModel должен выглядеть следующим образом:

1
2
3
4
5
6
@interface TagModel : JSONModel
 
@property (strong, nonatomic) NSString* id;
@property (strong, nonatomic) NSString* tag;
 
@end

Обратите внимание, что вы установили тип id в NSString . JSONModel прекрасно знает, как преобразовать числа в строки, он будет обрабатывать преобразование для вас. Идея состоит в том, что вам нужно сосредоточиться только на данных, которые вам нужны в вашем приложении, не беспокоясь о том, как выглядят данные JSON.

Хотя класс TagModel готов к использованию, вам нужен способ сообщить классу JokeModel что ключевые tags содержат список экземпляров TagModel . Это очень легко сделать с помощью JSONModel. Добавьте новый пустой протокол в TagModel.h и назовите его TagModel :

1
2
@protocol TagModel
@end

Откройте JokeModel.h и импортируйте файл TagModel класса TagModel :

1
#import «TagModel.h»

Здесь приходит волшебство. JokeModel новое свойство для JokeModel как показано ниже. Свойство tags имеет тип NSArray и соответствует двум протоколам.

1
@property (strong, nonatomic) NSArray<TagModel, Optional>* tags;
  1. TagModel — это протокол, который вы объявили минуту назад. Он сообщает JokeModel что массив тегов должен содержать экземпляры класса TagModel .
  2. Придерживаясь Optional протокола, класс JokeModel знает, что данные JSON не всегда будут содержать список тегов.

Это хороший момент, чтобы подчеркнуть, что каждое свойство в вашем классе модели по умолчанию требуется . Если id или text отсутствуют в данных JSON, инициализация объекта JokeModel завершится неудачно. Однако, если для какой-то шутки отсутствуют tags , JSONModel не будет жаловаться на это.

Сначала вам нужно внести пару изменений в класс ViewController . Откройте ViewController.m и, под существующим оператором import вверху, импортируйте класс JokeModel :

1
#import «JokeModel.h»

Вам нужно добавить два свойства в класс ViewController:

  • label для отображения текста шутки на экране
  • jokes для хранения массива шуток
1
2
3
4
5
6
@interface ViewController ()
 
@property (strong, nonatomic) UILabel* label;
@property (strong, nonatomic) NSArray* jokes;
 
@end

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

01
02
03
04
05
06
07
08
09
10
11
12
— (void)viewDidLoad {
    [super viewDidLoad];
     
    self.label = [[UILabel alloc] initWithFrame:self.view.bounds];
    self.label.numberOfLines = 0;
    self.label.textAlignment = NSTextAlignmentCenter;
    self.label.alpha = 0;
     
    [self.view addSubview: self.label];
     
    [self fetchJokes];
}

Вы создаете экземпляр UILabel размером с экран устройства и устанавливаете его alpha свойство UILabel 0 . Ярлык скрыт, пока первая шутка не будет готова для отображения.

В последней строке viewDidLoad вы вызываете fetchJokes , в котором приложение извлекает удаленные данные JSON и сохраняет их содержимое в свойстве jokes контроллера представления. Вы будете реализовывать fetchJokes в мгновение fetchJokes .

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

1
2
3
4
5
6
7
— (void)fetchJokes {
    NSURL* jokesUrl = [NSURL URLWithString:@»https://s3.amazonaws.com/com.tuts.mobile/jokes.json»];
     
    [[[NSURLSession sharedSession] dataTaskWithURL:jokesUrl completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        //handle data here
    }] resume];
}

dataTaskWithURL:completionHandler: создает для экземпляра NSURLSessionDataTask URL-адрес, который ему передается. NSURLSession resume для задачи с данными, вы NSURLSession экземпляру NSURLSession добавить задачу с данными в свою очередь.

Затем вам нужно добавить код для инициализации экземпляров JokeModel . Заменить //handle data here :

1
self.jokes = [JokeModel arrayOfModelsFromData:data error:nil];

arrayOfModelsFromData:error: берет объект NSData из ответа JSON и возвращает массив моделей. Но что происходит под капотом?

  1. [JokeModel arrayOfModelsFromData:error:] берет данные JSON и превращает их в массив объектов JSON.
  2. Затем JokeModel зацикливается на этих объектах и ​​создает экземпляры JokeModel из каждого объекта JSON.
  3. Каждый экземпляр JokeModel проверяет полученные данные JSON и инициализирует их свойства правильными значениями.
  4. Если экземпляр JokeModel находит содержимое в ключе tags данных, то он создает массив экземпляров TagModel из значения, связанного с ключом tags .

Если вам нужно создать только один экземпляр модели, тогда initWithData: и initWithString: — это методы, которые вам нужно использовать. Мы подробнее рассмотрим эти методы в следующем уроке.

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

1
2
3
dispatch_async(dispatch_get_main_queue(), ^{
    [self showNextJoke];
});
01
02
03
04
05
06
07
08
09
10
11
12
— (void)showNextJoke {
    JokeModel* model = self.jokes[arc4random() % self.jokes.count];
     
    NSString* tags = model.tags?[model.tags componentsJoinedByString:@»,»]:@»no tags»;
    self.label.text = [NSString stringWithFormat:@»%i. %@\n\n%@», model.id, model.text, tags];
     
    [UIView animateWithDuration:1.0 animations:^{
        self.label.alpha = 1.0;
    } completion:^(BOOL finished) {
        [self performSelector:@selector(hideJoke) withObject:nil afterDelay:5.0];
    }];
}

Сначала вы извлекаете случайную шутку из массива jokes и сохраняете ее в model . Если в шутке есть теги, вы сохраняете их в виде списка через запятую в переменной с именем tags . Если в шутке нет никаких тегов, вы устанавливаете tags @"no tags" .

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

Когда анимация завершается, вы ждете пять секунд, прежде чем hideJoke , который скрывает шутку с другой анимацией затухания. Когда анимация завершится, вы вызываете showNextJoke еще раз.

1
2
3
4
5
6
7
— (void)hideJoke {
    [UIView animateWithDuration:1.0 animations:^{
        self.label.alpha = 0.0;
    } completion:^(BOOL finished) {
        [self showNextJoke];
    }];
}

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

Однако существует проблема, заключающаяся в том, что при распечатке массива тегов отображаются объекты TagModel вместо строковых объектов. Такое поведение на самом деле является функцией библиотеки JSONModel. Он автоматически создает описание объекта, подобное тому, которое вы видели на предыдущем скриншоте. В нем перечислены свойства объекта модели и их значения, что действительно помогает при отладке.

Чтобы завершить этот урок, вы напишите первую строчку кода модели. Модели, которые наследуются от JSONModel , похожи на любой другой класс Objective-C. Это означает, что вы можете переопределить методы JSONModel и настроить их поведение так, как вам нравится.

Откройте TagModel.m и переопределите метод описания по умолчанию:

1
2
3
— (NSString *)description {
    return self.tag;
}

Теперь, когда вы вызываете componentsJoinedBySeparator: в массиве тегов, вместо описания по умолчанию TagModel вы просто получите тег в виде простого текста.

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

Теперь у вас есть общее представление о библиотеке JSONModel. Итак, вы узнали:

  • Как создать простой класс модели, который наследуется от JSONModel
  • как определить обязательные и дополнительные свойства
  • и как вложить модельные классы

В этом коротком руководстве я затронул только некоторые функции библиотеки JSONModel . В следующих статьях этой серии вы узнаете больше о преобразовании данных, работе с удаленными API-интерфейсами JSON и познакомитесь с некоторыми более продвинутыми функциями JSONModel.