iOS之深入解析内存管理NSTimer的强引用问题
生活随笔
收集整理的這篇文章主要介紹了
iOS之深入解析内存管理NSTimer的强引用问题
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
一、強(qiáng)引用問題分析
- 現(xiàn)在有兩個控制器 A、B,從 A push 到 B 控制器,在 B 控制器中有如下代碼:
- 當(dāng)從控制器 B pop 回到控制器 A 時,我們發(fā)現(xiàn)定時器沒有停止,其 popHome 方法仍然在執(zhí)行,這是為什么呢?
- 在控制器 B 的 dealloc 方法打上斷點(diǎn),可以看到程序并沒有執(zhí)行。因此可以得出,控制器 B 沒有被釋放,即控制器 B 沒有執(zhí)行 dealloc 方法,從而導(dǎo)致 timer 也無法停止運(yùn)行和釋放。
- 重寫 didMoveToParentViewController 方法,可以看到:當(dāng)控制器 B 退出到上層控制器的時候消除了引用,dealloc 方法被調(diào)用,timer 被銷毀:
- 定義 timer 時,可以采用閉包的形式,不需要指定 target,就不會產(chǎn)生 timer 無法被釋放的問題:
- 經(jīng)過上面的兩種方式,都可以正常處理 timer 釋放的問題,那么這又是為什么呢?
- 通過查看官方文檔對 timerWithTimeInterval:target:selector:userInfo:repeats: 方法中對 target 的描述:
- 從文檔中描述可以看出,timer 對傳入的 target 具有強(qiáng)持有,即 timer 持有 self,又由于 timer 是定義在控制器 B 中,所以 self 也持有 timer,因此 self -> timer -> self 構(gòu)成了循環(huán)引用。
- 我們知道:循環(huán)引用可以通過 __weak 即弱引用來解決,那么我們代碼修改如下:
- 再次運(yùn)行程序,進(jìn)行 push-pop 跳轉(zhuǎn),卻發(fā)現(xiàn)問題還是存在,即定時器方法仍然在執(zhí)行,并沒有執(zhí)行 B 的 dealloc 方法,這是為什么呢?
- 使用 __weak 雖然打破了 self -> timer -> self 之前的循環(huán)引用,即引用鏈變成了 self -> timer -> weakSelf -> self,但是我們遺漏了一個點(diǎn),Runloop 對 timer 也強(qiáng)持有,因?yàn)?Runloop 的生命周期比控制器 B 更長,所以導(dǎo)致了 timer 無法被釋放,同時也導(dǎo)致了控制器 B 的 self 也無法被釋放。
- 沒有添加 weakSelf 之前的引用鏈如下:
- 添加 weakSelf 之后的引用鏈變成了如下所示:
二、weakSelf 與 self
- 對于 weakSelf 和 self,我們關(guān)心的是:
-
- weakSelf 會對引用計(jì)數(shù)進(jìn)行 +1 操作嗎?
-
- weakSelf 和 self 的指針地址相同嗎,是指向同一片內(nèi)存嗎?
- 在添加 weakSelf 前后打印 self 的引用計(jì)數(shù):
- 運(yùn)行程序,可以看到前后 self 的引用計(jì)數(shù)都是 8,因此可以判定 weakSelf 沒有對內(nèi)存進(jìn)行 +1 操作。
- 繼續(xù)打印 weakSelf 和 self 對象,以及指針地址:
- 可以看出,當(dāng)前 self 取地址和 weakSelf 取地址的值是不一樣的,意味著有兩個指針地址,指向的是同一片內(nèi)存空間,即 weakSelf 和 self 的內(nèi)存地址是不一樣,都指向同一片內(nèi)存空間的。
- 此時 timer 捕獲的是 <ViewController: 0x7fea4f024200>,是一個對象,所以無法通過 weakSelf 來解決強(qiáng)持有,即引用鏈關(guān)系為:NSRunLoop -> timer -> weakSelf(<ViewController: 0x7fea4f024200>),所以 RunLoop 對整個對象的空間強(qiáng)持有,runloop 沒停,timer 和 weakSelf 就無法被釋放。
- block 的循環(huán)引用,與 timer 的是有區(qū)別的,通過 block 底層原理的方法 __Block_object_assign 可知,block 捕獲的是對象的指針地址,即 weakself 是臨時變量的指針地址,與 self 無關(guān),因?yàn)?weakSelf 是新的地址空間,所以此時的 weakSelf 相當(dāng)于中間值,其引用關(guān)系鏈為 self -> block -> weakSelf(臨時變量的指針地址),可以通過地址拿到指針。
- block 和 timer 循環(huán)引用的模型如下:
-
- timer 模型:self -> timer -> weakSelf -> self,當(dāng)前的 timer 捕獲的是控制器 B 的內(nèi)存,即 vc 對象的內(nèi)存,即 weakSelf 表示的是 vc 對象;
-
- Block 模型:self -> block -> weakSelf -> self,當(dāng)前的 block 捕獲的是指針地址,即 weakSelf 表示的是指向 self 的臨時變量的指針地址。
三、強(qiáng)引用的解決方案
① 當(dāng) controller 界面 pop 到上層界面的消除引用
- 根據(jù)上文中的分析中,由于 Runloop 對 timer 的強(qiáng)持有,導(dǎo)致 Runloop 間接的強(qiáng)持有了self(因?yàn)?timer 中捕獲的是 vc 對象),所以導(dǎo)致 dealloc 方法無法執(zhí)行,需要查看在 pop 時,是否還有其他方法可以銷毀 timer,這個方法就是 didMoveToParentViewController。
- didMoveToParentViewController 方法,是用于當(dāng)一個視圖控制器中添加或者移除 viewController 后,必須調(diào)用的方法,目的是為了告訴系統(tǒng),已經(jīng)完成添加/刪除子控制器的操作。
② 中介者模式,不直接使用 self
- 在 timer 模式中,主要是 popHome 能執(zhí)行,并不用管 timer 捕獲的 target 是誰,由于這里不能使用self(因?yàn)闀袕?qiáng)持有問題),所以可以將 target 換成其他對象,例如將 target 換成 NSObject 對象,將 popHome 交給 target 執(zhí)行:
- 運(yùn)行程序,發(fā)現(xiàn)程序執(zhí)行 dealloc 之后,timer 還是會繼續(xù)執(zhí)行,這是因?yàn)殡m然解決了中介者的釋放,但是沒有解決中介者的回收,即 self.target 的回收。
- 繼續(xù)通過在 dealloc 方法中,取消定時器來解決,代碼如下:
- 再次運(yùn)行程序如下,發(fā)現(xiàn) pop 之后,timer 被釋放,從而中介者也會進(jìn)行回收釋放。
③ 自定義封裝 timer
- 自定義 timerWapper:
-
- 在初始化方法中,定義一個 timer,其 target 是自己,即 timerWapper 中的 timer,一直監(jiān)聽自己,判斷 selector,此時的 selector 已交給了傳入的 target(即 vc 對象),此時有一個方法 popHomeWapper,在方法中,判斷 target 是否存在;
-
-
- 如果 target 存在,則需要讓 vc 知道,即向傳入的 target 發(fā)送 selector 消息,并將此時的 timer 參數(shù)也一并傳入,所以 vc 就可以得知 popHome 方法,就這事這種方式定時器方法能夠執(zhí)行的原因 ;
-
-
-
- 如果 target 不存在,已經(jīng)釋放了,則釋放當(dāng)前的 timerWrapper,即打破了 RunLoop 對 timeWrapper 的強(qiáng)持有 (timeWrapper <-×- RunLoop);
-
-
- 自定義 ydw_invalidate 方法中釋放 timer,這個方法在 vc 的 dealloc 方法中調(diào)用,即 vc 釋放,從而導(dǎo)致 timerWapper 釋放,打破了 vc 對 timeWrapper 的強(qiáng)持有( vc -×-> timeWrapper);
- timerWapper 的使用:
④ 利用 NSProxy 虛基類的子類
- 定義一個繼承自 NSProxy 的子類:
- 將 timer 中的 target 傳入 NSProxy 子類對象,即 timer 持有 NSProxy 子類對象:
- 這樣將強(qiáng)引用的注意力轉(zhuǎn)移成了消息轉(zhuǎn)發(fā),虛基類只負(fù)責(zé)消息轉(zhuǎn)發(fā),即使用 NSProxy 作為中間代理和中間者。
- 那么定義的 proxy 對象,在 dealloc 釋放時,還存在嗎?其實(shí),proxy 對象會正常被釋放,因?yàn)?vc 被釋放,所以可以釋放其持有者,即 timer 和 proxy,timer 的釋放也打破了 runLoop 對 proxy 的強(qiáng)持有,完美的達(dá)到了兩層釋放,即 vc -×-> proxy <-×- runloop。
總結(jié)
以上是生活随笔為你收集整理的iOS之深入解析内存管理NSTimer的强引用问题的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: HarmonyOS之剪贴板的功能和使用
- 下一篇: HarmonyOS之常用组件Text的功