iOS 內存管理

內存管理

1、ARC 下的內存管理

一、循環引用

如圖,就是循環引用的狀況,A、B 互相引用沒法釋放。 git

循環引用內存圖

形成循環引用的狀況github

(1)、代理

代理都是用弱指針,以免循環引用,即避免上面的狀況。安全

(2)、block

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

  • 定義弱指針,block 內部定義強指針,網絡請求的 block key這樣處理。
__weak typeof(self) ws = self;
self.block = ^{
    typeof(ws) strongSelf = ws;
    NSLog(@"%@", strongSelf);
};
複製代碼
  • 控制器做爲 block 的參數
self.block = ^(UIViewController *vc) {
    NSLog(@"%@", vc);
};
複製代碼
(3)、NSTimer
[NSTimer timerWithTimeInterval:1 target:self selector:@selector(timerHandleMethod) userInfo:nil repeats:YES];
複製代碼

使用timer時,timer 強引用 target,把 timer 添加到 runloop 中後,runloop 持有timer,此時便造成了一個強引用鏈:Runloop -> NSTimer -> Target工具

(4)、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

2、檢測循環引用

(1)、Instruments - Leaks
Leak

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 控制器再退出,反覆多試幾回,能夠檢測到循環引用

Leak
(2)、調試內存圖

從 Xcode 8 開始,Xcode 的 debug 欄提供了查看內存圖的功能,能夠很方便的查看程序的內存狀況

memory graph button

點擊以後app 會停住,顯示當前活着的對象,上個demo 裏面的內存引用圖是這樣的

內存圖
(3)、精準的內存檢測工具 MLeaksFinder

MLeaksFinder 是一個在開發階段能夠及時檢測內存泄露的開源庫,GitHub 地址:MLeaksFinder

使用 `pod 'MLeaksFinder' 命令安裝後不用其餘任何操做,出現循環引用後,會自動彈出提示框,默認只在 debug 下生效,也能夠經過 MLeaksFinder.h 裏的 //#define MEMORY_LEAKS_FINDER_ENABLED 0 來手動控制開關。

MLLeakFinder cycle

![123](user-gold-cdn.xitu.io/2019/3/29/1… =300x200)

3、CoreFoundation 下的內存管理

__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; 則程序不會崩潰。

相關文章
相關標籤/搜索