如圖,就是循環引用的狀況,A、B 互相引用沒法釋放。 git
形成循環引用的狀況github
代理都是用弱指針,以免循環引用,即避免上面的狀況。安全
block 能夠捕獲外界的變量,控制器中的 block 引用 self ,block 的結構體將有一個強指針指向 self(控制器)(以下面代碼),若是 block 又是控制器的一個屬性,則會出現循環引用。若是 block 只是個局部變量,block 會很快釋放則不會產生循環引用。bash
struct __TestBlockStructViewController__testBlockCaptureStackVariable_block_impl_0 {
struct __block_impl impl;
struct __TestBlockStructViewController__testBlockCaptureStackVariable_block_desc_0* Desc;
TestBlockStructViewController *const __strong self; // 強指針
__TestBlockStructViewController__testBlockCaptureStackVariable_block_impl_0
(void *fp,
struct __TestBlockStructViewController__testBlockCaptureStackVariable_block_desc_0 *desc,
TestBlockStructViewController *const __strong _self,
int flags=0) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
複製代碼
有一個常見的循環引用場景是,Controller 發送網絡請求,網絡回調 block 強引用 當前 Controller。在網速比較慢的狀況下,網絡請求可能持續幾十秒(建議網絡超時不超過 10s),在這段時間內將有循環引用。 例如:網絡
[NetReq getBookInfoWithShopId:@"3772" bookId:@"526377307" success:^(NSDictionary *data) {
self.arrayModel = ....
} fail:^(NSError *error) {
}];
複製代碼
解決block 的循環引用app
__weak typeof(self) ws = self;
self.block = ^{
typeof(ws) strongSelf = ws;
NSLog(@"%@", strongSelf);
};
複製代碼
self.block = ^(UIViewController *vc) {
NSLog(@"%@", vc);
};
複製代碼
NSTimer
[NSTimer timerWithTimeInterval:1 target:self selector:@selector(timerHandleMethod) userInfo:nil repeats:YES];
複製代碼
使用timer時,timer 強引用 target,把 timer 添加到 runloop 中後,runloop 持有timer,此時便造成了一個強引用鏈:Runloop
-> NSTimer
-> Target
。工具
WKWebView
WKWebView
h5 調用原生代碼用到了一個方法- (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name;
,這個方法會強引用 scriptMessageHandler
, 若是在控制器內部調用這個方法而scriptMessageHandler
又傳了self
,將會造成循環引用:oop
self -> WKWebView -> WKWebViewConfiguration ->
userContentController -> handler(self)
複製代碼
能夠在控制器中建立一個 model 做爲 scriptMessageHandler
的參數,處理h5與原生的交互。ui
在循環語句中有時會在循環中產生臨時變量,循環次數過多時便會產生大量的臨時變量。通常遇到這種狀況要在循環中加 @autoreleasepool
讓臨時變量在 runloop 結束時釋放。atom
Instruments - Leaks
Xcode提供的內存分析工具 Leaks 能夠檢測內存泄露。先在控制器A裏寫一段循環引用的代碼,由根控制器跳進 A,退出A 控制器後,A 確定釋放不了:
@property (copy, nonatomic) dispatch_block_t block;
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.block = ^{
NSLog(@"%@", self);
};
}
複製代碼
將代碼運行在真機上,在 Leaks 左上角選擇設備和檢測的app,點擊 左邊的紅色按鈕啓動便可,進入A 控制器再退出,反覆多試幾回,能夠檢測到循環引用
從 Xcode 8 開始,Xcode 的 debug 欄提供了查看內存圖的功能,能夠很方便的查看程序的內存狀況
點擊以後app 會停住,顯示當前活着的對象,上個demo 裏面的內存引用圖是這樣的
MLeaksFinder
MLeaksFinder
是一個在開發階段能夠及時檢測內存泄露的開源庫,GitHub 地址:MLeaksFinder。
使用 `pod 'MLeaksFinder' 命令安裝後不用其餘任何操做,出現循環引用後,會自動彈出提示框,默認只在 debug 下生效,也能夠經過 MLeaksFinder.h 裏的 //#define MEMORY_LEAKS_FINDER_ENABLED 0 來手動控制開關。
![123](user-gold-cdn.xitu.io/2019/3/29/1… =300x200)
__bridge
只進行轉換,原轉換對象引用計數不變,原對象須要調用 CFRelease
釋放內存。使用 __bridge
轉換安全性會很低,若是不注意對象的全部者,會引起野指針崩潰。下面來看對應的等效代碼爲:
// ARC
id obj = [NSObject new];
void *p = (__bridge void *)obj;
複製代碼
對應的 MRC 等效代碼爲:
/* MRC */
id obj = [NSObject new];
void *p = (__bridge void *)obj;
複製代碼
轉換後 p 沒有 retain 該對象,obj 釋放後 p 是個野指針,使用 p 會出現崩潰,ep:
- (void)testBridge {
/* MRC */
id obj = [NSObject new];
void *p = (__bridge void *)obj;
[obj release];
obj = nil;
NSLog(@"%@", p); // EXC_BAD_ACCESS
}
複製代碼
使用 __bridge
轉換,p 沒有持有對象,[obj release];
後對象就釋放了,上面的程序會出現 EXC_BAD_ACCESS
崩潰。
__bridge_retained
轉換後的指針也持有所賦值的對象。看等效代碼:
// ARC
id obj= [NSObject new];
void *p = (__bridge_retained void*)obj;
複製代碼
上面是 ARC 下的 __bridge_retained
轉換,對應的 MRC 等效代碼爲:
// MRC
id obj = [NSObject new];
void *p = obj;
[(id)p retain];
複製代碼
轉換後的 p 持有對象,obj 釋放後,p 不會像 __bridge
出現野指針的狀況。
__bridge_transfer
引用計數交給 ARC 管理,轉換對象持有的內存在轉換完成後隨之釋放。
id obj = (__bridge_transfer id)p;
複製代碼
上面代碼對應於 MRC 下的等效代碼:
id obj = (id)p;
[obj retain];
[(id)p release];
複製代碼
驗證 __bridge_transfer
執行後,原內存會釋放。
/* ARC */
id o = [NSObject new];
void *p = (__bridge void *)o;
id obj = (__bridge_transfer id)p;
NSLog(@"%@", [obj class]);
id obj2 = (__bridge id)p;
NSLog(@"obj2:%@", [obj2 class]); // EXC_BAD_ACCESS
複製代碼
執行結果爲:
2019-01-27 12:18:53.820708+0800 testThread[9588:293724] NSObject
2019-01-27 12:18:53.820897+0800 testThread[9588:293724] obj2:NSObject
複製代碼
第一次轉換爲 obj 後 p 的內存就釋放了,隨後轉換爲 obj 2 遇到了 EXC_BAD_ACCESS 錯誤,結論得以驗證。若將 id obj = (__bridge_transfer id)p;
修改爲 id obj = (__bridge id)p;
則程序不會崩潰。