NSURLSessionがメモリリークしてしまうのをなんとかした


自社のWebAPIの通信にNSURLSessionを使って通信をしていました。
Debug memory graghをみるとNSURLSessionのインスタンスでメモリリークが起こっていました。
それを直した記事です


# 環境
* Xcode8.3.3
* Swift3.1
* iOS10.3.2

# 問題点

NSURLSessionを使って通信処理をする実装をしていました。


```swift
let mySession = URLSession.init(configuration: URLSessionConfiguration.default)
//urlインスタンスはURL型
let task = session.dataTask(with: url, completionHandler: {
    data, response, error in
    
    // do something.
    })
task.resume()

```

Debug memory graghをみるとたくさんのメモリリークがありました。

![20170614_1497403813.jpg](https://qiita-image-store.s3.amazonaws.com/0/31587/9bb68868-454f-660c-ca78-e2fc2269a130.jpeg "20170614_1497403813.jpg")

ほっとくとメモリリークでアプリが落ちるなど品質に問題が出てきてしまいます。
なんとかしたいです。


Backtraceのメソッドを見るとNSURLSessionのインスタンスにたどり着きました。


![20170614_1497403851.jpg](https://qiita-image-store.s3.amazonaws.com/0/31587/13e635e0-fa24-11d0-12fe-e9058cc2774b.jpeg "20170614_1497403851.jpg")

`mySession`が漏れている？
なんでだろうと色々調べるとNSURLSessionのインスタンスは専用のメソッドを実行しないと解放されないことがわかりました。


# NSURLSessionの解放は専用のメソッドで行う

ドキュメントを見るとNSURLSessionのインスタンスはデリゲートに対して自分で解除するまで強参照で保持され続けるのだそうです

>Important
>The session object keeps a strong reference to the delegate until your app exits or explicitly invalidates the session. If you do not invalidate the session, your app leaks memory until it exits.
>
>https://developer.apple.com/documentation/foundation/nsurlsession?language=objc


なので解除するメソッドを実行する必要があります。
解除するメソッドはいくつかあってそれぞれ違いがあるようです。

### invalidateAndCancel
* 未処理のタスクをすべてキャンセルしてから、セッションを無効にします。
* https://developer.apple.com/documentation/foundation/nsurlsession/1411538-invalidateandcancel

### finishTasksAndInvalidate
* セッションを無効にし、未処理のタスクを完了させる。
* https://developer.apple.com/documentation/foundation/nsurlsession/1407428-finishtasksandinvalidate?language=objc

### flushWithCompletionHandler:
* クッキーとクレデンシャルをディスクに書き出し、一時的なキャッシュを消去し、将来の要求が新しいTCP接続で確実に発生するようにする。
* https://developer.apple.com/documentation/foundation/nsurlsession/1411622-flushwithcompletionhandler?language=objc

### resetWithCompletionHandler:
* すべてのCookie、キャッシュ、資格情報ストアを空にし、ディスクファイルを削除し、進行中のダウンロードをディスクにフラッシュし、将来の要求が新しいソケットで発生するようにします
* https://developer.apple.com/documentation/foundation/nsurlsession/1411479-resetwithcompletionhandler?language=objc

#NSURLSessionインスタンスのメモリリーク解消

今回は画像をアップロードする処理だったので、未処理タスクを完了させる`finishTasksAndInvalidate `で解放してみました。

```swift
let mySession = URLSession.init(configuration: URLSessionConfiguration.default)
//urlインスタンスはURL型
let task = session.dataTask(with: url, completionHandler: {
    data, response, error in
    session.finishTasksAndInvalidate() // 追加
    // do something.
    })
task.resume()

```
completionHandlerの一行目に入れてみました。
すると



![20170614_1497405301.jpg](https://qiita-image-store.s3.amazonaws.com/0/31587/d6bf6955-417d-1d75-2af7-ca8000a2d475.jpeg "20170614_1497405301.jpg")

なんということでしょう。
あんなにリークされていたNSURLSessionインスタンスが綺麗さっぱりなくなりました。

(のこっているリークはまた別な理由。）

#さいごに
NSURLSessionインスタンスが勝手に消えると思い込んで２日間悩みました。
みなさんもNSURLSession使う時は解放のメソッドを忘れないようにしましょう。

# 参考
* [NSURLSession](https://developer.apple.com/documentation/foundation/nsurlsession?language=objc)
* [[iOS] NSURLSessionのメモリリークに気をつける](http://chicketen.blog.jp/archives/1310911.html)
* [URLセッションのライフサイクル](https://developer.apple.com/jp/documentation/Cocoa/Conceptual/URLLoadingSystem/NSURLSessionConcepts/NSURLSessionConcepts.html#//apple_ref/doc/uid/10000165i-CH2-SW1)