设为首页 加入收藏

TOP

block本质探寻八之循环引用(一)
2019-08-26 07:00:41 】 浏览:66
Tags:block 本质 探寻 循环 引用

说明:阅读本文,请参照之前的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

首页 上一页 1 2 3 下一页 尾页 1/3/3
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇Swift构造 下一篇笔记:iOS字符串的各种用法(字符..

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目