Работа с 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"
]
},
…
}

Архитектура приложения будет выглядеть таким образом:

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

1) Необходимо создать класс Channel, в который будем парсить данные

Снимок экрана 2014-04-21 в 12.48.20
Выбираем 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]);
}