SwiftでHTTPリクエストを再帰的に複数並列で行い、全てのリクエストが完了してから次の処理

# SwiftでHTTPリクエストを再帰的に複数並列で行い、全てのリクエストが完了してから次の処理

SwiftでHTTPリクエストを再帰的に複数並列で行い、全てのリクエストが完了してから次の処理をするコードを書いたのでまとめました。https://example.com/orgs で組織のリストが、https://example.com/orgs/(orgId)/members で組織に所属するメンバーのリストが取得できるものとします。戻されるjsonは次のような配列の想定です。

```json
[
    { "orgId": "aaa", "orgName": "組織1" },
    { "orgId": "bbb", "orgName": "組織2" },
    { "orgId": "ccc", "orgName": "組織3" }
]
```

まず組織のリストを取得し、その後に各組織に取得するメンバーのリストを取得します。メンバーのリストを取得するリクエストは非同期に複数並列に実行されますが、全ての組織のメンバーリストが取得できてから、次の処理に移るようにします。

### 環境

- XCode 7.3
- Swift 2.2

## シンプルなGETリクエスト

まずは、シンプルなGETリクエストを行う例です。

```swift
let session: NSURLSession = NSURLSession.sharedSession()

// GETリクエストを発行するメソッド
func httpGet(urlString: String, completionHandler: (data: NSData?, response: NSURLResponse?, error: NSError?) -> Void) {
    
    let url: NSURL = NSURL(string: urlString)!
    let request: NSMutableURLRequest = NSMutableURLRequest(URL: url)
    
    request.HTTPMethod = "GET"
    
    session.dataTaskWithRequest(request, completionHandler: completionHandler).resume()
}

// 組織リストを取得するGETリクエストを発行する
func getOrgs() {
    let urlString: String = "https://example.com/orgs"
    httpGet(urlString, completionHandler: didGetOrgs)
}

// 組織リストを取得するGETリクエストが完了した時に呼ばれる
func didGetOrgs(data: NSData?, response: NSURLResponse?, error: NSError?) {

    if error == nil {
        
        // 何か処理
        print( NSString(data: data!, encoding: NSUTF8StringEncoding)! )
        
    } else {
        print(error)
    }
}
```

## 再帰的に処理する

組織のリストを取得したら、JSONデータをパースします。そして全ての組織のメンバーリストを取得します。

```swift
let session: NSURLSession = NSURLSession.sharedSession()

// GETリクエストを発行するメソッド
func httpGet(urlString: String, completionHandler: (data: NSData?, response: NSURLResponse?, error: NSError?) -> Void) {
    
    let url: NSURL = NSURL(string: urlString)!
    let request: NSMutableURLRequest = NSMutableURLRequest(URL: url)
    
    request.HTTPMethod = "GET"
    
    session.dataTaskWithRequest(request, completionHandler: completionHandler).resume()
}

// 組織リストを取得するGETリクエストを発行する
func getOrgs() {
    let urlString: String = "https://example.com/orgs"
    httpGet(urlString, completionHandler: didGetOrgs)
}

// 組織リストを取得するGETリクエストが完了した時に呼ばれる
func didGetOrgs(data: NSData?, response: NSURLResponse?, error: NSError?) {
    
    if error == nil {
        
        print( NSString(data: data!, encoding: NSUTF8StringEncoding)! )
        
        do {
            let orgs: NSArray = try NSJSONSerialization.JSONObjectWithData(data!, options: .MutableContainers) as! NSArray
            
            for org in orgs {
                
                let orgId: String = org.objectForKey("orgId") as! String
                
                // 組織に含まれるメンバーのリストを取得する
                getMembers(orgId)
            }
            
        } catch let error as NSError {
            print(error)
        }
        
    } else {
        print(error)
    }
}

// メンバーリストを取得するGETリクエストを発行する
func getMembers(orgId: String) {
    let urlString: String = "https://example.com/orgs/\(orgId)/members"
    httpGet(urlString, completionHandler: didGetMembers)
}

// メンバーリストを取得するGETリクエストが完了した時に呼ばれる
func didGetMembers(data: NSData?, response: NSURLResponse?, error: NSError?) {
    
    if error == nil {
        
        // 何か処理
        print( NSString(data: data!, encoding: NSUTF8StringEncoding)! )
        
    } else {
        print(error)
    }
}
```

## 全てのリクエストが完了してから次の処理

全ての組織のメンバーリストが取得できてから次の処理を行うようにします。すなわち複数の非同期処理の全てが完了してから次の処理を行います。このような時にはdispatch_group_asyncとdispatch_group_notifyを使います。

```swift
let session: NSURLSession = NSURLSession.sharedSession()
let queue: dispatch_queue_t = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
let group: dispatch_group_t = dispatch_group_create()

// GETリクエストを発行するメソッド
func httpGet(urlString: String, completionHandler: (data: NSData?, response: NSURLResponse?, error: NSError?) -> Void) {
    
    let url: NSURL = NSURL(string: urlString)!
    let request: NSMutableURLRequest = NSMutableURLRequest(URL: url)
    
    request.HTTPMethod = "GET"
    
    session.dataTaskWithRequest(request, completionHandler: completionHandler).resume()
}

// 疑似的に同期処理を行う
func getOrgsSync() {
    
    // グループに+1
    dispatch_group_enter(group)
    dispatch_group_async(group, queue, {
        getOrgs()
    })
    
    // グループの全ての処理が完了したら実行される
    dispatch_group_notify(group, queue, {
        
        // 何か処理
        
    })
}

// 組織リストを取得するGETリクエストを発行
func getOrgs() {
    let urlString: String = "https://example.com/orgs"
    httpGet(urlString, completionHandler: didGetOrgs)
}

// 組織リストを取得するGETリクエストが完了
func didGetOrgs(data: NSData?, response: NSURLResponse?, error: NSError?) {
    
    if error == nil {
        
        print( NSString(data: data!, encoding: NSUTF8StringEncoding)! )
        
        do {
            let orgs: NSArray = try NSJSONSerialization.JSONObjectWithData(data!, options: .MutableContainers) as! NSArray
            
            for org in orgs {
                
                let orgId: String = org.objectForKey("orgId") as! String
                
                // 組織に含まれるメンバーのリストを取得する
                getMembers(orgId)
            }
            
        } catch let error as NSError {
            print(error)
        }
        
        // グループから-1
        dispatch_group_leave(group)
        
    } else {
        print(error)
        
        // グループから-1
        dispatch_group_leave(group)
    }
}

// メンバーリストを取得するGETリクエストを発行
func getMembers(orgId: String) {
    let urlString: String = "https://example.com/orgs/\(orgId)/members"
    httpGet(urlString, completionHandler: didGetMembers)
}

// メンバーリストを取得するGETリクエストが完了
func didGetMembers(data: NSData?, response: NSURLResponse?, error: NSError?) {
    
    if error == nil {
        
        // 何か処理
        
        // グループから-1
        dispatch_group_leave(group)
        
    } else {
        print(error)
        
        // グループから-1
        dispatch_group_leave(group)
    }
}
```

## 参考にしたリンク

[【Swift】iOSの通信について【GET, POST】](http://ryokwkm2.hatenadiary.jp/entry/2016/01/04/174016)
[SwiftでHTTPリクエストする](http://qiita.com/sushichop/items/ac4ae99b905ce523c2fe)
[HTTP Request in Swift 2.0](http://qiita.com/Shirade/items/80d2ed2114d7352a7551)
[Swiftを使ってJSONを解析する](http://qiita.com/yosi-q/items/7ad1db0dca7b2dd4c065)
[SwiftでGCDを使ってみよう！](http://grandbig.github.io/blog/2016/01/18/semaphore/)