说明:阅读本文,请参照之前的block文章加以理解;
一、循环引用的本质
//代码——ARC环境
void test1() { Person *per = [[Person alloc] init]; per.age = 10; per.block = ^{ NSLog(@"-------1"); }; }
int main(int argc, const char * argv[]) { @autoreleasepool { test1(); // test2(); } NSLog(@"----"); return 0; }
#import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN typedef void(^MyBlock)(void); @interface Person : NSObject @property (nonatomic, assign) int age; @property (nonatomic, copy) MyBlock block; @end NS_ASSUME_NONNULL_END #import "Person.h" @implementation Person - (void)dealloc { // [super dealloc]; NSLog(@"%s", __func__); } @end
//打印
2019-01-17 16:46:28.353740+0800 MJ_TEST[2990:240693] -[Person dealloc] 2019-01-17 16:46:28.354013+0800 MJ_TEST[2990:240693] ---- Program ended with exit code: 0
分析:main函数日志输出之前,Person实例对象就被销毁了——因为在test1()方法中,强指针per持有[[Person alloc] init]对象会执行retain操作导致Person实例对象的retainCount值为2(此前alloc操作,其retainCount值就设置为1),当test1()方法结束时,per被存放在栈区也随之销毁,故per不会再持有Person实例对象即执行release操作导致该对象的retainCount指减1;当自动销毁池autoreleasepool结束时,会自动向池中的所有对象再次发送一条release消息,那么此时Person实例对象的retainCount值再次减1变成0,对象的引用计数一旦为0,其所占内存会被自动回收,因此Person实例对象就会销毁;
补充:我们知道blcok的内存管理模式为copy策略(原因就不分析了),因为在ARC环境下强指针持有block对象,系统会自动将block对象copy到堆区中,所以ARC模式下,系统会自动帮助我们对block进行copy的管理策略,我们写成strong的策略是没有任何问题的——但是,MRC模式下必须是copy策略,系统不会帮你管理内存,只能手动;这点请注意!
至此,以上Person实例对象销毁是正常的,那么什么情况下是不正常的?往下看:
//代码
void test2() { Person *per = [[Person alloc] init]; per.age = 10; per.block = ^{ NSLog(@"-------%d", per.age); }; }
//打印
2019-01-17 15:00:31.859710+0800 MJ_TEST[2486:187534] ----- Program ended with exit code: 0
//clang
main.cpp
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; Person *__strong per; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__strong _per, int flags=0) : per(_per) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } };
Person.cpp
struct Person_IMPL { struct NSObject_IMPL NSObject_IVARS; int _age; MyBlock _Nonnull _block; }; struct NSObject_IMPL { Class isa; }; static void(* _I_Person_block(Person * self, SEL _cmd) )(){ return (*(MyBlock _Nonnull *)((char *)self + OBJC_IVAR_$_Person$_block)); } static void _I_Person_setBlock_(Person * self, SEL _cmd, MyBlock _Nonnull block) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Person, _block), (id)block, 0, 1); }
分析:
<1>我们知道,oc对象编译成C++后的本质就是一个结构体Person_IMPL,该结构体的第一个成员变量就是isa指针,指向类对象本身;同时,@property修饰的属性,系统会自动生成一个结构体成员变量,还为之生成getter和setter方法——这些之前的文章已经说过,此处不再赘述!
<2>per实例对象结构体Person_IMPL中含有_block变量,通过setter法_I_Person_setBlock_将block对象(等号右边)赋值给该_block变量,因此_block指向block对象(强引用);
<3>在__main_block_impl_0结构体中,我们看到Person *__strong per,所以,block对象本身对Person实例对象也是强引用;
综上:block对象结构体__main_block_impl_0通过其内部成员指针变量Person *__strong per持有Person实例对象(强引用),而Person实例对象结构体Person_IMPL通过其内部成员指针变量_block持有block对象(强引用)——因此二者构成循环引用,当autoreleasepool大括号结束时,block对象和Person实例对象所占内存依然没有被系统回收,因为他们的引用计数依然大于0;
//图解——注:self是一个auto型的局部变量,指向的是[[Pers