一. 查看block內部實現
1.編寫block代碼
void (^DemoBlock)(int, int) = ^(int a, int b){ NSLog(@"%d",a+b); }; DemoBlock(1,3); 輸出:2019-01-14 13:01:47.104 iOSWorld[1324:103701] 4
- 使用
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc BlockViewController.m
命令查看c++源碼,搜索DemoBlock.void (*DemoBlock)(int, int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA)); ((void (*)(__block_impl *, int, int))((__block_impl *)DemoBlock)->FuncPtr)((__block_impl *)DemoBlock, 1, 3);
簡化:
((void (*)(int, int))
是一個函數指針,也就是把后面的代碼強轉為一個函數指針.刪減后得到(從下往上看)struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr; }; static struct __main_block_desc_0 { size_t reserved;//為0 size_t Block_size;//block的大小 } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)}; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; //__main_block_impl_0和結構體同名, 是一個構造函數, 返回結構體對象 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b){ NSLog(a+b); } //定義DemoBlock,&__main_block_impl_0是一個函數,有兩個參數,第一個為包裝的方法,第二個為描述信息 void (*DemoBlock)(int, int) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA)); //執行DemoBlock DemoBlock->FuncPtr(DemoBlock, 1, 3);
代碼解釋:
1.調用__main_block_impl_0()函數
- 參數1為__main_block_func_0,也就是我們block里面要做的事情,也就是匿名函數 --> NSLog(@"%d",a+b);
- 參數2為__main_block_desc_0_DATA,__main_block_desc_0_DATA是一個struct __main_block_desc_0的結構體,結構體參數reserved=0,結構體參數Block_size=sizeof(struct __main_block_impl_0);
- 調用后會來到__main_block_impl_0這個結構體的構造函數,將內部impl結構體設置impl.isa = &_NSConcreteStackBlock; impl.Flags = flags;impl.FuncPtr = fp;將內部__main_block_desc_0結構體的Desc = desc;
所以: block是封裝了函數調用以及函數調用環境的對象
底層:block是一個包含了函數指針和變量的結構體
3.分析(注意!!!!!標注的代碼)
impl.isa = &_NSConcreteStackBlock;
block有一個isa指針,說明block也是一個OC對象.impl.FuncPtr = fp
記錄一下block里面的函數Desc = desc;
記錄該block的描述
4.拓展
- (void)test { void(^DemoBlock)(void) = ^{ }; NSLog(@"%@",[DemoBlock class]); NSLog(@"%@",[[DemoBlock class] superclass]); NSLog(@"%@",[[[DemoBlock class] superclass] superclass]); NSLog(@"%@",[[[[DemoBlock class] superclass] superclass] superclass]); } 輸出:2019-01-14 16:16:15.953 iOSWorld[1542:128655] __NSGlobalBlock__ 2019-01-14 16:16:15.953 iOSWorld[1542:128655] __NSGlobalBlock 2019-01-14 16:16:15.954 iOSWorld[1542:128655] NSBlock 2019-01-14 16:16:15.954 iOSWorld[1542:128655] NSObject
block有superclass,說明就是OC對象.
二.block變量捕獲
- 對基本數據類型的局部變量截獲其值.
- 對對象類型的局部變量連同所有權修飾符一起截獲,如果以strong修飾,捕獲的時候底層就retain一次,如果是weak修飾的,就不retain
self也是局部變量,所以會捕獲self
- 以指針形式截獲靜態局部變量
雖然static修飾的局部變量也是永駐內存,但因為它是局部的,不像全局變量一樣誰都可以訪問,所以捕獲它的指針才能找到它.
- 不截獲全局變量、靜態全局變量,編譯的時候就寫死其地址了。
2.1 block捕獲局部變量
demo1
- (void)test { int a = 10; void(^DemoBlock)(void) = ^{ NSLog(@"%d", a); }; a = 20; DemoBlock(); } 輸出:2019-01-14 15:00:38.339 iOSWorld[1446:119920] 10
解釋:定義完
a=10
后,編譯器開始編譯DemoBlock,這時候DemoBlock內部已經捕獲了a的值.
繼續執行a=20
,繼續執行DemoBlock()
會打印出已經捕獲的a=10
,所以打印為10.
總結: 對于局部變量,一出作用域就會被銷毀了,所以block會及時捕獲局部變量并copy一份值
到block內部.
2.2 block捕獲全局變量
demo2
int a = 10; - (void)test { void(^DemoBlock)(void) = ^{ NSLog(@"%d", a); }; a = 20; DemoBlock(); } 輸出:2019-01-14 15:26:38.208 iOSWorld[1478:122759] 20
解釋:
int a = 10
,接下來編譯DemoBlock,繼續執行a=20
,繼續執行DemoBlock()
的時候會調用NSLog(@"%d", a)
,因為a是全局變量,這時候只需要拿到a打印就行了.
總結: 對于全局變量,即使出了作用域也不會被銷毀,所以block不會捕獲全局變量,啥時候用啥時候取就行.下劃線_age其實是self->age,所以會先捕獲self
三.3種block的內存存放區域
block分類
_NSConcreteStackBlock:在棧上創建的Block對象
_NSConcreteMallocBlock:在堆上創建的Block對象
_NSConcreteGlobalBlock:全局數據區的Block對象(data區)
- 沒有捕獲局部變量的block為GlobalBlock
- 捕獲了局部變量但是沒有進行
block復制
則為StackBlock---棧block容易出錯,因為有可能被銷毀了
,StackBlock不會強引用對象- 其他的block基本都為MallocBlock(堆block)
block復制
block從棧復制到堆的時候,會調用_Block_object_assign()
強引用
block在堆上銷毀的時候,會調用_Block_object_dispose()
release引用的對象在ARC有效時,大多數情況下編譯器會進行判斷,自動生成將Block從棧上復制到堆上的代碼,以下幾種情況棧上的Block會自動復制到堆上:
- 調用Block的copy方法
globalBlock copy仍為globalBlock,stackBlock copy后為mallocBlock,mallocBlock copy后引用計數+1
- 將Block作為函數返回值時
- 將Block賦值給__strong修飾的變量時
- 向Cocoa框架含有usingBlock的方法 或者 GCD的API傳遞Block參數時
所以如果不怕被銷毀,比如該block當函數用一次,那么block不一定非得用copy.
__block可以解除循環引用
四.block修改變量
???為什么不可以在block內部修改局部變量
因為:局部變量出了大括號就會銷毀,有可能你修改的時候它已經是nil了.
但是:我們可以使用__block修飾局部變量.
__block int a = 10;
void (^DemoBlock)(void) = ^(){
a = 20;
NSLog(@"%d",a);
};
DemoBlock()
輸出:---------> 20
- 使用
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc main.m
命令查看c++源碼,搜索DemoBlock.
void (*DemoBlock)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
- 發現__main_block_impl_0實現里面多了一個__Block_byref_a_0開頭的變量
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref
}
- 看看__Block_byref_a_0,發現它里面有個a,還有個自己類型的__forwarding-->
__Block_byref_a_0 *__forwarding
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
4.看看__main_block_func_0這個參數
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
//創建了一個__Block_byref_a_0類型的和我們定義一樣的同名變量a
__Block_byref_a_0 *a = __cself->a;
//將新a的__forwarding里面的a設置為20
(a->__forwarding->a) = 20;
//打印新a的__forwarding里面的a
NSLog(a->__forwarding->a);
}
5.將自己(__Block_byref_a_0
)的地址值傳到自己的第二個參數__forwarding
里面,也就是說__forwarding
指向的還是自己
__Block_byref_a_0 *__forwarding = (__Block_byref_a_0 *)&a
__forwarding
存在的意義:不論在棧上還是堆上,都可以順利的訪問同一個__block變量.
總結:__block原理
編譯器會將__block變量包裝成一個對象,并把局部變量賦值給該對象,這樣就能修改對象的變量
你以為你修改的還是那個局部變量,殊不知已經是__block自己生成的對象啦
拓展:
__block __weak Person *p = [Person new]
上面代碼:生成一個自己的對象block_p
包裹了p
,
而且block
強引用了block_p
,但是因為有weak
的存在,block_p
對p
是弱引用的.
{
TestBlock block1;
{
__block BlockReferDemoObject *objc = [[BlockReferDemoObject alloc] init];
block1 = ^{
NSLog(@"%@", objc);
};
}//默認強引用
block1();
}
NSLog(@"-----------------");
{
TestBlock block2;
{
BlockReferDemoObject *objc = [[BlockReferDemoObject alloc] init];
__block __weak BlockReferDemoObject *weakObjc = objc;
block2 = ^{
NSLog(@"%@", weakObjc);
};
}//__weak修飾,沒有強引用objc,導致出了大括號就被銷毀了
block2();
}
五.block的內存管理
- stackBlock對所有的局部變量都是弱引用
-
mallocBlock :
1.對__block或者__Strong使用_Block_object_assign()進行強引用.
2.對__weak使用_Block_object_assign()進行弱引用.
當block移除時,使用_Block_object_dispose()來釋放局部變量.
六.解決循環引用的新方法
將在Block內要使用到的對象(一般為self對象),以Block參數的形式傳入,Block就不會捕獲該對象,而將其作為參數使用,其生命周期系統的棧自動管理,不造成內存泄露。
即原來使用__weak的寫法:
__weak typeof(self) weakSelf = self;
self.blk = ^{
__strong typeof(self) strongSelf = weakSelf;
NSLog(@"Use Property:%@", strongSelf.name);
//……
};
self.blk();
改為Block傳參寫法后:
self.blk = ^(UIViewController *vc) {
NSLog(@"Use Property:%@", vc.name);
};
self.blk(self);
__weak typeof(self) weakSelf = self;
__weak UIViewController *weakSelf = self;
使用__block
也能解除循環引用
__block BlockReferDemoObject *objc = [BlockReferDemoObject new];
{
objc.demoBlock = ^{
NSLog(@"%@", objc);
objc = nil;
};
objc.demoBlock();
}