自社のWebAPIの通信にNSURLSessionを使って通信をしていました。
Debug memory graghをみるとNSURLSessionのインスタンスでメモリリークが起こっていました。
それを直した記事です
環境
- Xcode8.3.3
- Swift3.1
- iOS10.3.2
問題点
NSURLSessionを使って通信処理をする実装をしていました。
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をみるとたくさんのメモリリークがありました。
ほっとくとメモリリークでアプリが落ちるなど品質に問題が出てきてしまいます。
なんとかしたいです。
Backtraceのメソッドを見るとNSURLSessionのインスタンスにたどり着きました。
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
で解放してみました。
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の一行目に入れてみました。
すると
なんということでしょう。
あんなにリークされていたNSURLSessionインスタンスが綺麗さっぱりなくなりました。
(のこっているリークはまた別な理由。)
#さいごに
NSURLSessionインスタンスが勝手に消えると思い込んで2日間悩みました。
みなさんもNSURLSession使う時は解放のメソッドを忘れないようにしましょう。