Swift メモリリークのつまずきポイント

「**メモリリークが発生していることはわかるが、その原因がわからない。**」

このような質問を、聞くことが多々あります。

行き詰まる方の助けになれればと思い、
今回、そのポイントをまとめてみました。

~~知ってる方は「あるある！！」と思っていただければ幸いです。~~

## メモリリークって何？

![tumatta.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1139353/95da42e6-83ce-347a-9f21-255416d94e68.png)
何かが邪魔をして、画面を閉じてもインスタンスが残ったまま消えないという現象。
これでは、メモリが解放されません。

つまり、これを繰り返していると **どんどん動作が重くなり**、最悪の場合 **アプリが落ちます**。


![memoryLeak.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1139353/0e6dfcd8-f163-49a5-2751-3a8d627d857b.png)
アプリが落ちる…

それだけは避けたいですね。
ということで…
早速、見落としやすいポイントを見てみましょう。

## メモリリークのパターン・解消

メモリリークの発生箇所は、決まって２つ。

- **クロージャー** (`closure`)
- **デリゲート**(`delegate`)

では、何が原因？

全て **強参照** が原因です。

### 1. クロージャー  (closure)

クロージャー とは、処理（関数）そのものを「値」として **変数に格納できる** もの。
**引数として使えば** 、特定の処理を設定して…呼び出した後に実行したり。
とにかく便利なんです。彼。

ただ、そのままにしておくと**メモリリーク**を引き起こします。

ご安心ください。**弱参照** `[weak self]` で**全て解決します**。

~~~swift
class TestClass {
  var text = "test"
  lazy var testClosure: (() -> ()) = { [weak self] in // <- ここ
    print(self?.text)
  }
}
~~~

クロージャー を生成した際、**クラス内の変数・関数を呼び出していないか（ `self` を普通に、または暗黙的に使っていないか）をチェックしてください**。

もし使っていたら、`[weak self]` を使いましょう。

### 別クラスからクロージャーをセット

**TestClass2** という別のクラスから、**testClass** にクロージャーを**変数にセットします**。

~~~swift
class TestClass2 {
  var text = "test"
  var testClass = TestClass()
  
  func setClosure() {
    testClass.testClosure = { [weak self] in
        print(self?.text)
    }
  }
}
~~~

この場合、残念ながら **弱参照** `[weak self]` だけでは問題は解決しません。
別クラスのインスタンスが絡んでしまって、**自動ではインスタンスが解放されなくなります**。
結果、TestClass が解放されません。（TestClass2 は解放される）

インスタンスを解放する前に、
**処理を書いて、クロージャーを解放する**必要があります。

~~~swift
class TestClass {
  var text = "test"
  var testClosure: (() -> ())?
  
  ...
}
~~~

~~~swift
testClosure = nil
~~~



### DispatchQueue

**`DispatchQueue`** や **`UIView.animate`** 等の場合は、**基本的には不要です**。

ただ、メモリリーク以外の問題があります。
実行時点で既に self となるインスタンスが解放済みである場合、落ちます。（nilを強参照したら…そりゃね）
危なさそうな場所には、つけておいた方が無難ですね。

~~~swift
DispatchQueue.main.async { [weak self] in // <- ココ
    self?.image = image
}
~~~

要は、一瞬で終わらないような非同期処理のあとに、DispatchQueueを使ってるケース。
「**Web経由の非同期処理の後に、UIを変更する**」…といった場合に必要です。
（その場合、DispatchQueue より外の問題だけど）

危なさそうな場所には、つけておいた方が無難ですね。

- [[weak self]の使いどころ swift/kotlin勉強会#2用 //qiita](https://qiita.com/hs7/items/1f17baf43b943949a7ff)



###  `guard let` でもっとシンプルに

~~~swift
testClosure = [weak self] in
  guard let `self` = self else { return }
  
  // 中の処理
  print(self.text)
  
}
~~~

普段通りの書き方ができるほか、
クラス自体がすでに解放されている場合は、処理を安全にキャンセルします。

### 2. デリゲート (delegate)

外側にあるクラスから処理を呼び出したい時に使う、アレですね。
その点は、もうご存知かと思います。

こちらも強参照によるもの。
デリゲートを自作する場合、そのままだとメモリリークが発生します。

ここでの **ポイントは２つ** です。

- delegate を格納する場合、**弱参照** `weak` にする。
- delegate の `protocol` は、`AnyObject` を継承しておく。

~~~swift
class TestClass {
    weak var delegate: SampleDelegate?
    ...
}
~~~

~~~swift
protocol SampleDelegate: AnyObject {
    ...
}
~~~

これだけ。

### URLSession

`URLSession` を使用する際にも注意。元々、デリゲート自体が強参照されています。
流石に、ここを弱参照にすることはできませんね。

ここでは、**対応策が２つ** ほどあります。

- **`URLSession.shared`** を使う（こちらを使っているなら、**そもそも発生しない**。）
- `invalidateAndCancel()`  等の **専用メソッド** で、セッションを明示的に無効化する。

前者は `URLSession.shared` が使える場合ですね。
細かい設定は使えませんが、大抵はこちらで良いかと。

後者の専用メソッドについては、こちらの記事が参考になります。

- [NSURLSessionがメモリリークしてしまうのをなんとかした //qiita](https://qiita.com/SatoTakeshiX/items/b65f5518a025c23e1d9d)

### 【その他】

本記事は例の紹介です。
極力、別の話題は省いています。

メモリリークの仕組みに関しては、以下の記事が参考になります。

- [Swiftのメモリリークについてまとめてみた //qiita](https://qiita.com/ryu1sazae/items/201275f9ac3af1ec9e64)

**ARC(Automatic Reference Counting)** と呼ばれる機能を、swiftは有しています。
今回の問題も、全てはこれに基づいていますね。

![cheakMemory.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1139353/daadcf7c-6aaa-5a7c-4d3f-c69a81ec1431.png)

また、
**メモリリークが発生しているか、チェックしたい** 場合はこちら。

- [iOSアプリのメモリリーク (Memory Leak) の検出とデバッグの方法を、ネコに関するプログラムで学びましょう。 //qiita](https://qiita.com/MaShunzhe/items/8073d32d7aee28578cab)
- [[iOS] メモリリークをXcodeでチェックして、リークしないようにしたい！ //DevelopersIO](https://dev.classmethod.jp/articles/ios-memory-leak-check-and-prevent-190508/)

## ということでね

メモリリークのパターン（例）をまとめました！！~~（パターン紹介以外はほぼ記事のリンク紹介ですが）~~
思い当たる内容はあげましたが、その他にもある場合は教えていただけると助かります。

原因に関する詳しい事柄は、多くの方々が記事にまとめてくれていると思います。
そのため、本記事は「メモリリークから原因につなげるための記事」として書きました。

原因さえわかれば、それらの記事にたどり着けると思います。