iOS面试—二、autoreleasePool

 

问:api

1. 谈谈你对自动释放池的理解函数

2.自动释放池在mrc 和 arc 下的区别oop

3.多层自动释放池嵌套的对象在哪一层释放。优化

 

 

1、 释放时机 简介:

自动释放池是oc提供的一种自动回收的机制,具备延迟释放的特性,即当咱们建立了一个对象,并把他加入到了自动释放池中时,他不会当即被释放,会等到一次runloop结束或者做用域超出{}或者超出[pool release]以后再被释放。ui

一、MRC

(1). NSAutoreleasePool 建立.net

经过手动建立的方式来建立自动释放池,这种方式建立的自动释放池须要手动调用release(引用计数环境下,调用release和drain的效果相同,可是在CG下,drain会触发GC,release不会),自动释放池销毁时机:[pool release]代码执行完后线程

方法以下:指针

   NSAutoreleasePool *pool = [[ NSAutoreleasePool alloc]init ];//建立一个自动释放池
    Person *person = [[Person alloc]init];
    //调autorelease方法将对象加入到自动释放池
    //注意使用该方法,对象不会本身加入到自动释放池,须要人为调用autorelease方法加入
    [person autorelease];
    //,手动释放自动释放池执行完这行代码是,自动释放池会对加入他中的对象作一次release操做
    [pool release];

  

(2). 经过@autoreleasepool来建立对象

 *1. 对象的建立在自动释放池里面blog

@autoreleasepool {
        //在这个{}以内的变量默认被添加到自动释放池
         Person *p = [[Person alloc] init];
      }//出了这个括号,p被释放

  

 *2. 若是一个变量在自动释放池以外建立,以下,须要经过__autoreleasing该修饰符将其加入到自动释放池。

 @autoreleasepool {

}
Person *   __autoreleasing p = [
[Person alloc]init];
 self.person = p;

  

 

系统就是经过@autoreleasepool {}这种方式来为咱们建立自动释放池的,一个线程对应一个runloop,系统会为每个runloop隐式的建立一个自动释放池,全部的autoreleasePool构成一个栈式结构,在每一个runloop结束时,当前栈顶的autoreleasePool会被销毁,并且会对其中的每个对象作一次release(严格来讲,是你对这个对象作了几回autorelease就会作几回release,不必定是一次).

特别指出,使用容器的block版本的枚举器的时候,系统会自动添加一个autoreleasePool 

[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { 
// 这里被一个局部@autoreleasepool包围着 
}];

 

 

  

三、ARC

ARC下除了NSAutoreleasePool不可用之外,其余的同MRC

 

四、嵌套的autoreleasepool:autorelease对象被添加进离他最近的自动释放池,多层的pool会有多个哨兵对象。

@autoreleasepool {//p1被放在该自动释放池里面
        Person *p1 = [[Person alloc]init];
        @autoreleasepool {//p2被放在该自动释放池里面
            Person *p2 = [[Person alloc]init];
        }
    }

  

 

 

 

 

2、自动释放池的原理:

AutoreleasePoolPage:每个自动释放池没有单独的结构,每个autorealeasePool对象都是由若干个autoreleasePoolPage经过双向链表链接而成,类的定义以下

class AutoreleasePoolPage 
{
    magic_t const magic;
    id *next;//指向栈顶最新被添加进来的autorelease对象的下一个位置
    pthread_t const thread;//指向当前线程
    AutoreleasePoolPage * const parent;
    AutoreleasePoolPage *child;
    uint32_t const depth;
    uint32_t 
  }
  • autoreleasePool是按照线程一一对应的,
  • autoreleasePoolPage会开辟4096字节空间,除了上面的实例变量所占的空间,剩余的空间所有用来存储autorelease对象的地址
  • id *next:指向栈顶最新被添加进来的autorelease对象的下一个位置
  • 一个autoreleasePoolPage空间被占满时,会建立一个新的autoreleasePoolPage对象,后来的对象添加在在新
  • autoreleasePoolPage中
  • 哨兵对象:本质是一个值为nil的对象,由被定义在* NSAutoreleasePool类中的_token指针来保存。
  • hotPage:最新建立的AutoreleasePoolPage.

 

系统经过一个栈来管理全部的自动释放池,每当建立了一个新的自动释放池,系统就会把它压入栈顶,而且传入一个哨兵对象,将哨兵对象插入hotPage,这里分三种状况

  • 若hotPage未满,则直接插入哨兵对象,
  • 要是满了,新建一个NSAutoreleasePoolPage,并将其做为hotPage,而后将哨兵对象插入
  • 若是没有NSAutoreleasePoolPage,则新建一个NSAutoreleasePoolPage,并将其做为hotPage,插入哨兵对象,注意。这里的hotPage是没有父节点的。

每当有一个自动释放池要被释放的时候,哨兵对象就会做为参数被传入,找到该哨兵对象所在的位置后,将全部晚于哨兵对象的autorelease弹出,并对他们作一次release,而后将next指针一到合适的位置。

 

 

 

3、自动释放池的应用场景

一、对象做为函数返回值 
  当一个对象要做为函数返回值的时候,由于要遵循谁申请谁释放的思想,因此应该在返回以前释放,但要是返回以前释放了,就会形成野指针错误,可是要是不释放,那么就违背了谁申请谁释放的原则,因此就可使用autorelease延迟释放的特性,将其在返回以前作一次autorelease,加入到自动释放池中,保证能够被返回,一次runloop之>>后系统会帮咱们释放他

二、临时生成大量对象,必定要将自动释放池放在for循环里面,要释放在外面,就会由于大量对象得不到及时释放,而形成内存紧张,最后程序意外退出

 

for (int i = 0; i<10000; i++) {
        @autoreleasepool {
            UIImageView *imegeV = [[UIImageView alloc]init];
            imegeV.image = [UIImage imageNamed:@"efef"];
            [self.view addSubview:imegeV];
        }
    }

  

 

4、ARC下autorelease的优化

 ARC下,runtime提供了一套对autorelease返回值的优化策略TLS(线程局部存储),就是为每一个线程单独分配一块栈控件。以key-value的形式对数据进行存储(ARC对autorelease对象的优化标记)。先看优化中涉及到的几个函数:

 

一、__builtin_return_address(int level)

是一个内建函数,做用是返回函数的地址,参数是层级,若是是0,则表示是当前函数体返回地址;若是是1:则表示调用这个函数的外层函数。当咱们知道了一个函数体的返回地址的时候,就能够根据反汇编,利用某些固定的偏移量,被调方能够定位到主调放在返回值后>面的汇编指令,来肯定该条函数指令调用完成后下一个调用的函数

 

二、objc_autoreleaseReturnValue

经过__builtin_return_address(int level)检测外部是否是ARC环境,能够替代autorelease,是的话直接返回object,不是的话调用objc_autorelease()将对象注册到自动释放池里面,最后经过objc_retain来获取对象。

 

三、objc_retainAutoreleasedReturnValue 

经过以上代码能够看出, 与objc_autoreleaseReturnValue配合使用,若是在执行完objc_autoreleaseReturnValue()这个函数的下一个调用函数是objc_retainAutoreleasedReturnValue,那么就走最优化(在TLS中查询关于这个对象,若是有,直接返回对象,再也不对对象作retain)。

 

 

过程:

新建Person类 Person.h

#import <Foundation/Foundation.h>

@interface Person : NSObject
+(instancetype)createPerson;

@end

  

  Person.m

#import "Person.h"

@implementation Person

+(instancetype)createPerson{
    return [self new];
}

@end

  

ViewController.m

#import "ViewController.h"
#import "Person.h"
@interface ViewController ()
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
   [self testPerson];
}

- (void)TestPerson {
    self.person = [Person createPerson];
}

  

简化后对应的汇编:

+ (instancetype)createPerson {
    id tmp = [self new];  
    return objc_autoreleaseReturnValue(tmp); 
} 
- (void)testFoo {
    id tmp = _objc_retainAutoreleasedReturnValue([Person createPerson]); 

}

 

在调用objc_autoreleaseReturnValue的时候,会先经过__builtin_return_address这个内建函数return address,而后根据这个地址判断主调方在调用完objc_autoreleaseReturnValue这个函数之后是否紧接着调用了objc_retainAutoreleasedReturnValue函数,若是是,那么objc_autoreleaseReturnValue()就不将返回的对象注册到自动释放池里面(不作autorelease),runtime会将这个返回值存储在TLS中,作一个优化标记,而后直接返回这个对象给函数的调用方,在外部接收这个返回值的objc_retainAutoreleasedReturnValue()会先在TLS中查询有没有这个对象,若是有,那么就直接返回这个对象(不调用retain),因此经过objc_autoreleaseReturnValue和objc_retainAutoreleasedReturnValue的相互配合,利用TSL作一个中转,在ARC下省去了autorelease和retain的步骤,在必定程度上达到了最优化.

 

 

5、@autoreleasepool在ARC和MRC下的区别

  • ARC,将释放全部在@autorelesepool块中的对象,这就是为何本文示例使用了默认修饰符(strong),至关于作了retain,也同样被释放的缘由
  • MRC,在这种状况下@autorelesepool块等同于调用NSAutoreleasePool类的api

http://www.javashuo.com/article/p-qjdqtvul-mc.html

 

 

 

 

 

参考博客

http://www.javashuo.com/article/p-qjdqtvul-mc.html

黑幕背后的Autorelease 
内存管理

 

 

https://draveness.me/autoreleasepool

相关文章
相关标签/搜索