Open6
iOSアプリ開発におけるメモリリーク対策
まず注意すべき点
- ただし必ずしもこれらを使っているからと言って、メモリリークが起きるわけではない
- Delegateパターン
weak var XXDelegate?
- クロージャ
- キャプチャリストをつけて、弱参照にする
[weak self]
- メモリリークで気をつけるべきところのより詳細
- ライブラリを使用して、開発中に検知することも可能
- tableViewのセル数が多く、画像を読み込む必要がある場合
- 全てのセル分の画像を読み込むと、メモリを一気に枯渇させてしまう恐れがある
- SwiftUIの場合は、
LazyVStack
またはLazyHStack
を使用することで、Lazyロードを実現することができる
SwiftUIで注意すべきメモリ対策
- クロージャ
-
AnyView
の使用を避ける
メモリリークが発生しているかどうかの確認方法
-
deinit
を使用して、メモリが解放されていることを確認する
- それによって、循環参照が起きているかどうかを確認することができる
- Xcodeの下にあるメモリデバッグのマークを押して確認できる
- 紫の⚠️が出ているとメモリリークしている
- ただし必ずしもXcodeのツールで見て紫でなかったからといって、メモリリークしていないとは限らない!!
- コードのどの行でメモリリークが発生しているかは、Malloc Stack loggingをEdit Schemeからオンにすると確認できる
- Xcodeに付属しているInstrumentsを使用することでも、メモリリークを検知できる
動画
SwiftUIアプリを作っていて、早速メモリリークが発生
- 紫のマークが出ており、malloc loggingのbacktrackを見たが、なぜか初回起動の@mainで発生している(自分で書いたのは1行だけなのだが)
コード
import SwiftUI
@main
struct XXApp: App {
var body: some Scene {
WindowGroup {
SampleView()
}
}
}
-
Xcodeで確認した結果
-
$main()
の横にカーソルをフォーカスすると、矢印が出てくるので、そこを押すと該当コードにジャンプできる
修正プロセス
- クロージャを使用している場所で、
[weak self]
を忘れている箇所がないか確認- 全てにキャプチャリストをつけたが、まだメモリリークする
- セグメンテッドコントロールを使用している箇所のViewの呼び出しコードをコメントアウトしたところ、発生しなくなった
- Viewの呼び出し方か、ViewModelの渡し方が問題か?
- セグメンテッドコントロールを使用してミニアプリをもう一度作り直してみたところ、ViewModelを渡していなくてもメモリリークは発生していた
- 選択したセグメントによって切り替えるViewの部分を、セグメンテッドコントールを定義しているViewと同じViewのstructではなく、別のstructとして定義すると、メモリリークしなくなる!
- 理由が不明
- SwiftUI Segmented Control Tutorial | iOS 14 | Xcode 12.4を真似てみたところ、そうだとわかった
- ただし、元のアプリでは同様に行ってもメモリリークは解決しなかった・・・
private var content: some View {
Group {
switch selectedIndex {
case .A:
SampleAView(viewModel: SampleAViewModel())
case .B:
SampleBView(viewModel: SampleBViewModel())
}
}
}
メモリリークが発生しうるケース
-
NavigationView
を使用しているとき
-
Button
を使用しているとき