OC-AutoRelease的了解

1.前言

随着Xcode的发展,我们已经不需要自己手动管理OC对象的引用计数了;编译器在编译的时候会根据环境上下文帮我们自动添加上管理的代码。但是我们需要通过MAC去了解内存管理中关于AutoRelease的原理。

  • 创建一个项目,我们把环境切换到MRC下,看看如果不自己手动管理内存对象会怎么样。如下图,新建一个项目,把环境切换到MRC下:
    在这里插入图片描述
  • 新建一个Person的类,添加如下的代码:

-------.h-----------
@interface Person : NSObject

@property (nonatomic, copy) NSString *name;

@end



-------.m-----------
#import "Person.h"

@implementation Person

- (void)dealloc {
    NSLog(@"%s", __func__);
    [super dealloc];
}

@end

  • 使用如下(我这个是创建的MAC的项目):
#import "Person.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        Person *pers = [[Person alloc] init];
        pers.name = @"Test";
    }
    return 0;
}
  • 运行后,可以看到,并没有打印Person的dealloc的函数。因为在main的函数中,并没有调用Person的release操作,现在我们加上release测试一下:
#import "Person.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        Person *pers = [[Person alloc] init];
        pers.name = @"Test";
        [pers release];
    }
    return 0;
}
  • 运行效果如下:
    在这里插入图片描述
    当调用了release的方法之后,Person是会被释放的。但是调用release有个不好的地方是:对象已经被释放,如果在release后面继续调用这个对象就会崩溃;这样在使用起来有比较多的局限性。因此,我们可以用autoRelease来代替release,替换后的效果如下:
    在这里插入图片描述
  • 当我们用autoRelease时,对象会在@autoreleasepool{}这个大括号结束后,自动发送release的操作。

2.AutoRelease的工作原理

2.1AutoRelease转成c++的代码

编写代码如下:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *pers = [[[Person alloc] init] autorelease];
    }
    return 0;
}

用命令行,进入该项目main的文件夹,将main的文件转成main.cpp的文件,命令行的命令如下:

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main_arm64.cpp

生成cpp后,查看代码:
在这里插入图片描述
也就是说,将

@autoreleasepool {
    Person *pers = [[[Person alloc] init] autorelease];
}

这部分的代码转成了C++的代码如下:

/* @autoreleasepool */ 
{ 
	__AtAutoreleasePool __autoreleasepool; 
    Person *pers = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init")), sel_registerName("autorelease"));
}

也就是说,@autoreleasepool {·····}的的代码生成了

{
	__AtAutoreleasePool __autoreleasepool; 
	·········
}

类似的代码。

2.2查看__AtAutoreleasePool是个什么东西?

查看cpp里面是如何定义的:
在这里插入图片描述
解读如下:

struct __AtAutoreleasePool {
    
  __AtAutoreleasePool() { //构建函数,在__AtAutoreleasePool被创建的时候调用
      atautoreleasepoolobj = objc_autoreleasePoolPush();
  }
  ~__AtAutoreleasePool() {//析构函数,在__AtAutoreleasePool被销毁的时候调用
      objc_autoreleasePoolPop(atautoreleasepoolobj);
  }
  void * atautoreleasepoolobj;
};

也就是说,从调用@autoRelease{}时,转成的c++的代码查看:
在这里插入图片描述

  • __AtAutoreleasePool这个对象的作用域在 括号1括号2 之间;
  • 当进入括号1后,创建__autoreleasepool这个变量;
  • 根据struct __AtAutoreleasePool这个结构体的定义可知,此时会调用构建函数atautoreleasepoolobj = objc_autoreleasePoolPush();;
  • 当出了括号2后,__autoreleasepool这个对象就会被销毁( 因为__AtAutoreleasePool这个对象的作用域在 括号1括号2 之间),那么就会调用struct __AtAutoreleasePool这个结构体的析构函数objc_autoreleasePoolPop(atautoreleasepoolobj);,最后等价于:
int main(int argc, const char * argv[]) {
    
    atautoreleasepoolobj = objc_autoreleasePoolPush();
    Person *pers = [[[Person alloc] init] autorelease];
    objc_autoreleasePoolPop(atautoreleasepoolobj);
    
    return 0;
}

如果有多个@autoRelase嵌套的话,如下图:
在这里插入图片描述

3. AutoreleasePoolPage

3.1AutoreleasePoolPage的结构

查看runtime的源码中关于objc_autoreleasePoolPush()的函数,如下:
在这里插入图片描述
在objc_autoreleasePoolPush函数中,会有一个AutoreleasePoolPage的对象,其对象的结构如下:
在这里插入图片描述
其中,需要双向链表是因为,一个autoReleasePoolPage对象只有4040的空间存放调用autoRelease的对象,可能这个空间不够存放所有自动释放的对象,需要生成多个autoReleasePoolPage一起存放。

3.2AutoreleasePoolPage的工作原理

  • 当编译器调用objc_autoreleasePoolPush()函数时,源码如下:
void *
objc_autoreleasePoolPush(void)
{
    return AutoreleasePoolPage::push();
}

而push()的源码如下:
在这里插入图片描述
也就是说,当调用push时会创建一个新的autoReleasePoolPage, 然后将POOL_BOUNDARY存放到autoReleasePoolPage的如下位置:
在这里插入图片描述
然后其他的autoRelease的对象,再继续放到POOL_BOUNDARY的后面(POOL_BOUNDARY其实就是一个nil,空地址,用于做标记)。id next是指向下一个autoRelease的对象存放的地址。

  • 当编译器调用objc_autoreleasePoolPop()函数时,会从最后一个autoRelease的对象开始释放对象,直到POOL_BOUNDARY

3.3如果有多个@autoreleasepool嵌套的情况

代码如下:
在这里插入图片描述
那么poolPage的结构如下:
在这里插入图片描述

需要注意的是:p1 p2 p3 p4 应该在同一个poolPage里的,但是因为画图的时候,格数不够,就分开放了。

  • 我们可以通过extern void _objc_autoreleasePoolPrint(void);的系统函数,来查看释放池里面的数据:
    在这里插入图片描述

3.4插播一个小小的知识点:为什么我们可以调用系统的私有C语言函数?

我们可以在Person对象中,添加一个C语言的函数:

@implementation Person

void Person_test() {
    NSLog(@"%s", __func__);
}

- (void)dealloc {
    NSLog(@"%s", __func__);
    [super dealloc];
}

@end

在main中,如果我们想要使用这个 Person_test() 函数,我们可以这么做:

extern void Person_test(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person_test();
    }
    return 0;
}

效果如下:
在这里插入图片描述

4.RunLoop与AutoRelease的关系

iOS在主线程中会注册两个observer:

  • KCFRunLoopEntry (进入runLoop)
  • KCFRunLoopBeforeWaiting | KCFRunLoopExit(runloop进入休眠或者退出)

runLoop与AutoRelease的关系
(1)当observer监听到KCFRunLoopEntry时,就会objc_autoreleasePoolPush()的操作;
(2)当observer监听到KCFRunLoopBeforeWaiting时,就会调用objc_autoreleasePoolPop(),把之前的自定释放对象释放掉; 并且调用objc_autoreleasePoolPush()的操作,重新加入POOL_BOUNDRY;
(3)当observer监听到KCFRunLoopExit时,就会调用objc_autoreleasePoolPop()函数。

总结

  • 当给某个对象添加autoRelease时,如果是添加在@autoReleasePool{}里面的话,那么会出了**@autoReleasePool{}的右括号后**,直接释放;
  • 当给某个对象添加autoRelease时,是直接添加在某个方法上的,那么autoRelease释放的时机应该是runLoop休眠或者退出的时候
  • ARC下,不能调用release 和 autoRelease,系统自动为我们生成的release操作,所以出了局部变量的作用域,对象就会比立即释放掉。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值