2. IOS 內存、自動釋放池、橋接的研究

一:簡述內存的管理

內存管理最重要的就是誰建立誰釋放的原則,基本上能夠解決咱們90%以上(筆者憑藉經驗猜想的數字,不要太較真)的問題,可是有時候,系統作優化,會不遵循這個原則。git

下面咱們經過一個例子來介紹:github

1. 首先咱們定義一個Mark

@interface Mark : NSObject
+ (Mark *)newMark;
+ (Mark *)createMark;
+ (Mark *)getMark;
@end

@implementation Mark
+ (Mark *)newMark {
    return [[Mark alloc] init];
}
+ (Mark *)createMark {
    return [[Mark alloc] init];
}
+ (Mark *)getMark {
    return [[Mark alloc] init];
}
@end

複製代碼

2.在控制器中調用代碼

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    Mark * mark = [Mark createMark];
    Mark * mark2= [Mark getMark];
    Mark * mark3= [Mark newMark];
    
    self.mark = mark;
    self.mark2 = mark2;
    self.mark3 = mark3;
}
複製代碼

3. 咱們進入Hopper來查看這些代碼。

[Mark createMark][Mark getMark]的內部其實 會調用 autoreleaseReturnValue來放入到自動釋放池中。 [Mark newMark]會自動返回,給持有的人swift

void * +[Mark newMark](void * self, void * _cmd) {
    rax = [Mark alloc];
    rax = [rax init];
    return rax;
}

void * +[Mark createMark](void * self, void * _cmd) {
    rax = [[Mark alloc] init];
    rax = [rax autorelease];
    return rax;
}

void * +[Mark getMark](void * self, void * _cmd) {
    rax = [[Mark alloc] init];
    rax = [rax autorelease];
    return rax;
}

複製代碼

在控制器中,咱們能夠知道bash

對於 [[Mark createMark] retain][[Mark getMark] retain] 其實僞代碼有些錯誤,咱們經過查看彙編代碼能夠知道會有個 imp___stubs__objc_retainAutoreleasedReturnValue ,其實就是把autoReleaseretain 合併起來,這樣就不用再注入到自動釋放池中.數據結構

void -[AutoReleaseVC viewDidLoad](void * self, void * _cmd) {
    rax = var_20;
    [[rax super] viewDidLoad];
    var_28 = [[Mark createMark] retain];
    var_30 = [[Mark getMark] retain];
    var_38 = [Mark newMark];
    [self setMark:var_28];
    [self setMark2:var_30];
    [self setMark3:var_38];
    objc_storeStrong(var_38, 0x0);
    objc_storeStrong(var_30, 0x0);
    objc_storeStrong(var_28, 0x0);
    return;
}
複製代碼

二:自動釋放池

首先咱們要搞清兩個概念,一個是 autoreleasepool,另一個是AutoreleasePoolPage,帶着這兩個問題,咱們看下面例子。iphone

1. -rewrite-objc 查看cpp代碼

- (void)testAutoPool {
    @autoreleasepool {
        NSObject * obj = [[NSObject alloc] init];
        NSLog(@"obj = %@",obj);
    }
}
複製代碼

經過命令 xcrun -sdk iphoneos clang -rewrite-objc -F Foundation -arch arm64 Auto.m , 咱們看看 @autoreleasepool 究竟是什麼東西?優化

static void _I_AutoModel_testAutoPool(AutoModel * self, SEL _cmd) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        NSObject * obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_zx_py2__3n12pd_dj7nkk36jzqm0000gn_T_AutoModel_b08238_mi_0,obj);
    }
}

struct __AtAutoreleasePool {
  __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
  ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
  void * atautoreleasepoolobj;
};

複製代碼

再經過Hopper 咱們能夠更加清楚看到ui

void -[AutoModel testAutoPool](void * self, void * _cmd) {
    var_28 = objc_autoreleasePoolPush();
    var_18 = [[NSObject alloc] init];
    NSLog(@"obj = %@", var_18);
    objc_storeStrong(var_18, 0x0);
    objc_autoreleasePoolPop(var_28);
    return;
}
複製代碼

其實 @autoreleasepool{} 內部就是 經過 var_28 = objc_autoreleasePoolPush()objc_autoreleasePoolPop(var_28); 來做用的spa

經過查找源碼objc4-723中的NSObject.mm,咱們能夠查看到 AutoreleasePoolPage,而後咱們查看一下它的數據結構和一些方法線程

class AutoreleasePoolPage {
    magic_t const magic;
    id *next;
    // 說明 一個 autoreleasePool 對應一個線程
    pthread_t const thread; 
    AutoreleasePoolPage * const parent;
    AutoreleasePoolPage *child;
    uint32_t const depth;
    uint32_t hiwat;
};

void *objc_autoreleasePoolPush(void) {
    return AutoreleasePoolPage::push();
}

void objc_autoreleasePoolPop(void *ctxt) {
    AutoreleasePoolPage::pop(ctxt);
}
複製代碼

咱們應該很清楚的知道,AutoreleasePoolPage 是一個 雙向鏈表,爲何要設置多張Page,其實系統仍是爲了節省空間,相似咱們的內存分頁的思想。

經過 autpreleasePoolPush設置哨兵nil 也就是begain, 裏面有個next 指針指定了下個obj的位置,也就是end。那麼在調用autoreleasePoolPop的時候其實就是釋放end - begain 的空間,同時 給這裏的每一個對象都發送一個 release方法。

三:橋接的內存管理

橋接主要設計到下面幾個方法,其實CFBridgingRetain 內部是調用 __bridge_retained的; CFBridgingRelease 內部也是調用 __bridge_transfer

  • __bridge : 不會改變原來的ownership
  • p = (__bridge_retained void *)objp = (void *) CFBridgingRetain(obj) : 把OC裏面的ARCownership全部權,給CF類型的對象
  • (id)CFBridgingRelease(p)(__bridge_transfer id)p: 把CF的ownership全部權,交給 OC對象;

1.1 非持有 CF對象 = (__bridge void *)OC對象

經過 __bridge 把OC 轉換成CF

- (void)brigeTest1 {
    void * p = 0;
    {
        id obj = [NSObject new];
        p = (__bridge void *)obj;
    }
    // 直接崩潰,由於p 不持有 obj 對象, 當 {} 結束的時候,obj被釋放了, 因此致使崩潰
    NSLog(@" p = %@",p);
}
複製代碼

1.2 持有 CF對象 = (__bridge_retained void *)OC對象 或者 CF對象 = (void *) CFBridgingRetain(OC對象)

- (void)brigeTest2 {
    void * p = 0;
    {
        id obj = [NSObject new];
        p = (__bridge_retained void *)obj;
    }
    NSLog(@" p = %@",p);
    CFBridgingRelease(p);
}
- (void)brigeTest3 {
    void * p = 0;
    {
        id obj = [NSObject new];
        p = (void *) CFBridgingRetain(obj);
    }
    // 直接崩潰,由於p 不持有 obj 對象, 當 {} 結束的時候,obj被釋放了, 因此致使崩潰
    NSLog(@" p = %@",p);
    CFBridgingRelease(p);
}
複製代碼

2.1 非持有 id o = (__bridge id)p

- (void)test {
    
    // 生成 CF p
    void * p = 0;
    id obj = [NSObject new];
    p = (void *) CFBridgingRetain(obj);
    
    // OC 對象
    
    // 無論理p的內存
    //Use __bridge to convert directly (no change in ownership)
    //使用 __bridge 不會改變 p的全部權
    id o = (__bridge id)p;
}
複製代碼

2.2 持有id o1 = (id)CFBridgingRelease(p)id o2 = (__bridge_transfer id)p

- (void)test {
    
    // 生成 CF p
    void * p = 0;
    id obj = [NSObject new];
    p = (void *) CFBridgingRetain(obj);
    
    // OC 對象

    //Use CFBridgingRelease call to transfer ownership of a +1 'void *' into ARC
    // 把p的全部權轉移給 oc對象持有
    id o1 = (id)CFBridgingRelease(p);
    // 和 CFBridgingRelease 等價
    id o2 = (__bridge_transfer id)p;
}

複製代碼

參考

相關文章
相關標籤/搜索