OC底層原理 06: isa結構分析

主動已經是我對熱愛東西表達的極限了

對象的本質?
聯(lián)合體位域的簡析?
isa的結構信息?
isa如何關聯(lián)類?
通過位運算驗證關聯(lián)類
總結。

  • 什么是對象?
    對象在底層變成了什么呢?

  • 什么是Clang?

Clang是一個由Apple主導編寫,基于LLVM的C/C++/Objective-C編譯器

  • 用Clang做些什么?

Clang 通過底層編譯,將一些m文件編譯為cpp。 因為OC為C++或者C的超集,通過Clang底層編譯,可以更多的看到底層的實現(xiàn)原理與邏輯和底層的架構

  • 如何使用Clang?

Clang終端操作四種命令如下:

clang -rewrite-objc main.m -o main.cpp 把目標文件編譯成c++文件

`xcode`安裝的時候順帶安裝了`xcrun`命令,`xcrun`命令在`clang`的基礎上進行了 一些封裝,要更好用一些

xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp (模擬器)

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main- arm64.cpp (手機)

Clang 具體操作請查看:后續(xù)

通過Clang編譯查看的底層是如何實現(xiàn)的,首先定義如下:

  @interface LGPerson : NSObject
  @property (nonatomic, copy) NSString *name;
  @end

  @implementation LGPerson
  @end

  int main(int argc, const char * argv[]) {
      @autoreleasepool {
          // insert code here...
          NSLog(@"Hello, World!");    }
      return 0;
  }

找到已經編譯好的main.cpp文件,搜索LGPerson,得到我想要的信息如下:

  #ifndef _REWRITER_typedef_LGPerson
  #define _REWRITER_typedef_LGPerson
  typedef struct objc_object LGPerson;
  typedef struct {} _objc_exc_LGPerson;
  #endif
  
  extern "C" unsigned long OBJC_IVAR_$_LGPerson$_name;
  struct LGPerson_IMPL {
          struct NSObject_IMPL NSObject_IVARS;
      NSString *_name;
  };

  // @property (nonatomic, copy) NSString *name;
  /* @end */
   // @implementation LGPerson
  static NSString * _I_LGPerson_name(LGPerson * self, SEL _cmd) { 
        return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_name)); 
  }

  extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);

  static void _I_LGPerson_setName_(LGPerson * self, SEL _cmd, NSString *name) {
      objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _name), (id)name, 0, 1);
  }
  // @end
  • 對象在底層會被編譯為struct

對象是如何被編譯為結構體的呢?

我們知道結構體在C++可以繼承,C可以偽繼承

偽繼承方式:
直接將NSObject結構體定義為LGPerson中的第一個屬性,意味著LGPerson 擁有 NSObject中的所有成員變量
LGPerson中的第一個屬性 NSObject_IVARS 等效于 NSObject中isa

這里將LGPerson_IMPL結構體中的第一屬性命名為當前結構體,即LGPerson_IMPL擁有 NSObject中的所有成員變量。而NSObject_IVARSisa

LGPerson類中的nameget, set方法也是一一對應的:

main.cpp中類對應的get,set方法截圖

set方法調用了objc_setProperty,通過分析得出objc_setProperty采用工廠模式:return newValue, remove oldValue ;意味著任何類中的set方法都會執(zhí)行了如下操作:

objc4_781源碼中找到objc_setProperty查看如何執(zhí)行操作get , set

繼續(xù)分析isa結構,OC底層原理 結構體&聯(lián)合體

  • isa結構分析:

在之前探索 OC底層原理 alloc & init & new 篇 時提到過initInstanceIsa方法,通過initInstanceIsa為切入點來分析isa是如何初始化的,可以看到isa指針的類型isa_t定義是通過聯(lián)合體(union)來定義聯(lián)合體不清楚的小伙伴請參考:OC底層原理 結構體&聯(lián)合體

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls; //這里返回的cls 為什么會是一個class類型?請查看后續(xù)
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};

這里的isa_t為什么要定義成聯(lián)合體呢?是為了優(yōu)化內存,而isa指針占用的內存大小為8字節(jié),即64位,已經能夠存儲大量信息了,這樣可以節(jié)省內存空間,以提高性能的目的

isa_t的定義中可以看出,
通過cls初始化,bits無默認值
通過bits初始化,cls有默認值
體現(xiàn)了聯(lián)合體互斥性

通過宏ISA_BITFIELD可以看出isaiOS( __arm64__)macOS(__x86_64__)的計算是存在差異

isa不同環(huán)境的差異化截圖
  • isa的結構信息對應如下:
    • nonpointer:表示是否對 isa 指針開啟指針優(yōu)化
      0:純isa指針;
      1:不止是類對象地址,isa 中包含了類信息、對象的引用計數等

    • has_assoc:關聯(lián)對象標志位,
      0沒有關聯(lián)對象
      1存在關聯(lián)對象

    • has_cxx_dtor:該對象是否有 C++ 或者 Objc 的析構器(類似于dealloc
      如果析構函數,則需要做析構邏輯
      如果沒有,則可以更快的釋放對象

    • shiftcls:存儲類指針的值(類的地址), 即類信息開啟指針優(yōu)化的情況下,
      __arm64__ 架構中有 33 位用來存儲類指針。
      __x86_64__ 架構中有 44 位用來存儲類指針。

    • magic:用于調試器判斷當前對象是真的對象還是沒有初始化的空間

    • weakly_referenced:志對象是否被指向或者曾經指向一個 ARC 的弱變量
      沒有弱引用的對象可以更快釋放。

    • deallocating:標志對象是否正在釋放內存

    • has_sidetable_rc:當對象引用計數大于 10時,則需要借用該變量存儲進位

    • extra_rc:當表示該對象的引用計數值,實際上是引用計數值減 1, 例如,如果對象的引用計數為 10,那么 extra_rc9。如果引用計數大于 10, 則需要使用 has_sidetable_rc

更直觀的isa的結構信息圖如下:

isa結構對照表

通過 initIsa方法,查看 isa指針是如何被初始化

inline void 
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
{ 
    ASSERT(!isTaggedPointer()); 
    
    if (!nonpointer) {
        isa = isa_t((uintptr_t)cls);//isa初始化
    } else {
        ASSERT(!DisableNonpointerIsa);
        ASSERT(!cls->instancesRequireRawIsa());

        isa_t newisa(0);  //isa初始化
#if SUPPORT_INDEXED_ISA  // !nonpinter 執(zhí)行,即isa通過cls定義
        ASSERT(cls->classArrayIndex() > 0);
        newisa.bits = ISA_INDEX_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else // bits 時執(zhí)行的流程
        newisa.bits = ISA_MAGIC_VALUE; // bits進行賦值
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.shiftcls = (uintptr_t)cls >> 3; //isa與類關聯(lián)
#endif
        // This write must be performed in a single store in some cases
        // (for example when realizing a class because other threads
        // may simultaneously try to use the class).
        // fixme use atomics here to guarantee single-store and to
        // guarantee memory order w.r.t. the class index table
        // ...but not too atomic because we don't want to hurt instantiation
        isa = newisa;
    }
}
  • initIsa方法主要使用:cls初始化isa,和bits初始化isa

執(zhí)行代碼,開始驗證,以LGPerson為例:

對賦值newisa.bits賦值之后打印出newisa查看LLDB結果

p newisa 打印結果

  • 對賦值newisa.bits賦值會對cls進行追加;對cls賦值不會同時對bits進行賦值(聯(lián)合體互斥性

通過LLDB打印結果,在進行賦值的時候,bits中的magic會存在一個59呢?
使用計算器查看宏ISA_MAGIC_VALUE592進制

為什么magic為59

magic的占位為6,從47~52表示magic的占位,所以magic的默認值為59二進制表示;也可以理解為isa指針將magic的占位為6,轉換為2進制存儲

  • isa與類關聯(lián)

當執(zhí)行完如下代碼后,LLDB打印出當前newisa
clsisa 關聯(lián)原理就是isa指針中的shiftcls位域中存儲了類信息,其中initInstanceIsa的過程是將 calloc 指針 和當前的 類cls 關聯(lián)起來。而newisa.shiftcls = (uintptr_t)cls >> 3 是將當前cls 強轉為系統(tǒng)能夠編譯的01識別碼。然后右移3位。之所以要移動3位,要知道shiftcls才是我們需要存儲的類信息,從上面isa對照表中可以看出,前面三位并不是我們需要的類內容,需要右移3位進行抹0操作。

newisa.shiftcls = (uintptr_t)cls >> 3; //isa與類關聯(lián)
isa與類關聯(lián)結果圖

這個時候我們已經知道isa 進行了關聯(lián)。

  • 開始驗證isa中的shiftcls是否真正的存儲了當前類信息

initInstanceIsa方法返回出去,然后開始驗證:

  • 方式1. 通過isa指針地址與宏ISA_MSAK 的值 通過位運算& 來驗證
      #   define ISA_MASK        0x00007ffffffffff8ULL    //__x86_64__ 
      #   define ISA_MASK        0x0000000ffffffff8ULL    //__arm64__
    
    LLDB 操作:po obj ,然后x/4gx LGPerson的地址 或者直接x/4gx,然后取LGPersonisa地址指針 &ISA_MASK,結果如下:
    位運算驗證
  • 方式2. 使用位運算驗證
    shiftcls存儲在當前3~46位,
    向右移動3位,抹除0~2號位數據,
    向左移動20位,移除47~64號位的數據,
    向右移動17位,回到原來的shiftcls所在的存儲位,直接取出,即LGPerson 類信息
    位運算操作截圖
  • 方式3. 通過runtimeobject_getClass來驗證
    main.m導入頭文件#import <objc/runtime.h>,然后直接打印出當前的類也能夠驗證,這里就不打印了,直接去查看object_getClass的實現(xiàn),
    object_getClass方法如下:
Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}

我們進入getIsa方法繼續(xù)查看,

inline Class 
objc_object::getIsa() 
{
    if (fastpath(!isTaggedPointer())) return ISA(); //直接return了當前isa

    extern objc_class OBJC_CLASS_$___NSUnrecognizedTaggedPointer;
    uintptr_t slot, ptr = (uintptr_t)this;
    Class cls;

    slot = (ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
    cls = objc_tag_classes[slot];
    if (slowpath(cls == (Class)&OBJC_CLASS_$___NSUnrecognizedTaggedPointer)) {
        slot = (ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
        cls = objc_tag_ext_classes[slot];
    }
    return cls;
}

繼續(xù)查看ISA()做了哪些事情,

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); //這里與我們方式1做的操作是一樣的
#endif
}

我們可以看出object_getClass的內部實現(xiàn),也是采用了位運算&的方式對當前類進行處理。

驗證總結:驗證isa與類是否關聯(lián)還有其他方式,這里不一一闡述,有興趣的同學可以自己探索。

  • isa結構分析總結:
    1.isa通過isa_t方法進行初始化,其中使用到了聯(lián)合體位域isa關聯(lián)的同時,做了強轉的操作和位運算計算存儲,其中newisa.shiftcls = (uintptr_t)cls >> 3;uintptr_t表示long類型。
    2.x86_64arm64環(huán)境不同,存在部分計算差異
    3.掌握如何對isa的關聯(lián)驗證
    4.如何使用Clang查看源碼。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,117評論 6 537
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,860評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,128評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,291評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,025評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,421評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,477評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,642評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 49,177評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,970評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,157評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,717評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,410評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,821評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,053評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,896評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,157評論 2 375