Работа с JSON (iOs)
Основная статья, которой пользовалась: http://www.appcoda.com/fetch-parse-json-ios-programming-tutorial/
Мне довольно часто приходиться работать с получением данных с помощью json, потому я и решила составить пошаговую инструкцию.
Для примера использую json такого вида:
{
"channel":
[
{
"img": "resources/images/channels_icons/channel.png",
"img_url": "http://site/img/for_sprites/channel.png",
"isLocked": false,
"key": 77777,
"name": "Телеканал",
"priority": 2000000002,
"tag1": "#музыкальный",
"tag2": "#развлекательный",
"videoUrl": "http://site/output/channel/playlist.m3u8",
"videoUrlArr":
[
"http://site/output/channel/playlist.m3u8"
]
},
…
}
Архитектура приложения будет выглядеть таким образом:

Коммуникатор — обращается к API для получения json, затем в зависимости от результата делегирует фун-ям Манагера
Билдер — создает из json объект класса Channel
Манагер — является фасадом. Т.е он координирует работу Коммуникатора и Билдера. Как только коммуникатор получил json, Манагер передает их Билдеру, который создает необходимый нам объект Channel. Как только объект создан Манагер делегирует ViewController
1) Необходимо создать класс Channel, в который будем парсить данные

Выбираем New File -> Objective-C class
Вводим название класса (в моем случае Channel) и подкласс NSObject
В h файле прописываем свойства класса:
@property (strong, nonatomic) NSString *img_url; @property (nonatomic) BOOL isLocked; @property (strong, nonatomic) NSString *key; @property (strong, nonatomic) NSString *name; @property (strong, nonatomic) NSString *priority; @property (strong, nonatomic) NSString *tag1; @property (strong, nonatomic) NSString *tag2; @property (strong, nonatomic) NSString *videoUrl; @property (strong, nonatomic) NSMutableArray *videoUrlArr;
Свойства класса лучше называть точно так же, как они указаны в json, чтобы легче было затем парсить.
2) Переходим к получению данных от API
Для начала необходимо создать делегатор для Коммуникатора (в нем описываются методы, которые дергает Коммуникатор, реализация этих методов будет происходить в Манагере)
Выбираем New File -> Objective-C protocol
Вводим название (в моем случае BonustvCommunicatorDelegate)
В h файле:
@protocol BonustvCommunicatorDelegate <NSObject> - (void)receivedChannelsJSON:(NSData *)objectNotation; - (void)fetchingChannelsFailedWithError:(NSError *)error; @end
Теперь создадим сам Коммуникатор:
Выбираем New File -> Objective-C class
Вводим название класса (в моем случае BonustvCommunicator) и подкласс NSObject
В h файле прописываем свойства класса и методы:
@protocol BonustvCommunicatorDelegate; @interface BonustvCommunicator : NSObject @property (weak, nonatomic) id<BonustvCommunicatorDelegate> delegate; - (void)getChannels; @end
В m файле прописываем реализацию метода getChannels
И не забудьте сделать импорт делегатора для коммуникатора:
#import "BonustvCommunicatorDelegate.h"
@implementation BonustvCommunicator
- (void)getChannels
{
NSString *urlAsString = @"http://app1.bonus-tv.ru/getChannelList";
NSURL *url = [[NSURL alloc] initWithString:urlAsString];
NSLog(@"%@", urlAsString);
[NSURLConnection sendAsynchronousRequest:[[NSURLRequest alloc] initWithURL:url] queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (error) {
[self.delegate fetchingChannelsFailedWithError:error];
} else {
[self.delegate receivedChannelsJSON:data];
}
}];
}
@end
3) Начнем парсить json. Для этого создаем Билдер:
Выбираем New File -> Objective-C class
Вводим название (в моем случае ChannelBuilder) и подкласс NSObject
В h файле:
@interface ChannelBuilder : NSObject + (NSArray *)channelsFromJSON:(NSData *)objectNotation error:(NSError **)error; @end
В m файле из json создаем массив объектов класса Channel.
Первым делом создаем массив, в который будем записывать результат (объекты типа Channel)
NSMutableArray *channelsArray = [[NSMutableArray alloc] init];
Дальше распарсим пришедший json:
NSArray *results = [parsedObject valueForKey:@"channel"]; //@"channel" - т.к channel является корневым тегов в моем json
Пробегаем по полученному массиву и создаем объекты класса Channel
for (NSDictionary *chanelDic in results) {
Channel *channel = [[Channel alloc] init];
channel.videoUrlArr = [[NSMutableArray alloc] init];
for (NSString *key in chanelDic) {
if([key isEqualToString:@"videoUrlArr"])
for (NSDictionary *urls in [chanelDic valueForKey:key])
[channel.videoUrlArr addObject:urls];
else if([key isEqualToString:@"isLocked"])
channel.isLocked = [[chanelDic objectForKey:@"isLocked"] boolValue];
else if ([channel respondsToSelector:NSSelectorFromString(key)]) {
[channel setValue:[chanelDic valueForKey:key] forKey:key];
}
}
[channelsArray addObject:channel];
}
В основном у меня все свойства класса Channel типа строки, но есть одно свойство булеан и один массив. Для них я делаю проверку и парсю их немного иначе.
Для массива:
if([key isEqualToString:@"videoUrlArr"])
for (NSDictionary *urls in [chanelDic valueForKey:key])
[channel.videoUrlArr addObject:urls];
Для булеана:
else if([key isEqualToString:@"isLocked"])
channel.isLocked = [[chanelDic objectForKey:@"isLocked"] boolValue];
В итоге m файл у меня выглядит след. образом:
#import "ChannelBuilder.h"
#import "Channel.h"
@implementation ChannelBuilder
+ (NSArray *)channelsFromJSON:(NSData *)objectNotation error:(NSError **)error
{
NSError *localError = nil;
NSDictionary *parsedObject = [NSJSONSerialization JSONObjectWithData:objectNotation options:0 error:&localError];
if (localError != nil) {
*error = localError;
return nil;
}
NSMutableArray *channelsArray = [[NSMutableArray alloc] init];
NSArray *results = [parsedObject valueForKey:@"channel"];
NSLog(@"Count %d", results.count);
for (NSDictionary *chanelDic in results) {
Channel *channel = [[Channel alloc] init];
channel.videoUrlArr = [[NSMutableArray alloc] init];
for (NSString *key in chanelDic) {
if([key isEqualToString:@"videoUrlArr"])
for (NSDictionary *urls in [chanelDic valueForKey:key])
[channel.videoUrlArr addObject:urls];
else if([key isEqualToString:@"isLocked"])
channel.isLocked = [[chanelDic objectForKey:@"isLocked"] boolValue];
else if ([channel respondsToSelector:NSSelectorFromString(key)])
[channel setValue:[chanelDic valueForKey:key] forKey:key];
}
[channelsArray addObject:channel];
}
return channelsArray;
}
@end
4) Создадим Манагера, который объединит все вместе
Для начала необходимо создать делегатор для Манагера (в нем описываются методы, которые дергает Манагер, реализация этих методов будет происходить в Контроллере)
Выбираем New File -> Objective-C protocol
Вводим название (в моем случае BonustvManagerDelegate)
В h файле:
- (void)didReceiveChannels:(NSArray *)channelArray; - (void)fetchingChannelsFailedWithError:(NSError *)error;
Теперь создадим сам Манагер:
Выбираем New File -> Objective-C class
Вводим название класса (в моем случае BonustvManager) и подкласс NSObject
В h файле прописываем свойства класса и методы:
#import "BonustvManagerDelegate.h" #import "BonustvCommunicatorDelegate.h" @class BonustvCommunicator; @interface BonustvManager : NSObject<BonustvCommunicatorDelegate> @property (strong, nonatomic) BonustvCommunicator *communicator; @property (weak, nonatomic) id<BonustvManagerDelegate> delegate; - (void)fetchChannels; @end
В m файле описываем реализацию методов BonustvCommunicatorDelegate и метода fetchChannels, который дергает метод getChannels у Коммуникатора:
#import "BonustvManager.h"
#import "ChannelBuilder.h"
#import "BonustvCommunicator.h"
@implementation BonustvManager
- (void)fetchChannels
{
[self.communicator getChannels];
}
#pragma mark - BonustvCommunicatorDelegate
- (void)receivedChannelsJSON:(NSData *)objectNotation
{
NSError *error = nil;
NSArray *channelsArray = [ChannelBuilder channelsFromJSON:objectNotation error:&error];
if (error != nil) {
[self.delegate fetchingChannelsFailedWithError:error];
} else {
[self.delegate didReceiveChannels:channelsArray];
}
}
- (void)fetchingChannelsFailedWithError:(NSError *)error
{
[ self.delegate fetchingChannelsFailedWithError:error];
}
5) Последний шаг. Из контроллера нужно вызвать медоты Манагера.
Но сначала нужно импортнуть в h файл контроллера все необходимые классы, объявить манагер и массив, в который запишим результат:
#import "Channel.h"
#import "BonustvCommunicator.h"
#import "BonustvManager.h"
@interface ViewController : UIViewController <BonustvManagerDelegate>
{
NSArray *channels;
BonustvManager *manager;
}
В m файле контроллера создадим манагера во viewDidLoad:
manager = [[BonustvManager alloc] init];
manager.communicator = [[BonustvCommunicator alloc] init];
manager.communicator.delegate = manager;
manager.delegate = self;
И начинаем получать наши каналы (пишем там же во viewDidLoad ):
[self startFetchingChannels];
В контроллере необходимо определить этот метод:
- (void)startFetchingChannels
{
[manager fetchChannels];
}
Так же нужно создать реализацию методов BonustvManagerDelegate. Я использую многопоточность. Пока получаю json, у меня крутиться спиннер. Как получила убираю спиннер и обновляю даные в таблице:
#pragma mark - BonustvManagerDelegate
- (void)didReceiveChannels:(NSArray *)channelArray
{
channels = channelArray;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul);
dispatch_async(queue, ^{
//This code will run on a background thread
dispatch_async(dispatch_get_main_queue(), ^{
//this code runs on the main thread since it is UI changes
[self.collectionView reloadData];
[self.spinner stopAnimating];
});
});
}
- (void)fetchingChannelsFailedWithError:(NSError *)error
{
NSLog(@"Error %@; %@", error, [error localizedDescription]);
}
