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を使ってみました。

結果として画像のように相互参照が発生しメモリリークしてしまっていることがわかります。

Android

前の記事

ADBのパスを探す