URLSessionの拡張（URLSession#synchronousDataTask, URLSession#retryDataTask)

## URLSession拡張定義

```swift
extension URLSession {
    
    /*
     * synchronousDataTask
     * 
     * Httpデータを同期的に取得します。スレッドをブロックするので使用に注意してください。
     */
     
    func synchronousDataTask(with request:URLRequest) -> (data:Data?, response:URLResponse?, error:Error?) {
        let semaphore = DispatchSemaphore(value: 0)
        var _dat : Data?
        var _res : URLResponse?
        var _err : Error?
        self.dataTask(with: request) { dat, res, err in
            _dat = dat; _res = res; _err = err
            semaphore.signal()
        }.resume()
        _ = semaphore.wait(timeout: .distantFuture)
        return (_dat, _res, _err)
    }
    
    func synchronousDataTask(with url:URL) -> (data:Data?, response:URLResponse?, error:Error?) {
        return self.synchronousDataTask(with: URLRequest(url: url))
    }
    
    /*
     * retryDataTask
     * 
     * Httpデータ取得に失敗た場合に再度取得を試みます。取得に成功するまでtimesで指定した回数の再取得を試みます。
     */
    
    func retryDataTask(withRequest request:URLRequest, times num:Int=50, wait usec:useconds_t=200*1000, errorHandler:@escaping (Int, Error) -> ()={_,_ in}, completionHandler:@escaping (Data?, URLResponse?, Error?) -> ()) -> URLSessionDataTask {
        return self.dataTask(with: request) { dat, res, err in
            if let err = err, num > 0 {
                errorHandler(num, err)
                usleep(usec)
                self.retryDataTask(withRequest: request, times: num-1, wait: usec, errorHandler: errorHandler, completionHandler: completionHandler).resume()
            } else {
                completionHandler(dat, res, err)
            }
        }
    }
    
    func retryDataTask(withURL url:URL, times num:Int=50, wait usec:useconds_t=200*1000, errorHandler:@escaping (Int, Error) -> ()={_,_ in}, completionHandler:@escaping (Data?, URLResponse?, Error?) -> ()) -> URLSessionDataTask {
        return self.retryDataTask(withRequest: URLRequest(url: url), times: num, wait: usec, errorHandler: errorHandler, completionHandler: completionHandler)
    }
}
```

## 使用例

### URLSession#synchronousDataTask

```swift
let url = URL(string: "http://localhost:8080/")!

let result = URLSession.shared.synchronousDataTask(with: url)

print( result.data, result.response, result.error )
print("complete.")
```

### URLSession#retryDataTask

```swift
let url = URL(string: "http://localhost:8080/")!

// 単純データ取得

func get() {
    URLSession.shared.retryDataTask(withURL: url) { dat, _, _ in
        if let dat = dat, let body = String(data: dat, encoding: .utf8) {
            print(body)
        }
    }.resume()
}

// リトライを最大30回繰り返し。取得失敗の度にエラー情報を出力する。

let numberOfTimes = 30

func displayIndicator(count:Int, error:Error) {
    print("times:\(count)/\(numberOfTimes)")
    print(error)
}

func status() {
    URLSession.shared.retryDataTask(withURL: url, times: numberOfTimes, errorHandler: displayIndicator) { dat, res, err in
        if dat == nil, res == nil, let error = err {
            print(error)
        } else if let dat = dat, let response = res, err == nil, let body = String(data:dat, encoding:.utf8) {
            print(response)
            print(body)
        }
    }.resume()
}
```