彙編跟蹤 Objc alloc 實現 & init

今天看了一些關於 Objc 相關的知識,也是一些基礎部分的知識。使用匯編來查看 alloc 的實現過程。bash

而後分享一下 init 到底幹了啥。架構

下面就一塊兒看看具體流程。函數

個人測試代碼以下

#import <UIKit/UIKit.h>
#import "AppDelegate.h"
#import "CTStudent.h"
#import <objc/runtime.h>

int main(int argc, char * argv[]) {
    
    CTStudent *st = [CTStudent alloc];
    NSLog(@"size %lu", class_getInstanceSize([st class]));
    
    return 0;
}
複製代碼

爲了避免加載過多的變量及方法,我直接在Main 文件裏開始了。測試

說明:ui

  1. CTStudent 這個類裏面啥都沒寫。
  2. 使用 alloc 初始化,而後獲取 st 的大小。

那麼接下來就具體看看使用匯編來看看具體流程。atom

Notespa

注意請連接真機就行調試,不要用電腦,由於電腦和收回系統架構不一樣,彙編代碼也是不一樣的。3d

在此以前介紹幾個接下來要用的彙編命令調試

  1. bl 或者 b ---> 意思是跳轉到指定的函數中去
  2. ret ---> 意思是 方法走到這裏就返回了,不在往下面走了 具體的能夠參考網上盡詳的資料。

開始

  1. 首先我是找 alloc 這個方法的實現。因此我使用符號斷點標記 alloc, 只要通過這個方法的都會停留下來。

  1. 運行,他會停留在 _objc_rootAlloc 這個函數這裏. code

  2. 那麼我繼續使用斷點符號把 _objc_rootAlloc 也就好了標記.

  1. 運行發現他會走到一個class_createInstance 的函數調用中去。

Note:若是彙編沒有走到 class_createInstance 這一步以前,就不用啓用 class_createInstance 符號斷點,由於系統中的不少函數初始化也會走這個方法,因此我就在這個函數以前打了一個斷點,若是真的走到斷點符號前面的這個斷點,我就會使用 register read 來讀取當前內存中的值。那麼我爲何會在ret 設置斷點了,是由於alloc 調用完了會有返回值。

如上圖所示。 你們想必都熟悉一個 Runtime 的函數, Objc_mseSend 函數。 Objc_mseSend 默認會有兩個值。

Objc_mseSend(id self, SEL _cmd)

* 第一個方法的調用者
* 第二個參數是方法編號
複製代碼

在內存寄存器中,x0 默認是方法調用者, x1方法實現編號. 那麼結合上圖能夠看到上面的 x0 地址,使用 po 命令便可查看對應的值。 咱們能夠看到是CTStudent ,正是咱們 alloc的調用者. 若是肯定是咱們關注的對象,那麼咱們就須要ge 5. 我把 class_createInstance 加入斷點中。

接下來會來到 class_createInstance 的調用流程中。

通過一系列的走位,最終被 return 出去了。

而後繼續,被 return 到了上一次。此時控制到輸出了

那麼,說明此時 上的 st已經有大小了,大小位:8 字節。那麼爲啥我什麼都沒寫倒是 8 了,由於每一個類中都有一個 isa, 是 8 字節大小。

摘自 Objc4-750 源碼

inline Class 
objc_object::ISA() 
{
    assert(!isTaggedPointer()); 
#if SUPPORT_INDEXED_ISA
    if (isa.nonpointer) {
        uintptr_t slot = isa.indexcls;
        return classForIndex((unsigned)slot);
    }
    return (Class)isa.bits;
#else
    return (Class)(isa.bits & ISA_MASK);
#endif
}

複製代碼
union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};
複製代碼

能夠看出 isa 是一個 uintptr_t 類型。

那麼根據彙編來看 alloc 的流程爲:

alloc -> _objc_rootAlloc -> class_createInstance. 固然這裏面確定省略了不少步驟,可是使用匯編來查看代碼的運行步驟是頗有趣的。不妨動手試試看。固然詳細的過程,我會以解析源碼的過程來闡述明白。

附帶今天跟蹤源碼畫的 alloc 走位圖

init 走位

init 代碼實現部分

- (id)init {
    return _objc_rootInit(self);
}

id _objc_rootInit(id obj)
{
    return obj;
}
複製代碼

其實就是返回了本身。

那麼爲啥要這麼作了? 其實就是讓你在 init 中作一些初始化配置等操做。

想一想,咱們平時開發都在 init 中幹了啥了。

@interface Student : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSMapTable *mapTable;
- (void)save;
- (void)log;
@end

@implementation Student
- (instancetype)init
{
    self = [super init];
    if (self) {
        self.name = @"CT_Kare";
        self.mapTable = [[NSMapTable alloc] init];
    }
    return self;
}

- (void)save {
    [self.mapTable setObject:self.name forKey:@"name"];
}

- (void)log {
    NSLog(@"self.mapTable %@", self.mapTable);
}
@end
複製代碼

那麼,其實也是作了一些初始化操做。

總結:因此在初始化一個對象時,alloc 作了初始化準備,好比開闢對象空間,init Isa,等操做。init 其實就是返回了初始化對象自己,而後在 init 中作了一些初始化操做配置而已。

相關文章
相關標籤/搜索