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

相關文章
相關標籤/搜索