日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

iOS之深入定制基于PLeakSniffer和MLeaksFinder的内存泄漏检测工具

發布時間:2024/5/21 编程问答 41 豆豆
生活随笔 收集整理的這篇文章主要介紹了 iOS之深入定制基于PLeakSniffer和MLeaksFinder的内存泄漏检测工具 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一、背景

  • 在編寫日常業務代碼時,或多或少都會引入一些導致內存泄漏的代碼,而這種行為又很難被監控,這就導致應用內存泄漏的口子越開越大,直接影響到線上應用的穩定性。
  • 雖然 Xcode 的 Instrucment 提供了 Leaks 和 Allocations 工具能精準地定位內存泄漏問題,但是這種方式相對比較繁瑣,需要開發人員頻繁地去操作應用界面,以觸發泄漏場景,所以 Leaks 和 Allocations 更加適合定期組織的大排查,作為監測手段,則顯得笨重。
  • 對于內存泄漏的監測,業內已經有了兩款成熟的開源工具,分別是 PLeakSniffer 和 MLeaksFinder。
    • PLeakSniffer 使用 Ping-Pong 方式監測對象是否存活,在進入頁面時,創建控制器關聯的一系列對象代理,根據這些代理在控制器銷毀時能否響應 Ping 判斷代理對應的對象是否泄漏。
    • MLeaksFinder 則是在控制器銷毀時,延遲 3s 后再向監測對象發送消息,根據監測對象能否響應消息判斷其是否泄漏。
  • PLeakSniffer 和 MLeaksFinder 這兩個基本能覆蓋大部分對象泄漏或者延遲釋放的場景,考慮到性能損耗以及內存占用因素,個人更偏向于第二種方案。
  • 個人使用 MLeaksFinder,還存在以下問題:
    • 沒有處理集合對象;
    • 沒有處理對象持有的屬性;
    • 每個對象都觸發 3s 延遲機制,沒有緩存后統一處理;
    • 檢測結果輸出分散。
  • PLeakSniffer 存在以下問題:
    • 沒有處理集合對象;
    • 處理對象持有屬性時,系統類過濾不全面;
    • 處理對象持有屬性時,通過 KVC 訪問屬性導致一些懶加載的觸發;
    • 無法處理未添加到視圖棧中的泄漏視圖;
    • 檢測結果輸出分散。
  • 對于檢測到泄漏對象的交互處理,兩者都提供了終端 log 輸出和 alert 提示功能,MLeaksFinder 甚至可以直接通過斷言中斷應用,這種提示在開發階段尚可接受,但是在提測階段,強交互會給測試人員造成困擾。至于為什么在提測階段還要集成泄漏監測工具,主要有兩個原因:
    • 應用功能過多的情況下,開發人員無法兼顧到老頁面,一些老頁面的泄漏場景可以通過測試人員在測試時觸發,收集之后再統一處理;
    • 在組件化開發環境下,開發人員可能并沒有集成泄漏監測工具,這種情況下,需要在提測階段統一收集沒有解決的泄漏問題。
  • 因此,對于監測輸出的訴求有兩點:
    • 開發時,通過終端日志提示開發者出現了內存泄漏;
    • 提測時,收集內存泄漏的信息并上傳至效能后臺,統一分配處理;

二、監測入口

  • 和 MLeaksFinder 一樣,選擇延遲 3s 的機制來判斷對象是否泄漏,但是實現的細節略有差別。首先,監測入口變更為 viewDidDisappear: 方法,只需在控制器被父控制器中移除或者被 Dismissed 時,觸發監測動作即可:
- (void)LeaksMonitor_viewDidDisappear:(BOOL)animated {[self LeaksMonitor_viewDidDisappear:animated];if (![self isMovingFromParentViewController] && ![self isBeingDismissed]) {return;}[[YDWLeaksMonitor shared] detectLeaksForObject:self];}
  • 在應用中,還有一種監測入口出現在變更根控制器時,由于直接設置根控制器不會觸發 viewDidDisappear 方法,所以需要另外設置 :
- (void)LeaksMonitor_setRootViewController:(UIViewController *)rootViewController {if (self.rootViewController && ![self.rootViewController isEqual:rootViewController]) {[[YDWLeaksMonitor shared] detectLeaksForObject:self.rootViewController];}[self LeaksMonitor_setRootViewController:rootViewController];}
  • 為了能夠統一處理控制器及其持有對象,可以像 PLeakSniffer 一樣,給每個對象包裝一層代理 :
@interface YDWLeakObjectProxy : NSObject// 持有 target 的對象弱引用@property (weak, nonatomic) id host;// 被 host 持有的對象弱引用@property (weak, nonatomic, readonly) id target;@end
  • 只要 host 釋放了而 target 沒釋放,則視 target 已泄漏,如果 host 未釋放,則不檢測 target,然后使用一個 collector 去收集這些對象對應的 proxy ,在收集完之后統一監測 collector 中的所有 proxy ,這樣就可以在一個控制器監測完成后,統一上傳監測出的泄漏點 :
- (void)detectLeaksForObject:(id <YDWLeakObjectProxyCollectable>)object {// 收集控制器關聯的所有 proxy// 收集之后再統一處理,避免對每一個對象都進行 3s 檢測YDWLeakObjectProxyCollector *collector = [[YDWLeakObjectProxyCollector alloc] init];YDWLeakContext *context = [[YDWLeakContext alloc] init];context.host = object;(void)[object LeaksMonitor_collectProxiesForCollector:collector withContext:context];// 檢測 3s 之后,collector 中的所有 proxy 是否正常[self detectProxyCollector:collector];}

三、收集對象信息

  • 因為要對不同的類做特異化處理,因此先定義一個協議,通過這個協議中的 collect 方法去收集不同類實例化對象的 proxy :
@protocol YDWLeakObjectProxyCollectable <NSObject>/**收集對象及其名下的所有成員變量對應的 proxy@param collector 收集器,存儲 proxy@param ctx 上下文*/- (void)LeaksMonitor_collectProxiesForCollector:( YDWLeakObjectProxyCollector * _Nonnull )collector withContext:( YDWLeakContext * _Nullable )ctx;@end
  • 關鍵在于如何讓 NSObject 實現此協議,主要有四個步驟 :
    • 過濾系統類調用;
    • 向 collector 添加封裝的 proxy;
    • 循環遍歷對象對應的非系統類 / 父類屬性,找出 copy / strong 類型屬性,并獲取其對應的成員變量值;
    • 向收集的所有成員變量對象發送 collect 方法。
  • NSObject 實現 collect 協議方法后,其子類就可以通過這個方法遞歸地收集名下需要監測的屬性信息。比如對于集合類型 NSArray ,實現協議方法如下,表示收集自身和每個集合元素的信息,不過由于 NSArray 是系統類,所以其實例化對象并不會被收集進 collector ,如果要收集系統類的屬性信息,只能通過讓系統類實現協議并重載 collect 方法,手動向屬性值發送 collect 消息實現,UIViewController 的 childViewControllers、presentedViewController、view 屬性也同理 :
- (void)LeaksMonitor_collectProxiesForCollector:(YDWYDWLeakObjectProxyCollector *)collector withContext:(YDWLeakContext *)ctx {[super LeaksMonitor_collectProxiesForCollector:collector withContext:ctx];[self enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {if ([obj conformsToProtocol:@protocol(YDWLeakObjectProxyCollectable)]) {[obj LeaksMonitor_collectProxiesForCollector:collector withContext:LM_CTX_D(ctx, @"contains")];}}];}
  • 需要注意的是,直接調用屬性的 getter 方法獲取屬性值,可能會觸發屬性懶加載,導致出現意料之外的問題 (比如調用 UIViewController 的 view 會觸發 viewDidLoad),所以要通過 object_getIvar 去獲取屬性對應的成員變量值。當然,這種處理方式會導致無法收集某些沒有對應成員變量值的屬性,比如關聯對象、控制器的 view 等屬性,權衡利弊之后,可以選擇忽略這種屬性的監測。
  • 除了收集必要的對象信息之外,我還記錄了監測對象的引用路徑信息,也就是上面 LM_CTX_D 宏做的事情。有些情況下,對象的引用路徑能幫助我們發現,路徑上的哪些操作導致了對象的泄漏,特別是在網頁上瀏覽泄漏信息時,如果只有泄漏對象類和引用泄漏對象類兩個信息,脫離了對象泄漏時的上下文環境,會增加修復的難度。有了引用路徑信息后,輸出的泄漏信息如下 :
[O : YDWViewController.view->UIView.subviews->__NSArrayM(contains)->A.subviews->__NSArrayM(contains)->OYDWViewController : YDWViewController.childViewControllers->YDWViewController__NSCFTimer : YDWViewController.timer->__NSCFTimer]

四、過濾系統類

  • 系統類信息并不是需要關心的,過濾掉并不會影響到最終的監測結果。目前我嘗試了兩種方式來確定一個類是否為系統類:
    • 通過類所在 NSBundle 的路徑;
    • 通過類所在地址。
  • 第一種的邏輯較為簡單,代碼如下:
BOOL LMIsSystemClass(Class cls) {NSBundle *bundle = [NSBundle bundleForClass:cls];if ([bundle isEqual:[NSBundle mainBundle]]) {return NO;}static NSString *embededDirPath;if (!embededDirPath) {embededDirPath = [[NSBundle mainBundle].bundleURL URLByAppendingPathComponent:@"Frameworks"].absoluteString;}return ![bundle.bundlePath hasPrefix:embededDirPath];}
  • 應用的主二進制文件,和開發者添加的 embeded frameworks 都會在固定的文件目錄下,所以直接比對路徑前綴即可。
  • 第二種方式的實現步驟如下:
    • 遍歷所有的 image ,通過 image 的名稱判斷是否為系統 image;
    • 緩存所有系統 image 的起始位置,也就是 mach_header 的地址;
    • 判斷類是否為系統類時,使用 dladdr 函數獲取類所在 image 的信息,通過 dli_fbase 字段獲取起始地址;
    • 比對 image 的起始地址得知是否為系統類。
  • 實際嘗試下來后,發現第二種方式耗時會比第一種多,dladdr 函數占用了大部分時間(內部會遍歷所有 image 的開始結束地址,和傳入的地址進行比對),所以最終選擇了第一種方式作為判斷依據。
  • 過濾系統類時,針對那種會自泄漏的對象,需要進行特殊處理,不予過濾。比如 NSTimer / CADisplayLink 對象的常見內存泄漏場景,除了 target 強引用控制器造成循環引用域外,還有一種是打破了循環引用但沒有在控制器銷毀時執行 invalidate 操作,因為 NSTimer 由 RunLoop 持有,不手動停止的情況下,就會造成泄漏。

五、局限性

  • 基于延時的內存泄漏監測機制雖然適用于大部分視圖、控制器和一般屬性的泄漏場景,但是還有少部分情況,這種機制無法處理,比如單例對象和共享對象。
  • 首先說下單例對象,假設有 singleton 屬性,其 getter 方法返回 Singleton 單例,這時延時監測機制無法自動過濾這種情況,依然會認為 singleton 泄漏了。有一種檢測屬性返回值是否為單例的方法,就是向返回值對應類發送 init 或者 share 相關方法,通過方法返回值和屬性返回值的對比結果來判斷,但是事實上我們無法確定業務方的單例是否重寫了 init,也無法獲知具體的單例類方法,所以這種方案適用面比較局限。單例對象的處理,目前還是通過白名單的方式處理較為穩妥。
  • 共享對象的應用場景就比較普遍了,比如現有 A,B 頁面,A 頁面持有模型 M ,在跳轉至 B 頁面時,會將 M 傳遞給 B ,B 強引用了 M ,當 B 銷毀時, M 不會銷毀,而 M 又是 B 某個屬性的值,所以監測機制會判斷 M 泄漏了,實際上 M 只是 A 傳遞給 B 的共享對象。在一個控制器做完檢測就需要上傳至效能后臺的情況下,共享對象還沒有很好的處理方法,后期考慮結合 FBRetainCycleDetector 查找泄漏對象的循環引用信息,然后一并上傳至效能后臺,方便排查這種情況。因為每次 pop 都使用 FBRetainCycleDetector 檢測控制器會比較耗時、甚至會造成延遲釋放和卡頓,所以先用延時機制找出潛在的泄漏對象,再使用 FBRetainCycleDetector 檢測這些泄漏對象,能極大得減少需要處理的對象數量。最終網頁呈現的效果如下:

六、總結

  • 像內存泄露這種問題,最好在應用初期就開始著手監測和解決,否則當應用功能代碼逐漸增多后,回過頭來處理這種問題費時費力,還是比較麻煩的。
  • 基于 PLeakSniffer 和 MLeaksFinder 監測工具的基礎上,結合團隊業務情況,進行了一些的改造,添加了集合對象的處理、引用路徑的記錄、對象的統一檢測等功能,優化了部分有問題的代碼,在一定程度上提升了延時機制的可用性。

總結

以上是生活随笔為你收集整理的iOS之深入定制基于PLeakSniffer和MLeaksFinder的内存泄漏检测工具的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 欧美一区网站 | 中文字幕3区 | 伦在线| 91免费看黄 | 日本免费观看视频 | 91视频一区二区 | 免费在线视频一区二区 | 亚洲综合色小说 | 日韩影视一区二区三区 | 久久精品视频16 | 嫩色av| 性色av蜜臀av浪潮av老女人 | 91极品美女| 午夜免费福利小电影 | 男女插孔视频 | 带aaa级的网名 | www.4hu95.com四虎 极品在线视频 | 亚洲一区精品视频 | 五月视频 | 波多野结衣视频在线观看 | 青草视频在线观看视频 | 国产精品天天看 | 国产成人在线观看免费网站 | 成人国产一区二区三区精品麻豆 | 成人av影院 | 香蕉视频国产在线观看 | 日韩欧美一区二区三区久久婷婷 | 中文字幕av有码 | 一级片在线观看免费 | 亚洲熟悉妇女xxx妇女av | 91丨国产 | 亚洲永久无码7777kkk | 国产一区二区免费电影 | 色老汉av一区二区三区 | 一级特黄bbbbb免费观看 | 最近免费中文字幕大全免费版视频 | 亚洲天堂小说 | 欧洲黄色片 | 嫩模一区二区三区 | 色人天堂 | 欧美91精品久久久久国产性生爱 | 亚洲福利社区 | 桃色一区 | 97avcc| 日韩成人不卡 | 国产免费成人在线视频 | 四虎新网站 | 久久精品激情 | 成人黄色免费视频 | 久久久久久久久亚洲 | 天堂在线官网 | 国产精品视频大全 | 综合婷婷久久 | 69xxx免费视频| 日本黄图 | 神马影院一区二区 | 色天堂在线视频 | 欧美一区二区三区四区在线 | 国产一区二区三区视频网站 | www.激情五月 | 黄色片视频在线观看 | 午夜欧美视频 | 男人天堂你懂的 | 久色资源| 伊人青青久久 | 一区二区三区在线 | 在线观看91视频 | 久草福利在线视频 | 亚洲校园激情 | 国产日日日 | 欧美成人一区二免费视频软件 | 久久精品黄aa片一区二区三区 | 国产精品久久精品三级 | 99在线观看 | 精品日韩一区 | 黄视频网站在线看 | h部分肌肉警猛淫文 | 欧美顶级metart裸体全部自慰 | 中文字幕亚洲一区二区三区五十路 | 久久久午夜 | 亚洲成人va | 婷婷av一区二区三区 | 女人被男人躁得好爽免费视频 | 亚洲人人夜夜澡人人爽 | 国产91熟女高潮一区二区 | 久久国产香蕉 | 欧美精品1区2区3区 精品成人一区 | 色av性av丰满av | 日韩欧美一卡二卡 | 欧美成人免费观看 | 一级特级片 | 波多野结衣在线视频免费观看 | 日本少妇裸体 | 岛国大片在线免费观看 | 亚洲另类自拍 | 香蕉成人在线视频 | 9.1成人看片免费版 日韩经典在线 | 91视频在线免费 | 日本爽爽爽 |