パラメータ付きURLでの凡ミスを回避する方法（NSURLComponents）

iOS開発時、Safariでの遷移先URLがパラメータ付きだったりすることもあるかと思います。
パラメータがひとつだけなら特に問題ないかもしれませんが、パラメータが複数になるとミスが発生する可能性が出てきます。
例えば、下記のような感じ。

```
（正）
http://www.example.com/index.php?key1=value1&key2=value2

（誤）
// ?がタブってる。2つ目の?は&とするところ
http://www.example.com/index.php?key1=value1?key2=value2
```

パラメータ部分までをベタ書きしてたり定数やマクロで定義してたりすると起こり得るミスかなと思います。
パラメータが追加になったときについうっかりとか。
ええ、私もやらかしたことがあります…

そんな苦い経験を繰り返さないために、NSURLComponentsというクラスを使ってパラメータを設定する方法を紹介したいと思います。

## ゴール
NSURLComponentsを利用して、URLのパラメータを値のみセットする。
→?と&の間違えのような、単純なミスを起こさない環境にする。

### 方法
- iOS8以降はqueryItemsというクエリの配列のプロパティで指定
- iOS7にも対応するには辞書でクエリセットするようにカテゴリを作成

以下NSURLComponentsについて説明していきます。

# NSURLComponents


## 初期化
- URLを指定して初期化  

```
+ (instancetype)componentsWithString:(NSString *)URLString  
```

```
（例）
NSURLComponents *components = [NSURLComponents componentsWithString:@"http://example.com"];  
```

何も指定せず初期化し、あとからプロパティを追加していくことも可能。

## プロパティ

```

@property(copy) NSString * プロパティ名

※例外
@property(copy) NSArray <NSURLQueryItem *> *queryItems
→iOS8からなので注意！！
```

| プロパティ名  | 例         | プロパティ部分    |
|------------|-------------|--------------|
| fragment | http://www.example.com/index.html#jumpLocation | jumpLocation |
| host | http://www.example.com/index.html | www.example.com |
| password | http://username:password@www.example.com/index.html | password |
| path | http://www.example.com/index.html | /index.html |
| port | http://www.example.com:8080/index.php | 8080 |
| query | http://www.example.com/index.php?key1=value1&key2=value2 | key1=value1&key2=value2 |
| queryItems（iOS8以降） | 下記参照 | 下記参照 |
| scheme | http://www.example.com/index.html | http |
| user | http://username:password@www.example.com/index.html | username |

## URL確認  

```
（例）
NSLog(@"%@", components.URL);
```

## パラメータの設定
「query」プロパティにセットします。

```
（例）
components.query = @"key1=value1&key2=value2";
```

複数指定の場合は「&」で接続していきますが、手入力になるので結局バグ発生の可能性がありますね。
それを回避するには先述の２つの方法が考えられます。

- iOS8以降はqueryItemsというクエリの配列のプロパティで指定
- iOS7にも対応するには辞書でクエリセットするようにカテゴリを作成

## iOS8以降はNSURLQueryItemとqueryItemsが使える
- NSURLQueryItem：クエリ要素のクラス
- queryItems：NSURLComponentsのプロパティ（NSURLQueryItemの配列）

### 使い方

```objc

NSURLQueryItem *item1 = [NSURLQueryItem queryItemWithName:@"key1" value:@"hoge"];  
NSURLQueryItem *item2 = [NSURLQueryItem queryItemWithName:@"key2" value:@"fuga"];

components.queryItems = @[item1, item2];
```


## iOS7でも要素の指定で設定できるようにカテゴリを作成

### 実装
ヘッダファイル（.h）

```objc

/**
 *  NSURLComponentsのカテゴリクラスです。
 */
@interface NSURLComponents (Addition)

/**
 *  query（パラメータ等）指定を辞書型で扱うためのプロパティです。
 */
@property(nonatomic, copy) NSDictionary *queryDictionary;

@end
```

実装ファイル（.m）

```objc


@implementation NSURLComponents (Addition)

-(NSDictionary*)queryDictionary
{
    return self.queryDictionary;
}

-(void)setQueryDictionary:(NSDictionary *)queryDictionary
{
    __block NSString *queryString = nil;
    
    [queryDictionary enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {

        if ([key isKindOfClass:[NSString class]] && obj != [NSNull null])
        {
            id value;

            if ([obj isKindOfClass:[NSString class]]) {
                value = obj;
            } else {
                value = [obj description];
            }

            if (!queryString) {
                queryString = @"";
            } else {
                queryString = [queryString stringByAppendingString:@"&"];
            }

            queryString = [queryString stringByAppendingString:[NSString stringWithFormat:@"%@=%@", key, value]];
        }
    }];

    self.query = queryString;
}

@end
```

### 使い方

```objc

NSURLComponents *components = [NSURLComponents componentsWithString:@"http://example.com/path"];  
components.queryDictionary = @{@"key1": @"value1", @"key2": @"value2"};

NSLog(@"%@", components.URL); // http://example.com/path?key1=value1&key2=value2
```

## おわりに
人が何かをする以上ミスをしてしまうのは仕方のないことです。
ミスの起こりにくい環境を整えていくことがアプリの品質の向上につながるのではないかと考えています。

本投稿が少しでもそのお役に立てれば幸いです。

### 参考
https://developer.apple.com/library/prerelease/ios/documentation/Foundation/Reference/NSURLComponents_class/index.html
http://oopsmouse.github.io/blog/2013/11/18/title/