AFNetworkingでAPIにアクセスしてXMLデータをパースする


## 完成予想図

![スクリーンショット 2016-09-15 午後11.41.29.png](https://qiita-image-store.s3.amazonaws.com/0/97280/1ae2ba3b-ebb6-0d0b-1762-66dbf8724eaa.png "スクリーンショット 2016-09-15 午後11.41.29.png")

THE雑ですがGitHubに今回作成したソースをプッシュしておきました

https://github.com/nnsnodnb/XML-Parser-sample

## 作業環境

- OSX 10.11.6
- Xcode7.3.1
- Objective-C
- CocoaPods 0.39.0
  - AFNetworking 3.1.0

## 準備

![スクリーンショット 2016-09-15 午後9.53.03.png](https://qiita-image-store.s3.amazonaws.com/0/97280/f0c361da-ef02-2fdf-39f5-eaf4377d731a.png "スクリーンショット 2016-09-15 午後9.53.03.png")

ここまで準備をしてから始めます。

面倒なのでMVCで実装します。

## 使用するデータ

[サンプル XML ファイル (books.xml) | MicroSoft](https://msdn.microsoft.com/ja-jp/library/ms762271(v=vs.85).aspx)

上記データを使わせていただきます！！
また、データは私のサーバにアップロードしているので使いたい方は使ってください！

https://dl.nnsnodnb.moe/ios-app/sample.xml

catalog
→book
→→author
→→title
→→genre
→→price
→→publish_date
→→description

上記のようなデータ構造になっているのでTableViewにデータを表示させてみようと思います！
(矢印が入れ子)

## 実装


### XML整形ライブラリ

使用するライブラリ？
https://github.com/nnsnodnb/XML-to-NSDictionary
→ [本家](https://github.com/bcaccinolo/XML-to-NSDictionary)

自分なりにARCに対応させました。開発がすでに止まっているのでPRを出してもマージされることはないでしょう！

`vender` フォルダに `XmlReader.h` 及び `XmlReader.m` を突っ込んでおく！

### Model Object

![スクリーンショット 2016-09-15 午後10.12.25.png](https://qiita-image-store.s3.amazonaws.com/0/97280/d8bead9b-39e4-2f7e-0350-67175133953a.png "スクリーンショット 2016-09-15 午後10.12.25.png")

Bookクラスを作っておく。中身は以下

```obj-c:Book.h
#import <Foundation/Foundation.h>

@interface Book : NSObject

@property (nonatomic) NSString *author;
@property (nonatomic) NSString *title;
@property (nonatomic) NSString *genre;
@property (nonatomic) NSString *price;
@property (nonatomic) NSString *publishDate;
@property (nonatomic) NSString *bookDescription;

@end
```

`description` に関しては予約語なので `bookDescription` とした

### TableViewCell クラス

```obj-c:TableViewCell.h
#import <UIKit/UIKit.h>
#import "Book.h"

@interface TableViewCell : UITableViewCell

@property (weak, nonatomic) IBOutlet UILabel *authorLabel;
@property (weak, nonatomic) IBOutlet UILabel *titleLabel;
@property (weak, nonatomic) IBOutlet UILabel *genreLabel;
@property (weak, nonatomic) IBOutlet UILabel *priceLabel;
@property (weak, nonatomic) IBOutlet UILabel *publicDateLabel;
@property (weak, nonatomic) IBOutlet UILabel *descriptionLabel;

- (void)setConfigure:(Book *)model;

@end
```

```obj-c:TableViewCell.m

#import "TableViewCell.h"

@implementation TableViewCell

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

- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
    [super setSelected:selected animated:animated];
}

- (void)setConfigure:(Book *)model {
    self.authorLabel.text = [model.author stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
    self.titleLabel.text = [model.title stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
    self.genreLabel.text = [model.genre stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
    self.priceLabel.text = [model.price stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
    self.publicDateLabel.text = [model.publishDate stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
    self.descriptionLabel.text = [model.bookDescription stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
}

@end
```

改行が含まれていたので改行を削除する処理をしています。

![スクリーンショット 2016-09-15 午後10.17.09.png](https://qiita-image-store.s3.amazonaws.com/0/97280/9d1b69c7-3880-43df-a8c8-48255b68988c.png "スクリーンショット 2016-09-15 午後10.17.09.png")

当たり前なところは端折りながらやります！

![スクリーンショット 2016-09-15 午後10.21.23.png](https://qiita-image-store.s3.amazonaws.com/0/97280/c6d5f863-ce6a-3b18-f89b-6b7cca64bb78.png "スクリーンショット 2016-09-15 午後10.21.23.png")

デザイン性に欠けていますがとりあえずこんな感じでstoryboardを編集

DescriptionはLineを0に設定しています。

### XML整形ライブラリの補足
なんかkeyの中に更にkeyが入っているという謎仕様ｗｗ

### AFNetworking当たりのお仕事

```obj-c:GetXmlData.h
#import <Foundation/Foundation.h>
#import "Book.h"

@protocol CallBackDelegate <NSObject>

- (void)callbackWithError;

@end

typedef void (^Callback)(NSArray<Book *>* dataSource);

@interface GetXmlData : NSObject

@property (weak, nonatomic) id<CallBackDelegate> delegate;
- (void)requestXmlDataWithCallback:(Callback)callback;

@end
```

```obj-c:GetXmlData.m
#import "GetXmlData.h"
#import "XMLReader.h"
#import <AFNetworking/AFNetworking.h>

static NSString *const accessUrl = @"https://dl.nnsnodnb.moe/ios-app/sample.xml";

@implementation GetXmlData

- (void)requestXmlDataWithCallback:(Callback)callback {
    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
    AFHTTPRequestSerializer * requestSerializer = [AFHTTPRequestSerializer serializer];
    AFHTTPResponseSerializer * responseSerializer = [AFHTTPResponseSerializer serializer];
    responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"text/xml", nil];
    manager.responseSerializer = responseSerializer;
    manager.requestSerializer = requestSerializer;
    
    NSMutableArray<Book *> *dataSource = [NSMutableArray array];
    __weak typeof (self) wself = self;
    
    [manager GET:accessUrl
      parameters:nil
        progress:nil
         success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
             NSData *data = (NSData *)responseObject;
             NSDictionary<NSString *, NSDictionary *> *dictionary = [XMLReader dictionaryForXMLData:data
                                                                                              error:nil];
             NSDictionary<NSString *, NSDictionary *> *category = dictionary[@"catalog"];
             for (NSDictionary<NSString *, NSDictionary *> *book in category[@"book"]) {
                 Book *model = [Book new];
                 model.author = book[@"author"][@"text"];
                 model.title = book[@"title"][@"text"];
                 model.genre = book[@"genre"][@"text"];
                 model.price = book[@"price"][@"text"];
                 model.publishDate = book[@"publish_date"][@"text"];
                 model.bookDescription = book[@"description"][@"text"];
                 
                 [dataSource addObject:model];
             }
             callback(dataSource);
         } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
             if ([wself.delegate respondsToSelector:@selector(callbackWithError)]) {
                 [wself.delegate callbackWithError];
             }
         }];
}

@end
```

`+ (NSDictionary *)dictionaryForXMLData:(NSData *)data error:(NSError *)error;`
上記のようなクラスメソッドが用意されているので `responseObject` をNSDataに変換してあげて、このメソッドに当ててあげるとグチャグチャなNSDictionaryが返ってきます！！
こんなものか。

### ViewControllerは？

```obj-c:ViewController.m
#import "Book.h"
#import "GetXmlData.h"
#import "TableViewCell.h"
#import "ViewController.h"

@interface ViewController () <UITableViewDataSource, UITableViewDelegate, CallBackDelegate>

@property (weak, nonatomic) IBOutlet UITableView *tableView;
@property (nonatomic) NSArray<Book *> *dataSource;
@property (nonatomic) GetXmlData *getXmlData;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.dataSource = [NSArray array];
    self.tableView.dataSource = self;
    self.tableView.delegate = self;
    
    self.getXmlData = [GetXmlData new];
    self.getXmlData.delegate = self;
    
    __weak typeof (self) wself = self;
    [self.getXmlData requestXmlDataWithCallback:^(NSArray<Book *> *dataSource) {
        wself.dataSource = dataSource;
        [wself.tableView reloadData];
    }];
}

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

#pragma mark - UITableViewDataSource

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.dataSource.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    TableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"
                                                          forIndexPath:indexPath];
    [cell setConfigure:self.dataSource[indexPath.row]];
    
    return cell;
}


#pragma mark - UITableViewDelegate

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
}

#pragma mark - CallBackDelegate

- (void)callbackWithError {
    NSLog(@"Error");
}

@end
```

## 最後に

なんか最後はぶっ飛ばしで行きましたが、なんとかなったのではないでしょうか？
今回使用したデータが悪かったのかなんかちょっと時間をかけすぎてしまいました。