【初学者向け】モデルクラスを作ってJSONをパースする


## はじめに

新人のコードレビューを行っていてどうやらモデルクラスを使ったJSONデータのパースができない、データベースからのクエリパースができない、みたいなことが多いので初学者向けに記事を書きます。

ここまでするか？っていうぐらいの半分ぐらいはします。

## 環境

- Xcode 7.3.1
- AFNetworking 3.0
- OS X 10.11.6
- CocoaPods 0.39.0

CocoaPodsは導入済み前提で進めます。

## プロジェクト作成

![スクリーンショット 2016-08-19 午後3.06.58.png](https://qiita-image-store.s3.amazonaws.com/0/97280/20f6ccbc-cb73-06ac-e294-dcefc4d25cf1.png "スクリーンショット 2016-08-19 午後3.06.58.png")

Xcodeを閉じておく

![スクリーンショット 2016-08-19 午後4.18.55.png](https://qiita-image-store.s3.amazonaws.com/0/97280/a55d6038-41a8-be52-b30c-cfbd1ff7cb38.png "スクリーンショット 2016-08-19 午後4.18.55.png")

閉じた！！かわいい！！

## AFNetworkingインストール

```bash
$ cd /path/to/project/
$ pod init
$ vim Podfile
```

```ruby:Podfile
target 'Sample' do
    pod 'AFNetworking', '~> 3.0'
end
```

```bash
$ pod install
$ open /path/to/project/project.xcworkspace/
```

## 使用するAPI

http://weather.livedoor.com/forecast/webservice/json/v1?city=130010

今回は東京都の天気を使用します。

## サンプルコード

ATS関係で `http` にはアクセスできないのでInfo.plistに書き込みする

```xml:Info.plist
	<key>NSAppTransportSecurity</key>
	<dict>
    	<key>NSAllowsArbitraryLoads</key>
    		<true/>
	</dict>
```

![スクリーンショット 2016-08-19 午後3.25.25.png](https://qiita-image-store.s3.amazonaws.com/0/97280/b71138d5-6948-92c6-f73e-7e4503e09db6.png "スクリーンショット 2016-08-19 午後3.25.25.png")

Info.plistにkeyを上記のように追加しても良い

### GETリクエスト

モデルわけするのがちょっと面倒なのでViewControllerに書いちゃいます。

```objective-c:ViewController.m
#import "ViewController.h"
#import <AFNetworking/AFNetworking.h>

@interface ViewController ()

@property (weak, nonatomic) NSMutableArray *weatherDatas;

@end

static NSString *const accessUrl = @"http://weather.livedoor.com/forecast/webservice/json/v1";

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    _weatherDatas = [NSMutableArray array];
    _weatherDatas = [self getWeatherInfoData];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
}

- (NSMutableArray *)getWeatherInfoData {
    NSDictionary<NSString *, NSString *> *parameters = @{@"city" : @"130010"};
    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
    [manager GET:accessUrl
      parameters:parameters
        progress:nil
         success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
             // ここに処理
         }
         failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
             NSLog(@"Error: %@", error);
         }
     ];
}

@end
```

### モデルクラス作成

__File > New > File...__ からファイルを作成します。
iOS内のSourceからCocoa Touch Classが選択されていたらNext

![スクリーンショット 2016-08-19 午後3.34.42.png](https://qiita-image-store.s3.amazonaws.com/0/97280/3dc07bdc-fd78-1a34-e6d3-d26f21521db2.png "スクリーンショット 2016-08-19 午後3.34.42.png")

![スクリーンショット 2016-08-19 午後3.34.59.png](https://qiita-image-store.s3.amazonaws.com/0/97280/8c9c51d0-ff2b-a23e-b0cc-d31922b5637f.png "スクリーンショット 2016-08-19 午後3.34.59.png")

Weatherクラスを作りました。

今回は「 __今日__ 」「 __明日__ 」「 __明後日__ 」のデータを取得するようにします。

```objective-c:Weather.h
#import <Foundation/Foundation.h>

@interface Weather : NSObject

@property (strong, nonatomic) NSString *dateLabel;
@property (strong, nonatomic) NSString *telop;
@property (strong, nonatomic) NSString *imageUrl;

@end
```

ざっとこんな感じにしておきましょう！

### JSONデータパース

`ViewController.m` で `Weather.h` をimportしましょう！
結構記事を作るのが面倒になってきたのでいろいろ端折りますが最終的には以下の様になればいいかな？って思います。

```obj-c:ViewController.m
#import "ViewController.h"
#import "Weather.h"
#import <AFNetworking/AFNetworking.h>

@interface ViewController ()

typedef void (^apiCallback)(NSMutableArray<Weather *> *weatherDatas);

@end

static NSString *const accessUrl = @"http://weather.livedoor.com/forecast/webservice/json/v1";

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    NSDictionary<NSString *, NSString *> *parameters = @{@"city" : @"130010"};
    [self getWeatherInfoData:parameters callback:^(NSMutableArray<Weather *> *weatherDatas) {
        for (Weather *weather in weatherDatas) {
            NSLog(@"%@の天気は%@です。", weather.dateLabel, weather.telop);
        }
    }];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
}

- (void)getWeatherInfoData:(NSDictionary<NSString *, NSString *> *)parameters callback:(apiCallback)callback {
    NSMutableArray *weatherDatas = [NSMutableArray new];
    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
    [manager GET:accessUrl
      parameters:parameters
        progress:nil
         success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
             for (NSDictionary<NSString *, NSString *> *forecasts in responseObject[@"forecasts"]) {
                 Weather *weather = [Weather new];
                 weather.dateLabel = forecasts[@"dateLabel"];
                 weather.telop = forecasts[@"telop"];
                 weather.imageUrl = [forecasts valueForKeyPath:@"image.url"];
                 [weatherDatas addObject:weather];
             }
             callback(weatherDatas);
         }
         failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
             NSLog(@"Error: %@", error);
             callback(nil);
         }
     ];
}

@end
```

NSLog自体はこんな感じででてきます！

```log:NSLog出力結果
2016-08-19 16:09:18.008 Sample[57209:10708282] 今日の天気は曇時々晴です。
2016-08-19 16:09:18.008 Sample[57209:10708282] 明日の天気は曇時々雨です。
2016-08-19 16:09:18.008 Sample[57209:10708282] 明後日の天気は曇りです。
```

今回は画像をそのまま入れただけにしていますが、AFNetworkingを使って非同期で画像をキャッシュしつつ表示させるようにするのは以下のようにする

```objective-c:ViewController.m
#import <AFNetworking/UIImageView+AFNetworking.h>

[imageView setImageWithURL:[NSURL URLWithString:@"http://example.com/image.png"]
                      placeholderImage:[UIImage imageNamed:@"placeholder"]];
```

みたいにすると行けます