FBMemoryProfiler是Facebook開源的一款用於分析iOS內存使用和檢測循環引用的工具庫。
在上一篇《在iOS上自動檢測內存洩露》中,Facebook講解了FBMemoryProfiler的核心原理。鑒於有人在評論裡問怎麼使用,我這裡就簡單介紹一下。
安裝
CocoaPods
建議使用Cocoapods安裝,只需要在pod文件中,添加這麼一句就行:
pod 'FBMemoryProfiler'
FBMemoryProfiler最低支持iOS 8,所以如果你的pod文件上最低要求是6或者7的話,是無法直接安裝的。所以,建議在開發分支或者如果你有多個target的話,在開發的target上添加FBMemoryProfiler,在生產的target上不需要添加FBMemoryProfiler。
我一般習慣於有兩個target,一個用於開發,裡面可能會包含Reveal、蒲公英等的庫,而這在生產包中是不必要的,另一個用於生產,只用於打生產包。
所以我的pod文件可能是這樣的:
# Uncomment this line to define a global platform for your project platform :ios, '8.0' # Uncomment this line if you're using Swift # use_frameworks! target 'FBMemoryProfilerTest' do end target 'FBMemoryProfilerTest_Dev' do pod 'FBMemoryProfiler' end
安裝成功之後,打開對於的.xcworkspace文件即可。
Carthage
如果你的app從iOS 8開始支持的話,你可以使用Carthage來安裝。
在創建的Cartfile文件中添加:
github "facebook/FBMemoryProfiler"
之後,運行carthage update --configuration Debug即可。
因為我的app要從iOS 6開始支持,所以我沒有使用這個。
嵌入代碼
首先,要在main.m中添加FBRetainCycleDetector的hook,同時,也要開啟FBAllocationTracker的生成追蹤:
#import#import "AppDelegate.h" #if DEBUG #import #import #endif int main(int argc, char * argv[]) { @autoreleasepool { #if DEBUG [FBAssociationManager hook]; [[FBAllocationTrackerManager sharedManager] startTrackingAllocations]; [[FBAllocationTrackerManager sharedManager] enableGenerations]; #endif return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } }
我習慣於添加一個DEBUG標識符,只在Debug狀態才開啟FBMemoryProfiler。當然,你可以不需要。
之後,我們要在AppDelegate.m的application: didFinishLaunchingWithOptions:中嵌入FBMemoryProfiler的創建代碼:
#if DEBUG #import#import #import "CacheCleanerPlugin.h" #import "RetainCycleLoggerPlugin.h" #endif @interface AppDelegate () { #if DEBUG FBMemoryProfiler *memoryProfiler; #endif } @end @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { #if DEBUG memoryProfiler = [[FBMemoryProfiler alloc] initWithPlugins:@[[CacheCleanerPlugin new], [RetainCycleLoggerPlugin new]] retainCycleDetectorConfiguration:nil]; [memoryProfiler enable]; #endif return YES; } @end
其中,插件是可以不開啟的,如果你不想開啟的話,你可以這麼寫:
FBMemoryProfiler *memoryProfiler = [FBMemoryProfiler new]; [memoryProfiler enable];
插件主要是用來進行過濾、去重或者輸出、存儲等操作的,畢竟如果不開啟插件的話,只能通過在手機、模擬器上點擊屏幕來看內存洩露,而如果自定義log插件的話,可以將捕獲到的內存洩露輸出到控制台或者文件中。
比如說,我們可以自定義一個RetainCycleLoggerPlugin,使用FBMemoryProfilerPluggable協議,重寫memoryProfilerDidFindRetainCycles:方法:
- (void)memoryProfilerDidFindRetainCycles:(NSSet *)retainCycles { if (retainCycles.count > 0) { NSLog(@"\nretainCycles = \n%@", retainCycles); } }
當FBRetainCycleDetector找到循環引用之後,就會調用到上面的方法,但是,retainCycles可能是個空集合,所以這裡可以過濾一下。
我在測試我的app的時候,發現這樣一個問題:
我確信這裡沒有因為我而導致的循環引用,但是FBRetainCycleDetector在這裡檢測到了這個環,這裡的主要問題在於_subviewCache,這是蘋果的機制,但是並不會造成內存洩露。對於這種情況,我們需要將它過濾出去。
除此之外,還有一個Timer的問題,因為一般情況下,Timer會強引用target,所以可能導致內存洩露,如果你確信沒有問題的話,可以關閉對Timer的檢測。
過濾代碼類似於這種:
NSArray *filters = @[FBFilterBlockWithObjectIvarRelation([UIView class], @"_subviewCache")]; FBObjectGraphConfiguration *configuration = [[FBObjectGraphConfiguration alloc] initWithFilterBlocks:filters shouldInspectTimers:YES]; memoryProfiler = [[FBMemoryProfiler alloc] initWithPlugins:@[[CacheCleanerPlugin new], [RetainCycleLoggerPlugin new]] retainCycleDetectorConfiguration:configuration]; [memoryProfiler enable];
我們只需要設置一個過濾數組,然後添加到FBMemoryProfiler的Configuration中即可。
對於你確信沒有問題或者不想修改的問題,你可以在Configuration中直接過濾掉。
比如:
NSArray *filters = @[FBFilterBlockWithObjectIvarRelation([UIPanGestureRecognizer class], @"_internalActiveTouches")];
如果你有CI的需要,你可以在代理中輸出log到文本,之後傳出到服務器上。
運行
具體操作可以參考Facebook的視頻,上一篇譯文中也有給出。
代碼
上面說到的代碼放在了這個demo中:https://github.com/Forkong/FBMemoryProfilerDemo
git clone 之後需要 pod install 一下,才可以運行。