__main_block_impl_0
分析aotu修飾的局部變量和static修飾的局部變量之間的差異html
int main(int argc, const char * argv[]) { @autoreleasepool { auto int a = 10; static int b = 10; void(^block)(void) = ^{ NSLog(@"age is %d, height is %d", a, b); }; a = 1; b = 2; block(); } return 0; } // log : 信息--> age = 10, height = 2 // block中a的值沒有被改變而b的值隨外部變化而變化。
從新生成c++代碼看一下內部結構中兩個參數的區別。ios
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; int a; // a 爲值 int *b; // b 爲指針 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int *_b, int flags=0) : a(_a), b(_b) { impl.isa = &_NSConcremainackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int a = __cself->a; // bound by copy int *b = __cself->b; // bound by copy NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_fd2a14_mi_0, a, (*b)); } 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 age_ = 10; static int height_ = 10; int main(int argc, const char * argv[]) { @autoreleasepool { void(^block)(void) = ^{ NSLog(@"age is %d, height is %d", age_, height_); }; age_ = 1; height_ = 2; block(); } return 0; } // log 信息--> age = 1, height = 2
一樣生成c++代碼查看全局變量調用方式c++
int age_ = 10; static int height_ = 10; 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 = &_NSConcremainackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_fd2a14_mi_0, age_, height_); } 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)};
如下Persion類代碼中block變量分析數組
@interface Person : NSObject @property (copy, nonatomic) NSString *name; - (void)test; - (instancetype)initWithName:(NSString *)name; @end #import "Person.h" @implementation Person int age_ = 10; - (void)test { void (^block)(void) = ^{ NSLog(@"-------%d", [self name]); }; block(); } - (instancetype)initWithName:(NSString *)name { if (self = [super init]) { self.name = name; } return self; } @end
一樣轉化爲c++代碼查看其內部結構函數
int age_ = 10; struct __Person__test_block_impl_0 { struct __block_impl impl; struct __Person__test_block_desc_0* Desc; Person *self; __Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, Person *_self, int flags=0) : self(_self) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __Person__test_block_func_0(struct __Person__test_block_impl_0 *__cself) { Person *self = __cself->self; // bound by copy NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_Person_1027e6_mi_0, ((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("name"))); } static void __Person__test_block_copy_0(struct __Person__test_block_impl_0*dst, struct __Person__test_block_impl_0*src) {_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);} static void __Person__test_block_dispose_0(struct __Person__test_block_impl_0*src) {_Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);} static struct __Person__test_block_desc_0 { size_t reserved; size_t Block_size; void (*copy)(struct __Person__test_block_impl_0*, struct __Person__test_block_impl_0*); void (*dispose)(struct __Person__test_block_impl_0*); } __Person__test_block_desc_0_DATA = { 0, sizeof(struct __Person__test_block_impl_0), __Person__test_block_copy_0, __Person__test_block_dispose_0}; static void _I_Person_test(Person * self, SEL _cmd) { void (*block)(void) = ((void (*)())&__Person__test_block_impl_0((void *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, self, 570425344)); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); } static instancetype _I_Person_initWithName_(Person * self, SEL _cmd, NSString *name) { if (self = ((Person *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Person"))}, sel_registerName("init"))) { ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)self, sel_registerName("setName:"), (NSString *)name); } return self; } static NSString * _I_Person_name(Person * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_Person$_name)); } extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool); static void _I_Person_setName_(Person * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Person, _name), (id)name, 0, 1); } // @end struct _prop_t { const char *name; const char *attributes; };
不論對象方法仍是類方法都會默認將self做爲參數傳遞給方法內部,既然是做爲參數傳入,那麼self確定是局部變量。上面講到局部變量確定會被block捕獲。源碼分析
在block內部使用name成員變量或者調用實例的屬性編碼
- (void)test { void(^block)(void) = ^{ NSLog(@"%@",self.name); NSLog(@"%@",_name); }; block(); }
獲得結論:在block中使用的是實例對象的屬性,block中捕獲的仍然是實例對象,並經過實例對象經過不一樣的方式去獲取使用到的屬性。atom
經過源碼分析獲得,block中的isa指針指向的是_NSConcreteStackBlock類對象地址。那麼block是否就是_NSConcreteStackBlock類型的呢?spa
咱們經過代碼用class方法或者isa指針查看具體類型。指針
int main(int argc, const char * argv[]) { @autoreleasepool { // __NSGlobalBlock__ : __NSGlobalBlock : NSBlock : NSObject void (^block)(void) = ^{ NSLog(@"Hello"); }; NSLog(@"%@", [block class]); NSLog(@"%@", [[block class] superclass]); NSLog(@"%@", [[[block class] superclass] superclass]); NSLog(@"%@", [[[[block class] superclass] superclass] superclass]); } return 0; } // log 打印結果 __NSGlobalBlock__ // log 打印結果 __NSGlobalBlock // log 打印結果 NSBlock // log 打印結果 NSObjcet
從上述打印內容能夠看出block最終都是繼承自NSBlock類型,而NSBlock繼承於NSObjcet。那麼block其中的isa指針實際上是來自NSObject中的。這也更加印證了block的本質其實就是OC對象。
經過代碼查看一下block在什麼狀況下其類型會各不相同
int main(int argc, const char * argv[]) { @autoreleasepool { // 1. 內部沒有調用外部變量的block void (^block1)(void) = ^{ }; // 2. 內部調用外部變量的block int a = 10; void (^block2)(void) = ^{ NSLog(@"log :%d",a); }; // 3. 直接調用的block的class NSLog(@"%@ %@ %@", [block1 class], [block2 class], [^{ NSLog(@"%d",a); } class]); } return 0; } // 最後一行 Log :打印結果 __NSGlobalBlock__, __NSStackBlock__ ,__NSMallocBlock__
咱們能夠推測runtime運行時過程當中也許對類型進行了轉變。最終類型固然以runtime運行時類型也就是咱們打印出的類型爲準。
經過下面一張圖看一下不一樣block的存放區域
block是如何定義其類型
接着咱們使用代碼驗證上述問題,首先關閉ARC回到MRC環境下,由於ARC會幫助咱們作不少事情,可能會影響咱們的觀察。
// MRC環境!!! int main(int argc, const char * argv[]) { @autoreleasepool { // Global:沒有訪問auto變量:__NSGlobalBlock__ void (^block1)(void) = ^{ NSLog(@"block1---------"); }; // Stack:訪問了auto變量: __NSStackBlock__ int a = 10; void (^block2)(void) = ^{ NSLog(@"block2---------%d", a); }; NSLog(@"%@ %@", [block1 class], [block2 class]); // __NSStackBlock__調用copy : __NSMallocBlock__ NSLog(@"%@", [[block2 copy] class]); } return 0; } // Log 打印信息 --> __NSGlobalBlock__ ,__NSStackBlock__ ,__NSMallocBlock__
可是__NSStackBlock__訪問了aotu變量,而且是存放在棧中的,上面提到過,棧中的代碼在做用域結束以後內存就會被銷燬,那麼咱們頗有可能block內存銷燬以後纔去調用他,那樣就會發生問題,經過下面代碼能夠證明這個問題。MRC 環境下的。
void (^block)(void); void test() { // __NSStackBlock__ int a = 10; block = ^{ NSLog(@"block---------%d", a); }; } int main(int argc, const char * argv[]) { @autoreleasepool { test(); block(); } return 0; } // Log 打印信息 :MRC 環境下 : block---------272632424 // Log 打印信息 :ARC 環境下 : block---------10
void (^block)(void); void test() { // __NSStackBlock__ 調用copy 轉化爲__NSMallocBlock__ int age = 10; block = [^{ NSLog(@"block---------%d", age); } copy]; [block release]; } int main(int argc, const char * argv[]) { @autoreleasepool { // insert code here... test(); block(); // Log 打印信息 : block---------10 } return 0; }
能夠發現a的值變爲了避免可控的一個數字。爲何會發生這種狀況呢?由於上述代碼中建立的block是__NSStackBlock__類型的,所以block是存儲在棧中的,那麼當test函數執行完畢以後,棧內存中block所佔用的內存已經被系統回收,所以就有可能出現亂得數據。查看其c++代碼能夠更清楚的理解。
爲了不這種狀況發生,能夠經過copy將__NSStackBlock__類型的block轉化爲__NSMallocBlock__類型的block,將block存儲在堆中,如下是修改後的代碼。
void (^block)(void); void test() { // __NSStackBlock__ 調用copy 轉化爲__NSMallocBlock__ int age = 10; block = [^{ NSLog(@"block---------%d", age); } copy]; [block release]; } // Log 打印信息 : block---------10
那麼其餘類型的block調用copy會改變block類型嗎?下面表格已經展現的很清晰了。
因此在平時開發過程當中MRC環境下常常須要使用copy來保存block,將棧上的block拷貝到堆中,即便棧上的block被銷燬,堆上的block也不會被銷燬,須要咱們本身調用release操做來銷燬。而在ARC環境下回系統會自動copy,是block不會被銷燬。
在ARC環境下,編譯器會根據狀況自動將棧上的block進行一次copy操做,將block複製到堆上。
會自動將block進行一次copy操做的狀況。
block做爲函數返回值時
typedef void (^Block)(void); Block myblock() { int a = 10; // 上文提到過,block中訪問了auto變量,此時block類型應爲__NSStackBlock__ Block block = ^{ NSLog(@"---------%d", a); }; return block; } int main(int argc, const char * argv[]) { @autoreleasepool { Block block = myblock(); block(); // 打印block類型爲 __NSMallocBlock__ NSLog(@"%@",[block class]); } return 0; } Log 打印信息 :---------10 Log 打印信息 :__NSMallocBlock__
int main(int argc, const char * argv[]) { @autoreleasepool { // block內沒有訪問auto變量 Block block = ^{ NSLog(@"block---------"); }; NSLog(@"%@",[block class]); int a = 10; // block內訪問了auto變量,但沒有賦值給__strong指針 NSLog(@"%@",[^{ NSLog(@"block1---------%d", a); } class]); // block賦值給__strong指針 Block block2 = ^{ NSLog(@"block2---------%d", a); }; NSLog(@"%@",[block1 class]); } return 0; } Log 打印信息 :__NSGlobalBlock__ Log 打印信息 :__NSStackBlock__ Log 打印信息 :__NSMallocBlock__
NSArray *array = @[]; [array enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { }];
static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ }); dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ });
MRC下block屬性的建議寫法
@property (copy, nonatomic) void (^block)(void);
ARC下block屬性的建議寫法
@property (strong, nonatomic) void (^block)(void);
@property (copy, nonatomic) void (^block)(void);