經過上一篇文章對block本質的分析,咱們能夠了解到,block的本質就是一個OC對象,擁有一個isa
指針,那麼block就確定有本身的類型。上一篇文章中經過C++代碼咱們也看到了,isa
指向一個&_NSConcreteStackBlock
,本文就繼續分析block的類型,繼承鏈等知識點。bash
block
有三種類型,能夠經過isa
或者調用class
方法獲取,最終都繼承自NSObject
. 下面咱們經過class獲取到block的類型函數
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
void(^myBlock)(void) = ^{
NSLog(@"this is a block ");
};
NSLog(@"%@",[myBlock class]);
NSLog(@"%@",[[myBlock class] superclass]);
NSLog(@"%@",[[[myBlock class] superclass] superclass]);
NSLog(@"%@",[[[[myBlock class] superclass] superclass] superclass]);
}
return 0;
}
複製代碼
結果以下,也更加印證了block是一個OC對象
。post
block[12750:370967] __NSGlobalBlock__
block[12750:370967] __NSGlobalBlock
block[12750:370967] NSBlock
block[12750:370967] NSObject
複製代碼
上面咱們分析出了block
都繼承自NSBlock
,最終繼承自NSObject
,下面繼續看看block
的三種類型學習
void (^block1)(void) = ^{
NSLog(@"this is a block");
};
int age = 10;
void (^block2)(void) = ^{
NSLog(@"this is a block %d",age);
};
NSLog(@"%@ --- %@ --- %@",[block1 class],[block2 class],[^{
NSLog(@"this is a block %d",age);
} class]);
複製代碼
輸出: __NSGlobalBlock__ --- __NSMallocBlock__ --- __NSStackBlock__
可是咱們仍是須要編輯成C++代碼再看一下isa指向的類型,就不貼代碼了,直接說結果,都是_NSConcreteStackBlock;
測試
這裏要說一下一切以運行時
的結果類型爲準,由於clang這個命令編譯的結果可能跟咱們最初寫的代碼有些出入,只能做爲學習的參考。ui
一圖勝前言 this
編寫的代碼都放在程序區域spa
全局變量通常放在數據區域3d
程序區域和數據區域 都是編譯器自動處理的區域指針
堆:動態分配內存 好比[NSObject alloc] malloc(20) 須要開發者申請內存頁須要本身管理內存
棧:局部變量 參數 系統自動分配內存,自動釋放內存
NSGlobalBlock就放在數據區域,NSMallocBlock放在堆區 ,NSStackBlock放在棧區
那麼咱們開發中寫的不少block,咱們怎麼知道他是什麼類型的block呢?
下面總結幾點判斷的規則:
爲何上面要強調一下copy操做呢,由於在棧上的block調用copy以後會從棧賦值到堆上,在堆上的block再copy,則會引用計數加1,而在數據區的NSGlobalBlock調用block則不會有任何操做。
上面內存分部中說過NSStackBlock作了copy操做就是NSMallocBlock
,在ARC下,系統會根據狀況自動把棧上的block copy到堆上,那是什麼狀況系統會作這件事呢?下面舉例說一說
typedef void (^MyBlock)(void);
MyBlock returnBlock() {
int age = 20;
//自動調用copy
return ^{
NSLog(@"----- %d",age);
};
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
MyBlock block = returnBlock();
block();
NSLog(@"%@",[block class]);//NSMallocBlock
}
return 0;
}
複製代碼
根據咱們上面分析的結果,調用了auto變量,是一個NSStackBlock
,可是最後打印的結果是一個NSMallocBlock
,說明編譯器幫咱們作了一次copy
操做,固然在ARC下咱們也不須要關心release
,在做用域結束,編譯器也幫咱們作了release
操做。
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 20;
MyBlock block = ^{
NSLog(@"----- %d",age);
};
block();
NSLog(@"%@",[^{
NSLog(@"----- %d",age);
} class]);//NSStackBlock
NSLog(@"%@",[block class]); //NSMallocBlock
}
return 0;
}
複製代碼
NSArray *array = [NSArray array];
[array enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
}];
複製代碼
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
<#code to be executed once#>
});
複製代碼
總結一下:經過表面看本質,棧區
的block是不會對外面的變量執行retain
操做的(MRC),或者說不會對外面變量發生強引用
(ARC),只有堆區
的block纔有可能持有變量。
說到__block
這個修飾符,你們開發中必定是用過不少次了,我們先從爲何要用開始提及,再說__block
加了這個修飾詞以後到底發生了什麼。
爲何要用__block
呢?最多見的一個使用就是在block裏面
想修改block外部
的局部變量,前面咱們也已經分析過了,auto變量是經過值傳遞
,被記錄在block的結構體中的,並且是跨函數訪問,咱們如何能在block執行的函數中,修改另一個函數中的auto變量呢,固然是改不了的。而後咱們又知道了只要用__block
修飾這個想改的auto變量
,咱們就能夠在block中修改他的值了,那麼這裏就能夠想想__block
這個修飾詞究竟是作了什麼呢?也許他就是把以前的值傳遞
改爲了地址傳遞
呢?
想要修改auto變量,咱們先說一種不是使用__block
的方法
static修飾
前面咱們也分析過static修飾的變量被block捕獲是地址傳遞,只要是地址傳遞咱們能拿到地址,就能夠根據地址修改對應內存中的內容,能夠確定,static修飾,是能夠修改auto變量的全局變量
就更不用說了,誰均可以改,都不用經過block去捕獲__block
除了上面兩種方法(可能會有內存問題,咱們通常不用),__block纔是咱們最優的一種解決方法仍是經過clang編譯成C++代碼區分析__block
的原理,先寫一段測試代碼
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int age = 20;
MyBlock block = ^{
age = 33;
NSLog(@"----- %d",age);
};
block();
}
return 0;
}
複製代碼
__block int age = 20;
最終被編譯成了
__attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 20};
複製代碼
其實這裏能夠理解爲,添加了__block修飾以後的age,被編譯成了一個__Block_byref_age_0
對象
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
複製代碼
其中__forwarding被賦值了&age,也就是__Block_byref_age_0
對象自己的地址,age的值(20)也被存儲在了這個對象中。而後就是block中修改age的值,並打印age,使用age->__forwarding->age
取到age的值
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_age_0 *age = __cself->age; // bound by ref
(age->__forwarding->age) = 33;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_86_ljphgvys0hzg8hfxdtv5q5gm0000gn_T_main_76267f_mi_0,(age->__forwarding->age));
}
複製代碼
到這裏咱們應該已經清楚了,爲何__block修飾的變量在block中能夠被修改,其實說白了,仍是變相的實現了地址的傳遞,只有拿到地址才能夠改變相應地址指向的內存區域中的數據。
下面再說一個block中使用array
的小問題:
addObject
編譯不會報錯,可是給
array
賦值成
nil
就報錯,這是爲何呢?
先說賦值nil
報錯,根據上面的分析,由於咱們並無用__block
修飾array,block中不能修改auto對象,因此報錯了。
再說addObject
爲何不會報錯,由於addObject其實並非去改變array的值,只是去操做array
,而block只是不能修改auto變量,並非不能操做變量,因此不會報錯。