iOS Block - 深入學習篇

前面寫了一篇Block開發中的簡單使用,這篇文章將深入的學習一下Block和開發中的一些使用。

目錄

  • Block的實質
  • Block的儲存域
  • Block的捕獲變量和循環引用的問題
一、 Block的實質

block其實也是一個對象,在存放block對象的內存區域中,也包含我們經常說的isa指針,和一些能讓block正常運轉的各種信息。關于isa指針,在這里簡單的說一下,在OC中每個實例對象都會有個isa指針,它指向對象的類,其實在類里面也會有isa指針,這個指針是指向該類的元類。所以說類的實質也是對象,在OC中一切皆對象。然后來看一下block的內存布局。


Block對象的內存布局

invoke變量:這個是函數指針,指向block的實現代碼,也是最重要的變量了。
其他的都是一些維持block正常運行的信息了,我們還注意到有一塊內存是存放捕獲到的變量,捕獲變量這塊下面還會有講解。

二、Block的儲存域

說到內存的儲存先簡單說一下棧和堆。
棧:由編譯器自動分配釋放,存放函數的參數值,局部變量的值等。
堆:由程序員分配釋放,如果程序員不釋放,程序結束的時候系統會收回。
我們定義block的時候,其所占的內存區域是分配在棧上的,如果不注意這點話,很可能會寫出有問題的代碼。

void (^block)();
if (isYes) {
  block = ^{
    NSLog(@"blockA");
  };
}else
{
  block = ^{
    NSLog(@"blockB");
  };
}
block();

這段代碼會有什么問題呢,因為這block的內存是分配在棧上的,棧上的內存是系統來管理的,如果編輯器沒有覆寫待執行的block,程序正常,若覆寫了,程序就會崩潰。
那如何解決這個問題呢?那就是用block對象發送copy消息,讓block從棧復制到堆上,copy后該block就成了帶引用計數的對象了。

void (^block)();
if (isYes) {
  block = [^{
    NSLog(@"blockA");
  } copy ];
}else
{
  block = [^{
    NSLog(@"blockB");
  } copy];
}
block();

這樣這段代碼就安全了。如果手動管理內存,用完之后可以手動將其釋放。
block除了儲存在棧和堆上,還有一種是全局的block,全局的block不會捕獲變量(捕獲變量會在下面說明),儲存在全局的內存里面。

Block儲存域

理解block三種的儲存區域,對后面的變量捕獲的理解會很有用處。

三、Block的捕獲變量和循環引用的問題
1.捕獲變量

在block的內存布局那張圖中我們看到,有一塊內存是用來儲存捕獲變量的,下面具體說一下block的捕獲變量問題。

    int add = 5;
    
    int (^addBlock) (int a) = ^(int a){
      
        return a + add;
    };
    
    int addValue = addBlock(2);// addValue = 7;

這段代碼我們就能看出,在聲明block的范圍內,所有變量都可以為其捕獲,也就是說,在那個范圍里面的所有變量,在塊里面都可以使用。但是如果想在block里面修改變量值得話,就必須使用 __block來修飾了。只有使用該修飾符才能在block里面修改變量。

__block int add = 5;

那這些捕獲的變量什么時候才能被釋放呢?

棧里面的block:
如果該block儲存在棧里面,那么該block只會在聲明的作用范圍內有效,作用域結束的時候,棧上的__block變量和block也會被廢棄。也就是說block和捕獲的變量被系統一塊釋放了。在棧里面的__block變量只是被block使用而已,而沒有被block所持有。

堆里面的block:
當棧里面的block被Copy到堆里面的時候,__block變量也會被copy到堆里面并且會被block所持有,只有不被block持有的時候才會被釋放。

全局里面block:只有不被block持有的時候才會被釋放。

2.捕獲對象和循環引用的問題

先看下面的代碼

NSMutableArray *array = [NSMutableArray array];
    
void (^block)(id object) = [^(id object){
      
    [array addObject:object];
        
    NSLog(@"array count = %lu",(unsigned long)array.count);
    
} copy];
    
block([[NSObject alloc] init]);
block([[NSObject alloc] init]);
block([[NSObject alloc] init]);

打印結果

array count = 1
array count = 2
array count = 3

block會被copy到堆內存里,block持有array對象。
捕獲對象引起的循環引用問題

#import "MyObject.h"

typedef void(^block)(void);

@interface MyObject()
{
    block _block;
}

@end
@implementation MyObject

- (instancetype)init
{
    self = [super init];
    if (self) {
        _block = ^{
          NSLog(@"self = %@",self);
        };
    }
    return self;
}

- (void)dealloc
{
    NSLog(@"dealloc");
}
MyObject *myObject = [[MyObject alloc]init];
NSLog(@"%@",myObject);

我們知道當對象銷毀的時候,系統會調用dealloc方法,在外邊使用實例化MyObject對象后,是不會調用dealloc方法銷毀MyObject實例對象的,因為MyObject對象持有block,block又持有MyObject對象,這就是block引起的循環引用。


Block循環引用問題

如果把block改成這樣就會解決這個問題。

__weak typeof(self) (wself) = self;
_block = ^{
    NSLog(@"self = %@",wself);
};
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容