iOS效能優化之記憶體洩露檢測
iOS效能優化是一個比較複雜的問題,其中之一就是記憶體洩露檢測,很多人會第一時間想到使用Instruments。由於學習成本比較高,專業詳細的教程也比較少,在學習了基本介紹後就望而生畏了。今天瀏覽了微信閱讀團隊的技術部落格,發現了一個非常友好的記憶體洩露檢測庫 ofollow,noindex">MLeaksFinder
MLeaksFinder
簡單介紹一下MLeaksFinder。
官方解釋:
具體的方法是,為基類 NSObject 新增一個方法 -willDealloc 方法,該方法的作用是,先用一個弱指標指向 self,並在一小段時間(3秒)後,通過這個弱指標呼叫 -assertNotDealloc,而 -assertNotDealloc 主要作用是直接中斷言。
核心程式碼:
- (BOOL)willDealloc { __weak id weakSelf = self; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [weakSelf assertNotDealloc]; }); return YES; } - (void)assertNotDealloc { NSAssert(NO, @“”); }
這樣,當我們認為某個物件應該要被釋放了,在釋放前呼叫這個方法,如果3秒後它被釋放成功,weakSelf 就指向 nil,不會呼叫到 -assertNotDealloc 方法,也就不會中斷言,如果它沒被釋放(洩露了),-assertNotDealloc 就會被呼叫中斷言。這樣,當一個 UIViewController 被 pop 或 dismiss 時(我們認為它應該要被釋放了),我們遍歷該 UIViewController 上的所有 view,依次調 -willDealloc,若3秒後沒被釋放,就會中斷言。
例如在UIViewController的分類中,使用 Method Swizzling ,hook掉了 viewDidDisappear:
, viewWillAppear:
, dismissViewControllerAnimated:completion:
等方法,讓他們都執行willDealloc方法,這樣,在不入侵開發程式碼的情況下,為UIViewController添加了檢查記憶體洩露的功能(AOP)。
安裝
安裝非常簡單,直接在Podfile中新增 pod 'MLeaksFinder'
,你不需要在任何檔案中引入標頭檔案,執行 pod install
後,直接構建或者run一下就好了。
案例
在iOS中,比較常見的記憶體洩露場景就是迴圈引用。作為一個iOS工程師,應該時刻警惕迴圈引用帶來的問題。然而在趕工或者稍有不慎的情況下,還是會出現一些有問題的程式碼。
對於Xcode來說,編譯器會對編寫程式碼中明顯的迴圈引用進行提示,比如對於self.property持有的block中,使用self,Xcode就會顯示警告。但是,對於以下程式碼,Xcode就不會警告:
LessonWithoutJobCell *cell = [tableView dequeueReusableCellWithIdentifier:reusedID1]; if (!cell) { cell = [[LessonWithoutJobCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reusedID1]; } cell.model = model; [cell setEnterBlock:^{ if (self.classBlock) { self.classBlock(model); } }]; return cell;
在tableView中的某一個cell,持有了一個block,在block中使用了self.classBlock,乍一看好像沒有問題啊。
真的沒有問題嗎?
在這個控制器被pop後,MLeaksFinder立刻就彈出了彈窗

IMG_6481.PNG
點選Retain Cycle

IMG_6479.PNG
仔細想想,真的是出現迴圈引用。首先是self引用了tableView,tableView引用了cell,cell中的block引用了self,這樣,在控制器被pop之後,這個view還存在著強引用,這樣它的記憶體就得不到釋放,這樣就造成了迴圈引用,經歷頻繁的push和pop操作後,記憶體將會暴增。其實這是一個很低階的bug了,但是稍不注意,就被忽略了。
既然發現了問題,解決迴圈引用就非常簡單了。在cell的block外面,把self定義為weak型別,打破迴圈引用:
······ __weak typeof(self) weakSelf = self; ······ //把block中的self換成weakSelf即可 [cell setEnterBlock:^{ if (weakSelf.classBlock) { weakSelf.classBlock(model); } }];
至此,MLeaksFinder的簡單使用就介紹完了,真的十分簡單就找到了一些潛在的問題,對於工程幾乎0入侵,強烈推薦使用。