NSURLConnection から NSURLSession への移行例

# はじめに
iOS 9 より NSURLConnection が DEPRECATED となってしまったので、既存コードを NSURLSession に移行する場合について考察。

# Delegateを使っていない場合
## 非同期処理
非同期処理（+ sendAsynchronousRequest:queue:completionHandler:）の場合は、特に何も考えずにBlocksの中身をそのまま使うことができます。

```objc:NetworkManager.m
    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {
        if (connectionError) {
            [[NSNotificationCenter defaultCenter] postNotificationName:@"LoadDataFailed" object:nil];
            return;
        }
        NSInteger statusCode = [(NSHTTPURLResponse *)respnse statusCode];
        if (statusCode != 200) {
            [[NSNotificationCenter defaultCenter] postNotificationName:@"LoadDataFailed" object:nil];
            return;
        }
        
        [[NSNotificationCenter defaultCenter] postNotificationName:@"LoadDataSuccess" object:data];
    }];

    NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&respnse error:&error];
```

上のようなコードの場合だと、下のように completionHandler: をほぼそのまま流用することができます。

```objc:NetworkManager.m
    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
    NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
    NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        // connectionError -> error
        if (error) {
            [[NSNotificationCenter defaultCenter] postNotificationName:@"LoadDataFailed" object:nil];
            return;
        }
        NSInteger statusCode = [(NSHTTPURLResponse *)respnse statusCode];
        if (statusCode != 200) {
            [[NSNotificationCenter defaultCenter] postNotificationName:@"LoadDataFailed" object:nil];
            return;
        }
        
        [[NSNotificationCenter defaultCenter] postNotificationName:@"LoadDataSuccess" object:data];
    }];
    [task resume];
```

ただ、別スレッドで処理されているため、Viewの操作をする場合は注意が必要です。

## 同期処理
同期処理（+ sendSynchronousRequest:returningResponse:error:）の場合は、 NSURLSessionTask が非同期処理であるため、このままでは都合が悪いです。
GCD のディスパッチセマフォという仕組みを利用し、非同期処理を同期処理にすることで対応します。

```objc:NetworkManager.m
- (NSString *)connectionSyncRequest:(NSString *)targetUrl
{
    NSURL *url = [NSURL URLWithString:targetUrl];
    NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];

    NSURLResponse *response = nil;
    NSError *error = nil;
    NSData *data = [NSURLConnection sendSynchronousRequest:urlRequest returningResponse:&response error:&error];
    
    if (error) {
        NSLog(@"%@", error);
        return nil;
    }
    
    NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
    if (httpResponse.statusCode != 200) {
        return nil;
    }

    NSString *receivedMessage = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];

    return receivedMessage;
}
```

上のようなコードの場合では、下のようにディスパッチセマフォを利用して、同期処理にします。

```objc:NetworkManager.m
- (NSString *)sessionSyncRequest:(NSString *)targetUrl
{
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

    NSURL *url = [NSURL URLWithString:targetUrl];
    NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];

    __block NSString *receivedMessage = nil;
    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
    NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
    NSURLSessionDataTask *task = [session dataTaskWithRequest:urlRequest completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        NSLog(@"did finish download.\n%@", response.URL);
        if (error) {
            NSLog(@"%@", error);
            dispatch_semaphore_signal(semaphore);
            return;
        }
        
        NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
        if (httpResponse.statusCode != 200) {
            dispatch_semaphore_signal(semaphore);
            return;
        }
        receivedMessage = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        dispatch_semaphore_signal(semaphore);

    }];
    [task resume];

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

    return receivedMessage;
}
```

# Delegateを使っている場合
Delegate を使っている場合も、わりとすんなり移行することができます。

```objc:NetworkManager.m
- (void)sessionAsyncRequest:(NSString *)targetUrl
{
    NSURL *url = [NSURL URLWithString:targetUrl];
    NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];

    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
    NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
    NSURLSessionDataTask *task = [session dataTaskWithRequest:urlRequest completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        NSLog(@"did finish download.\n%@", response.URL);
        if (error) {
            NSLog(@"%@", error);
            [self connection:nil didFailWithError:error];
            return;
        }
        
        NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
        if (httpResponse.statusCode != 200) {
            [self connection:nil didFailWithError:nil];
            return;
        }

        self.receivedData = [NSMutableData dataWithData:data];
        [self connectionDidFinishLoading:nil];
    }];
    [task resume];
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    // 正常終了時の処理
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
    // エラー時の処理
}
```