Работа с 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]); }