block本质探寻五之atuto类型局部实例对象
说明:阅读本文章,请参考之前的block文章加以理解;
一、栈区block分析
//代码
//arc void test1() { { person *per = [[person alloc] init]; per.age = 10; ^{ nslog(@"age:%d", per.age); }; } nslog(@"-------1"); }
//打印
2019-01-14 17:24:12.118653+0800 mj_test[6638:285938] person dealloc 2019-01-14 17:24:12.118934+0800 mj_test[6638:285938] -------1 program ended with exit code: 0
分析:
<1>block代码内部引用的person实例对象先于输出语句销毁,因为per仅限于大括号内,但此时block销毁了没有?往下看;
<2>上述block代码块并没有被指针持有,接下来看看指针持有的情况;
//代码
typedef void(^myblock)(void); //arc void test2() { myblock block; { person *per = [[person alloc] init]; per.age = 10; block = ^{ nslog(@"age:%d", per.age); }; } nslog(@"-------1"); }
//打印
2019-01-14 17:34:58.473267+0800 mj_test[6824:293129] -------1 2019-01-14 17:34:58.473705+0800 mj_test[6824:293129] person dealloc program ended with exit code: 0
分析:person实例对象后于输出语句销毁,为什么有指针持有,顺序就变了?
<1>等号左边:是一个auto类型的局部的block指针变量,存放在栈区;等号右边:是一个block代码块(对象),也是一个局部对象,存放在栈区;
<2>在arc模式下,如果有指针持有(默认是强指针,修饰符为__strong)一个局部的block对象,系统会自动copy该block对象从栈区到堆区;
补充:其他三种情况——block作为函数返回值、含usingblock方法(如数据的枚举方法)、gcd的应用(自己可以验证,此处不再赘述);
那么,我们再看看mrc的情况
//打印————test1()和test2()
2019-01-14 17:56:46.641171+0800 mj_test[7171:306788] -------1 program ended with exit code: 0
分析:为什么per对象没有销毁?——因为需要手动释放;
//代码
[per release];
//打印————test1()和test2()
2019-01-14 17:59:39.091313+0800 mj_test[7243:309139] person dealloc 2019-01-14 17:59:39.091974+0800 mj_test[7243:309139] -------1 2019-01-14 17:59:39.092013+0800 mj_test[7243:309139] person dealloc 2019-01-14 17:59:39.092086+0800 mj_test[7243:309139] -------1 program ended with exit code: 0
分析:
<1>此时的block对象的作用域在第一个大括号范围内,超出则被释放;
<2>person实例对象被捕获到block对象结构体体中,同时其作用域也仅限于第一个大括号内,因此超出同样被释放;
二、堆区block分析
1)类型分析——arc
//strong类型
执行上述test2()方法,我们知道系统会自动将block对象从栈区copy到堆区;同时,person实例对象会被捕捉到block对象的结构体中,如下
struct __test2_block_impl_0 { struct __block_impl impl; struct __test2_block_desc_0* desc; person *per; __test2_block_impl_0(void *fp, struct __test2_block_desc_0 *desc, person *_per, int flags=0) : per(_per) { impl.isa = &_nsconcretestackblock; impl.flags = flags; impl.funcptr = fp; desc = desc; } };
分析:可以看到per是一个指针变量,而该指针变量默认修饰符为__strong;修改代码
__strong person *per = [[person alloc] init];
clang命令:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-9.0.0 main.m
说明:
<1>该命令行,只针对arc模式下,mrc模式下,如果有release语句会报错;
<2>该命令行,是解决arc模式下,实例对象为__weak类型,转成c++代码;
struct __test2_block_impl_0 { struct __block_impl impl; struct __test2_block_desc_0* desc; person *__strong per; __test2_block_impl_0(void *fp, struct __test2_block_desc_0 *desc, person *__strong _per, int flags=0) : per(_per) { impl.isa = &_nsconcretestackblock; impl.flags = flags; impl.funcptr = fp; desc = desc; } };
分析:因此,一般的指针变量,默认修饰符为__strong;
//weak类型
//代码
//arc void test2() { myblock block; { person *per = [[person alloc] init]; per.age = 10; __weak person *weakper = per; block = ^{ nslog(@"age:%d", weakper.age); }; // [per release]; } nslog(@"-------1"); }
//c++代码
struct __test2_block_impl_0 { struct __block_impl impl; struct __test2_block_desc_0* desc; person *__weak weakper; __test2_block_impl_0(void *fp, struct __test2_block_desc_0 *desc, person *__weak _weakper, int flags=0) : weakper(_weakper) { impl.isa = &_nsconcretestackblock; impl.flags = flags; impl.funcptr = fp; desc = desc; } };
//打印
2019-01-15 10:21:36.280746+0800 mj_test[997:45906] person dealloc 2019-01-15 10:21:36.281063+0800 mj_test[997:45906] -------1 program ended with exit code: 0
分析:
<1>此时的per由weakper弱指针指向,并被捕捉到block对象结构体中;
<2>结合上述强指针引用person实例对象的打印结果,weak修饰的指针变量先于输出语句销毁,可以肯定系统是没有对block对象copy到堆区的;
那针对不同类型指针的引用,系统是如果判断操作的呢?往下看
2)调用原理
//c++代码
typedef void(*myblock)(void); struct __test2_block_impl_0 { struct __block_impl impl; struct __test2_block_desc_0* desc; person *__weak weakper; __test2_block_impl_0(void *fp, struct __test2_block_desc_0 *desc, person *__weak _weakper, int flags=0) : weakper(_weakper) { impl.isa = &_nsconcretestackblock; impl.flags = flags; impl.funcptr = fp; desc = desc; } }; static void __test2_block_func_0(struct __test2_block_impl_0 *__cself) { person *__weak weakper = __cself->weakper; // bound by copy nslog((nsstring *)&__nsconstantstringimpl__var_folders_tb_zgsq5gq15rd3zvbdmw1c09y80000gn_t_main_3e374f_mi_2, ((int (*)(id, sel))(void *)objc_msgsend)((id)weakper, sel_registername("age"))); } static void __test2_block_copy_0(struct __test2_block_impl_0*dst, struct __test2_block_impl_0*src) {_block_object_assign((void*)&dst->weakper, (void*)src->weakper, 3/*block_field_is_object*/);} static void __test2_block_dispose_0(struct __test2_block_impl_0*src) {_block_object_dispose((void*)src->weakper, 3/*block_field_is_object*/);} static struct __test2_block_desc_0 { size_t reserved; size_t block_size; void (*copy)(struct __test2_block_impl_0*, struct __test2_block_impl_0*); void (*dispose)(struct __test2_block_impl_0*); } __test2_block_desc_0_data = { 0, sizeof(struct __test2_block_impl_0), __test2_block_copy_0, __test2_block_dispose_0}; void test2() { myblock block; { person *per = ((person *(*)(id, sel))(void *)objc_msgsend)((id)((person *(*)(id, sel))(void *)objc_msgsend)((id)objc_getclass("person"), sel_registername("alloc")), sel_registername("init")); ((void (*)(id, sel, int))(void *)objc_msgsend)((id)per, sel_registername("setage:"), 10); __attribute__((objc_ownership(weak))) person *weakper = per; block = ((void (*)())&__test2_block_impl_0((void *)__test2_block_func_0, &__test2_block_desc_0_data, weakper, 570425344)); } nslog((nsstring *)&__nsconstantstringimpl__var_folders_tb_zgsq5gq15rd3zvbdmw1c09y80000gn_t_main_3e374f_mi_3); } int main(int argc, const char * argv[]) { /* @autoreleasepool */ { __atautoreleasepool __autoreleasepool; test2(); } return 0; }
分析:
<1>根据前面的文章分析,我们发现block描述结构体(__test2_block_desc_0)中多了两个函数指针的成员变量
void (*copy)(struct __test2_block_impl_0*, struct __test2_block_impl_0*); void (*dispose)(struct __test2_block_impl_0*);
其中,copy函数指针指向__test2_block_copy_0函数,dispose指向__test2_block_dispose_0;
<2>__test2_block_copy_0函数主要是通过_block_object_assign函数来确定对per对象是否强引用,其根据就是per的引用类型——如果是__strong类型,则block对象对per对对象进行强引用(per的生命周期可控);如果是__weak类型,则进行弱引用(per的生命周期不可控);
<3>当block对像从堆区销毁时,会调用__test2_block_dispose_0函数会自动释放引用的per对象(相当于release)——注:严格意义上,此处的释放指的断开是block对象对per的引用即retaincount减1,至于per对象所占的内存是否被释放(回收)则在所不问(也许还有其他的指针变量引用),只有retaincount变为0零,其内存才会被回收;
补充:当block访问的外部的auto类型的局部数据为对象时,则会产生上述两个函数指针;如果是非实例对象(如基础数据类型),则不会有上述两个函数指针——原因:实例对象一般是在堆区开辟的内存,需要对其进行内存管理;
//代码
void test3() { int age = 10; ^{ nslog(@"%d", age); }; }
//clang
static struct __test3_block_desc_0 { size_t reserved; size_t block_size; } __test3_block_desc_0_data = { 0, sizeof(struct __test3_block_impl_0)};
三、结论
【1】栈区block:不论是arc还是mrc模式,指向该block对象的指针变量,不会对引用的auto类型的局部的实例对象进行强引用;
【2】堆区block:不论是arc还是mrc模式,指向该block对象的指针变量,根据引用的auto类型的局部的实例对象的引用类型,通过调用block结构体中的copy函数指针来调用_block_object_assign函数,来决定对实例对象是否进行强引用——__strong类型强引用,__weak类型弱引用;
【3】堆区block释放:系统会通过调用block结构体中的dispose函数指针来调用__test2_block_dispose_0函数,自动释放引用的外部auto类型的局部实例对象;
说明:
<1>block对象本身,即代码块(位于等号右边),非block指针变量(位于等号左边);
<2>所谓的强引用,类似于retain操作即保留实例对象(所占内存不随作用域限制而被自动回收),保证手动管理内存释放,达到可控的目的;
四、拓展——gcd引用分析
1)
//代码
- (void)touchesbegan:(nsset<uitouch *> *)touches withevent:(uievent *)event { person *per = [[person alloc] init]; dispatch_after(dispatch_time(dispatch_time_now, (int64_t)nsec_per_sec*3), dispatch_get_main_queue(), ^{ nslog(@"%@", per); }); nslog(@"touchesbegan"); }
//打印
2019-01-15 11:56:19.867668+0800 gcd_refrence[1558:90109] touchesbegan 2019-01-15 11:56:22.867823+0800 gcd_refrence[1558:90109] <person: 0x600003bb8cf0> 2019-01-15 11:56:22.867996+0800 gcd_refrence[1558:90109] person dealloc
2)
//代码
- (void)touchesbegan:(nsset<uitouch *> *)touches withevent:(uievent *)event { person *per = [[person alloc] init]; __weak person *weakper = per; dispatch_after(dispatch_time(dispatch_time_now, (int64_t)nsec_per_sec*3), dispatch_get_main_queue(), ^{ nslog(@"%@", weakper); }); nslog(@"touchesbegan"); }
//打印
2019-01-15 11:58:58.381396+0800 gcd_refrence[1619:93062] touchesbegan 2019-01-15 11:58:58.381583+0800 gcd_refrence[1619:93062] person dealloc 2019-01-15 11:59:01.381697+0800 gcd_refrence[1619:93062] (null)
3)
//代码
- (void)touchesbegan:(nsset<uitouch *> *)touches withevent:(uievent *)event { person *per = [[person alloc] init]; __weak person *weakper = per; dispatch_after(dispatch_time(dispatch_time_now, (int64_t)nsec_per_sec*1), dispatch_get_main_queue(), ^{ dispatch_after(dispatch_time(dispatch_time_now, (int64_t)nsec_per_sec*2), dispatch_get_main_queue(), ^{ nslog(@"%@", per); }); }); nslog(@"touchesbegan"); }
//打印
2019-01-15 12:00:44.996108+0800 gcd_refrence[1653:94592] touchesbegan 2019-01-15 12:00:48.088426+0800 gcd_refrence[1653:94592] <person: 0x6000010f4a20> 2019-01-15 12:00:48.088664+0800 gcd_refrence[1653:94592] person dealloc
4)
//代码
- (void)touchesbegan:(nsset<uitouch *> *)touches withevent:(uievent *)event { person *per = [[person alloc] init]; __weak person *weakper = per; dispatch_after(dispatch_time(dispatch_time_now, (int64_t)nsec_per_sec*1), dispatch_get_main_queue(), ^{ dispatch_after(dispatch_time(dispatch_time_now, (int64_t)nsec_per_sec*2), dispatch_get_main_queue(), ^{ nslog(@"%@", weakper); }); }); nslog(@"touchesbegan"); }
//打印
2019-01-15 12:01:42.122836+0800 gcd_refrence[1672:95546] touchesbegan 2019-01-15 12:01:42.123038+0800 gcd_refrence[1672:95546] person dealloc 2019-01-15 12:01:45.123256+0800 gcd_refrence[1672:95546] (null)
5)
//代码
- (void)touchesbegan:(nsset<uitouch *> *)touches withevent:(uievent *)event { person *per = [[person alloc] init]; __weak person *weakper = per; dispatch_after(dispatch_time(dispatch_time_now, (int64_t)nsec_per_sec*1), dispatch_get_main_queue(), ^{ nslog(@"%@", weakper); dispatch_after(dispatch_time(dispatch_time_now, (int64_t)nsec_per_sec*2), dispatch_get_main_queue(), ^{ nslog(@"%@", per); }); }); nslog(@"touchesbegan"); }
//打印
2019-01-15 12:02:50.591355+0800 gcd_refrence[1693:96581] touchesbegan 2019-01-15 12:02:51.685830+0800 gcd_refrence[1693:96581] <person: 0x6000033a4470> 2019-01-15 12:02:53.686541+0800 gcd_refrence[1693:96581] <person: 0x6000033a4470> 2019-01-15 12:02:53.686810+0800 gcd_refrence[1693:96581] person dealloc
6)
//代码
- (void)touchesbegan:(nsset<uitouch *> *)touches withevent:(uievent *)event { person *per = [[person alloc] init]; __weak person *weakper = per; dispatch_after(dispatch_time(dispatch_time_now, (int64_t)nsec_per_sec*1), dispatch_get_main_queue(), ^{ nslog(@"%@", per); dispatch_after(dispatch_time(dispatch_time_now, (int64_t)nsec_per_sec*2), dispatch_get_main_queue(), ^{ nslog(@"%@", weakper); }); }); nslog(@"touchesbegan"); }
//打印
2019-01-15 12:03:47.349637+0800 gcd_refrence[1715:97486] touchesbegan 2019-01-15 12:03:48.447971+0800 gcd_refrence[1715:97486] <person: 0x600000163900> 2019-01-15 12:03:48.448271+0800 gcd_refrence[1715:97486] person dealloc 2019-01-15 12:03:50.448553+0800 gcd_refrence[1715:97486] (null)
7)
//代码
- (void)touchesbegan:(nsset<uitouch *> *)touches withevent:(uievent *)event { person *per = [[person alloc] init]; __weak person *weakper = per; dispatch_after(dispatch_time(dispatch_time_now, (int64_t)nsec_per_sec*1), dispatch_get_main_queue(), ^{ nslog(@"%@", per); dispatch_after(dispatch_time(dispatch_time_now, (int64_t)nsec_per_sec*2), dispatch_get_main_queue(), ^{ nslog(@"%@", per); }); }); nslog(@"touchesbegan"); }
//打印
2019-01-15 12:04:37.584067+0800 gcd_refrence[1735:98332] touchesbegan 2019-01-15 12:04:38.679922+0800 gcd_refrence[1735:98332] <person: 0x600003bd7570> 2019-01-15 12:04:40.876560+0800 gcd_refrence[1735:98332] <person: 0x600003bd7570> 2019-01-15 12:04:40.876803+0800 gcd_refrence[1735:98332] person dealloc
分析:
<1>arc环境下,使用gcd时,系统自动将block对象从栈区copy到堆区;
<2>根据以上打印结果,发现如果是单纯的对per进行强引用,则延时3秒后per对象才销毁;如果是弱引用,则立即销毁,再次使用时为空(此时已经被释放);
<3>如果既对per强引用又有弱引用,在嵌套的gcd使用中,始终以强引用为准,弱引用不影响强引用;
结论:同一个auto类型的局部的实例对象,既有强引用,也有弱引用,以强引用为准;