protocolを介しても相互参照でメモリリークするのか気になったので調べてみた
結論
ios設計パターンを読んでいてMVPの章でprotocolを介してViewとPresenterをつないでいました。
protocolをメンバとして弱参照で持っていたのですが、protocolは実態がないのでweakつけなくても大丈夫なんじゃないか?疑問に思って確かめてみました。
結論からいうと、protocolを介してもweakをつけないと初期化する際にどのViewかを指定する為相互参照が発生しメモリリークします!!
気になったコード
気になったコードの一部を抜粋するとこんな感じでした。
presenterはviewへのoutputとしてUserDetailPresenterOutputをメンバで持っており、viewはpresenterからのinputとしてUserDetailPresenterInputを持っている状態でした。
もとのソースコードはここからみれます。フォルダ5です
presenter
protocol UserDetailPresenterInput {
var repositories: [Repository] { get }
func repository(forRow row: Int) -> Repository?
func viewDidLoad()
}
protocol UserDetailPresenterOutput: AnyObject {
func updateRepositories(_ repositories: [Repository])
}
final class UserDetailPresenter: UserDetailPresenterInput {
private var userName: String
private(set) var repositories: [Repository] = []
private weak var view: UserDetailPresenterOutput!
private var model: UserDetailModelInput
init(userName: String, view: UserDetailPresenterOutput, model: UserDetailModelInput) {
self.userName = userName
self.view = view
self.model = model
}
〜省略〜
}
View
final class UserDetailViewController: UIViewController {
@IBOutlet private weak var tableView: UITableView!
private var presenter: UserDetailPresenterInput!
func inject(presenter: UserDetailPresenterInput) {
self.presenter = presenter
}
〜省略〜
}
私が勘違いした原因としてはPresenterを作成しているところまでしっかりみていなくて、PresenterがProtocolという概念をメンバに持っていると考えていた為です。
実際はAppDelegateでDIの注入を行なっており、そこでUserDetailViewControllerを渡していました。
Presenterが持っているプロトコルの実態はUserDetailViewControllerだったのでweakをつける必要がありました。
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let searchUserViewController = UIStoryboard(name: "SearchUser", bundle: nil).instantiateInitialViewController() as! SearchUserViewController
let navigationController = UINavigationController(rootViewController: searchUserViewController)
let model = SearchUserModel()
let presenter = SearchUserPresenter(view: searchUserViewController, model: model)
searchUserViewController.inject(presenter: presenter)
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = navigationController
window?.makeKeyAndVisible()
return true
}
Debug Memory Graphでメモリリークしているか確認してみた
本当にメモリリークしているのか確かめてみたくなったので、weakを外し強参照でメンバを持つようにしてDebug Memory Graphを使ってみました。
結果として画像のように相互参照が発生しメモリリークしてしまっていることがわかります。