struct Block_descriptor { unsigned long int reserved; unsigned long int size; void (*copy)(void *dst, void *src); void (*dispose)(void *); }; struct Block_layout { void *isa; int flags; int reserved; void (*invoke)(void *, ...); struct Block_descriptor *descriptor; /* Imported variables. */ };
從上面代碼看出,Block_layout就是對block結構體的定義:html
isa指針:指向代表該block類型的類。ios
flags:按bit位表示一些block的附加信息,好比判斷block類型、判斷block引用計數、判斷block是否須要執行輔助函數等。閉包
reserved:保留變量,個人理解是表示block內部的變量數。ide
invoke:函數指針,指向具體的block實現的函數調用地址。函數
descriptor:block的附加描述信息,好比保留變量數、block的大小、進行copy或dispose的輔助函數指針。學習
variables:由於block有閉包性,因此能夠訪問block外部的局部變量。這些variables就是複製到結構體中的外部局部變量或變量的地址。動畫
舉例,定義一個最簡單block 打印hello world:atom
int main(int argc, const char * argv[]) { void (^block)()=^{printf("hello world");}; block(); return 0; }
使用clang指令spa
clang -rewrite-objc main.m
3d
獲得一個cpp文件,編譯後,你就會看到什麼是block了
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __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) { printf("hello world"); } static struct __main_block_desc_0 { size_t reserved; size_t Block_size; } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)}; int main(int argc, const char * argv[]) { void (*block)()=((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA)); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); return 0; } static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
你定義完block以後,實際上是建立了一個函數,在建立結構體的時候把函數的指針一塊兒傳給了block,因此以後能夠拿出來調用。
再看看值捕獲的問題
int main(int argc, const char * argv[]) { int a=10; //__block int a=10; //__block前綴 void (^block)()=^{printf("打印a=%d",a);}; block(); return 0; }
定義block的時候,變量a的值就傳遞到了block結構體中,僅僅是值傳遞,因此在block中修改a是不會影響到外面的a變量的。
而加了__block前綴,編譯後:
struct __Block_byref_a_0 { void *__isa; __Block_byref_a_0 *__forwarding; int __flags; int __size; int a; }; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __Block_byref_a_0 *a; // by ref __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { __Block_byref_a_0 *a = __cself->a; // bound by ref printf("打印a=%d",(a->__forwarding->a));} static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);} static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);} static struct __main_block_desc_0 { size_t reserved; size_t Block_size; void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*); void (*dispose)(struct __main_block_impl_0*); } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0}; int main(int argc, const char * argv[]) { __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10}; void (*block)()=((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344)); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); return 0; } static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
並非直接傳遞a的值了,而是把a的地址(&a)傳過去了,因此在block內部即可以修改到外面的變量了。
isa:isa指針,在Objective-C中,任何對象都有isa指針。block 有三種類型:
_NSConcreteGlobalBlock 全局靜態,不會訪問任何外部變量,不會涉及到任何拷貝,好比一個空的block。例如:
#include int main() { ^{ printf("Hello, World!\n"); } (); return 0; }
_NSConcreteStackBlock 保存在棧中,出函數做用域就銷燬,例如:
#include int main() { char a = 'A'; ^{ printf("%c\n",a); } (); return 0; }
_NSConcreteMallocBlock 保存在堆中,retainCount == 0銷燬
該類型的block都是由_NSConcreteStackBlock類型的block從棧中複製到堆中造成的。例以下面代碼中,在exampleB_addBlockToArray方法中的block仍是_NSConcreteStackBlock類型的,在exampleB方法中就被複制到了堆中,成爲_NSConcreteMallocBlock類型的block:
void exampleB_addBlockToArray(NSMutableArray *array) { char b = 'B'; [array addObject:^{ printf("%c\n", b); }]; } void exampleB() { NSMutableArray *array = [NSMutableArray array]; exampleB_addBlockToArray(array); void (^block)() = [array objectAtIndex:0]; block(); }
總結一下:
_NSConcreteGlobalBlock類型的block要麼是空block,要麼是不訪問任何外部變量的block。它既不在棧中,也不在堆中,我理解爲它可能在內存的全局區。
_NSConcreteStackBlock類型的block有閉包行爲,也就是有訪問外部變量,而且該block只且只有有一次執行,由於棧中的空間是可重複使用的,因此當棧中的block執行一次以後就被清除出棧了,因此沒法屢次使用。
_NSConcreteMallocBlock類型的block有閉包行爲,而且該block須要被屢次執行。當須要屢次執行時,就會把該block從棧中複製到堆中,供以屢次執行。
而ARC和MRC中,還略有不一樣
題目:下面代碼在按鈕點擊後,在ARC下會發生什麼,MRC下呢?爲何?
@property(nonatomic, assign) void(^block)(); - (void)viewDidLoad { [superviewDidLoad]; int value = 10; void(^blockC)() = ^{ NSLog(@"just a block === %d", value); }; NSLog(@"%@", blockC); _block = blockC; } - (IBAction)action:(id)sender { NSLog(@"%@", _block); }
在ARC 打印:
mytest[25284:7473527] test:<__NSMallocBlock__: 0x60000005f3e0> mytest[25284:7473527] NSShadow {0, -1} color = {(null)}
雖然不會crash,第二個是野指針
MRC 會打印:test:<__NSStackBlock__: 0x7fff54941a38> 而後crash
例如:
NSArray *testArr = @[@"1", @"2"]; NSLog(@"block is %@", ^{ NSLog(@"test Arr :%@", testArr); });//結果:block is <__NSStackBlock__: 0x7fff54f3c808> void (^TestBlock)(void) = ^{ NSLog(@"testArr :%@", testArr); }; NSLog(@"block2 is %@", TestBlock);//block2 is <__NSMallocBlock__: 0x600000045e80>
//其實上面這句在非arc中打印是 NSStackBlock, 可是在arc中就是NSMallocBlock
//即在arc中默認會將block從棧複製到堆上,而在非arc中,則須要手動copy.
循環引用
Block的循環引用是比較容易被忽視,本來也是相對比較難檢查出來的問題。固然如今蘋果在XCode編譯的層級就已經作了循環引用的檢查,因此這個問題的檢查就忽然變的沒有難度了。
簡單說一下循環引用出現的原理:Block的擁有者在Block做用域內部又引用了本身,所以致使了Block的擁有者永遠沒法釋放內存,就出現了循環引用的內存泄漏。下面舉個例子說明一下:
@interface ObjTest () { NSInteger testValue; } @property (copy, nonatomic) void (^block)(); @end @implement ObjTest - (void)function { self.block = ^() { self.testValue = 100; }; } @end
在這個例子中,ObjTest擁有了一個名字叫block的Block對象;而後在這個Block中,又對ObjTest的一個成員變量testValue進行了賦值。因而就產生了循環引用:ObjTest->block->ObjTest。
要避免循環引用的關鍵就在於破壞這個閉合的環。在目前只考慮ARC環境的狀況下,筆者所知的只有一種方法能夠破壞這個環:在Block內部對擁有者使用弱引用。
@interface ObjTest () { NSInteger testValue; } @property (copy, nonatomic) void (^block)(); @end @implement ObjTest - (void)function { __weak ObjTest* weakSelf = self; self.block = ^() { weakSelf.testValue = 100; }; } @end
在單例模式下 Block避免循環引用,以下:
@interface Singleton : NSObject @property (nonatomic, copy) void(^block)(); + (instancetype)share; @end @implementation Singleton + (instancetype)share { static Singleton *singleton; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ singleton = [[Singleton alloc] init]; }); return singleton; } @end //============分割線================= //控制器中代碼的實現 @implementation NextViewController - (void)viewDidLoad { [super viewDidLoad]; __weak typeof(self) weakSelf=self; void (^blockTest)()=^(){ // NSLog(@"print %@", self);//會內存泄漏 NSLog(@"print %@", weakSelf); }; Singleton *singleton = [Singleton share]; singleton.block = blockTest; } - (IBAction)btnClick:(UIButton *)sender { [Singleton share].block(); } - (void)dealloc { NSLog(@"%s", __FUNCTION__); } @end
爲何iOS中系統的block方法可使用self
由於:首先循環引用發生的條件就是持有這個block的對象,被block裏邊加入的對象持有。固然是強引用。
因此UIView的動畫block不會形成循環引用的緣由就是,這是個類方法,當前控制器不可能強引用一個類,因此循環沒法造成。
ARC狀況下:
一、若是用copy修飾Block,該Block就會存儲在堆空間。則會對Block的內部對象進行強引用,致使循環引用。內存沒法釋放。
解決方法:新建一個指針(__weak typeof(Target) weakTarget = Target )指向Block代碼塊裏的對象,而後用weakTarget進行操做。就能夠解決循環引用問題。
二、若是用weak修飾Block,該Block就會存放在棧空間。不會出現循環引用問題。MRC狀況下用copy修飾後,若是要在Block內部使用對象,則須要進行(__block typeof(Target) blockTarget = Target )處理。在Block裏面用blockTarget進行操做。
返回值類型(^block變量名)(形參列表) = ^(形參列表) {};調用Block保存的代碼block變量名(實參);默認狀況下,,Block內部不能修改外面的局部變量Block內部能夠修改使用__block修飾的局部變量
參考 收藏:https://www.zhihu.com/question/30779258/answer/49492783