isa與cls的關(guān)聯(lián)探索

聯(lián)合體

聯(lián)合體(共用體):一種特殊的數(shù)據(jù)類型像捶,允許在相同的內(nèi)存位置存儲(chǔ)不同的數(shù)據(jù)類型上陕。

  • 所有成員占用同一段內(nèi)存,修改一個(gè)成員會(huì)影響其余所有成員拓春。
    共用體使用了內(nèi)存覆蓋技術(shù)释簿,同一時(shí)刻只能保存一個(gè)成員的值,如果對(duì)新的成員賦值硼莽,就會(huì)把原來成員的值覆蓋掉庶溶。
  • 各變量是互斥的——缺點(diǎn)就是不夠包容; 但優(yōu)點(diǎn)是內(nèi)存使用更為精細(xì)靈活,也節(jié)省了內(nèi)存空間懂鸵。
  • 共用體占用的內(nèi)存等于最長(zhǎng)的成員占用的內(nèi)存偏螺。

Clang

  • Clang是一個(gè)由Apple主導(dǎo)編寫,基于LLVMC/C++/Objective-C編譯器
  • 作用:借助Clang可以將oc文件輸出成C++文件匆光,方便探究其底層的一些結(jié)構(gòu)套像、邏輯、底層的實(shí)現(xiàn)原理等终息。

簡(jiǎn)單的使用命令

//把目標(biāo)文件編譯成c++文件
clang -rewrite-objc main.m -o main.cpp 

// UIKit報(bào)錯(cuò)問題
clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0.0 -isysroot / Applications/Xcode.app/Contents/Developer/Platforms/ iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.0.sdk main.m

`xcode`安裝的時(shí)候順帶安裝了`xcrun`命令夺巩,`xcrun`命令在`clang`的基礎(chǔ)上進(jìn)行了 一些封裝,要更好用一些
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 (手機(jī))

接下來借助Clang來簡(jiǎn)單的探索一下OC的對(duì)象采幌。

oc對(duì)象探索

  • 準(zhǔn)備oc對(duì)象LGPerson,并添加屬性name劲够。此時(shí)LGPeson應(yīng)該有nameisa兩個(gè)成員。
#import <Foundation/Foundation.h>
@interface LGPerson : NSObject

@property (copy, nonatomic) NSString *name;

@end

@implementation LGPerson
@end

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

  • 轉(zhuǎn)化輸出C++文件,找到LGPerson對(duì)象
#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 (copy, nonatomic) 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
  • 從輸出的C++文件可以看到LGPersonobjc_object結(jié)構(gòu)體休傍。

可以看到定義的屬性_name,但是沒有發(fā)現(xiàn)isa蹲姐。反而有一個(gè)struct NSObject_IMPL NSObject_IVARS;磨取。
c++文件中搜索NSObject_IMPL {會(huì)發(fā)現(xiàn)缺少的isa在里面。但是卻變成了Class類型柴墩。

struct NSObject_IMPL {
    Class isa;
};

我們?cè)谔剿?a href="http://www.reibang.com/p/ee56f6d3d4a9" target="_blank">alloc &init時(shí)忙厌,有initInstanceIsa是將isacls相關(guān)聯(lián)。那么應(yīng)該是在關(guān)聯(lián)這里做什么江咳。下面進(jìn)入源碼探索一下關(guān)聯(lián)的操作逢净。

isa關(guān)聯(lián)cls

  • isacls的源碼關(guān)聯(lián)操作
if (!zone && fast) {
        obj->initInstanceIsa(cls, hasCxxDtor);
 } else {
        // Use raw pointer isa on the assumption that they might be
        // doing something weird with the zone or RR.
        obj->initIsa(cls);
 }
關(guān)聯(lián)操作

可以看到isacls的關(guān)聯(lián),其實(shí)就是將cls向右移動(dòng)了3位,賦值保存在isashiftcls的區(qū)域中爹土。因此LGPerson其實(shí)就保存在isa甥雕。

那么通過object_getClass獲取Class時(shí),源碼底層肯定是從isa中通過某種操作取出我們需要的Class胀茵。
進(jìn)入object_getClass源碼

object_getClass

可以發(fā)現(xiàn)object_getClass方法就是獲取isa社露。

isa

最終發(fā)現(xiàn)是isabits成員或者&ISA_MASK的結(jié)果在轉(zhuǎn)成Class返回

isa是如何存儲(chǔ)cls

  • isa類型是isa_t是一個(gè)聯(lián)合體(union)
union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }
    //提供了cls 和 bits 琼娘,兩者是互斥關(guān)系
    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};

從isa_t的定義有Class cls和一個(gè)uintptr_t bits互斥的成員及結(jié)構(gòu)體中宏定義的位域ISA_BITFIELD

ISA_BITFIELD

  • nonpointer:表示是否對(duì)isa指針開啟指針優(yōu)化
    0:純isa指針
    1:不止是類對(duì)象地址,isa中包含了類信息峭弟、對(duì)象的引用計(jì)數(shù)
  • has_assoc:關(guān)聯(lián)對(duì)象標(biāo)志位,0沒有脱拼,1存在
  • has_cxx_dtor:該對(duì)象是否有C++或者Objc析構(gòu)器,如果有析構(gòu)函數(shù),則需要做析構(gòu)邏輯, 如果沒有,則可以更快的釋放對(duì)象瞒瘸。
  • shiftcls:存儲(chǔ)類指針的值。開啟指針優(yōu)化的情況下熄浓,在arm64架構(gòu)中有33位用來存儲(chǔ)類指針情臭。
  • magic:用于調(diào)試器判斷當(dāng)前對(duì)象是真的對(duì)象還是沒有初始化的空間
  • weakly_referenced:對(duì)象是否被指向或者曾經(jīng)指向一個(gè)ARC的弱變量,沒有弱引用的對(duì)象可以更快釋放
  • deallocating:標(biāo)志對(duì)象是否正在釋放內(nèi)存
  • has_sidetable_rc:當(dāng)對(duì)象引用技術(shù)大于10 時(shí)玉组,則需要借用該變量存儲(chǔ)進(jìn)位
  • extra_rc:當(dāng)表示該對(duì)象的引用計(jì)數(shù)值谎柄,實(shí)際上是引用計(jì)數(shù)值減 1.
    例如,如果對(duì)象的引用計(jì)數(shù)為 10惯雳,那么extra_rc為 9朝巫。如果引用計(jì)數(shù)大于 10, 則需要使用到下面的has_sidetable_rc

arm64ISA_BITFIELD位域的存儲(chǔ)的信息

arm64下ISA_BITFIELD位域的存儲(chǔ)

位域中shiftcls位置的33存儲(chǔ)類的指針的值石景。那么取出isa存儲(chǔ)的位域中的shiftcls的值劈猿,應(yīng)該是我們的存儲(chǔ)的Class

x86_64下存儲(chǔ)cls的流程

  • 斷點(diǎn)clsLGPerson時(shí)進(jìn)入isa源碼
    isa源碼

    斷點(diǎn)newisa的初始化isa_t newisa(0);賦初值的操作newisa.bits = ISA_MAGIC_VALUE;分別輸出:
    isa初始化潮孽、賦初值

從結(jié)果可以看到cls和bits賦值后揪荣,位域中存儲(chǔ)的信息,也發(fā)生了變化往史。nonpointer代表對(duì)isa開啟指針優(yōu)化仗颈。magic值成了59。因?yàn)檫@里只是進(jìn)行了賦初值那么這59肯定來自初始值ISA_MAGIC_VALUE椎例。

x86_64結(jié)構(gòu)圖中看magic是從47位開始的占6位挨决。將初始值ISA_MAGIC_VALUE輸出二進(jìn)制進(jìn)行查看《┩幔看到從magic位置的二進(jìn)制111011是十進(jìn)制的59脖祈。

FB82AADE-0403-4F57-9575-7D5E53022FEF.png

  • cls的賦值newisa.shiftcls = (uintptr_t)cls >> 3;
    cls轉(zhuǎn)化成uintptr_t右移3位存儲(chǔ)在shiftcls區(qū)域。
    存儲(chǔ)cls

了解了cls的存儲(chǔ)的位置及操作刷晋。那么通過存儲(chǔ)的逆向操作應(yīng)該能從isa中取出LGPerson盖高。

  • isa中獲取LGPerson
isa獲取cls

移動(dòng)操作

通過反向?qū)?code>isa的反向操作確實(shí)獲得了LGPerson慎陵。
但是通過object_getClass獲取Class時(shí)是isa.bits& ISA_MASK操作。

return (Class)(isa.bits & ISA_MASK);

ISA_MASK = 0x00007ffffffffff8ULL其實(shí)就是上面的移動(dòng)抹0操作的位與運(yùn)算喻奥。遮住不需要的信息席纽,保留需要的信息。

位與運(yùn)算

通過isa的與cls的關(guān)聯(lián)存儲(chǔ)及object_getClass獲取源碼也就可以理解c++文件中isaClass類型了映凳。

 struct NSObject_IMPL {
    Class isa;
};
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末胆筒,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子诈豌,更是在濱河造成了極大的恐慌仆救,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件矫渔,死亡現(xiàn)場(chǎng)離奇詭異彤蔽,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)庙洼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門顿痪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人油够,你說我怎么就攤上這事蚁袭。” “怎么了石咬?”我有些...
    開封第一講書人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵揩悄,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我鬼悠,道長(zhǎng)删性,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任焕窝,我火速辦了婚禮蹬挺,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘它掂。我一直安慰自己巴帮,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開白布虐秋。 她就那樣靜靜地躺著晰韵,像睡著了一般。 火紅的嫁衣襯著肌膚如雪熟妓。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,165評(píng)論 1 299
  • 那天栏尚,我揣著相機(jī)與錄音起愈,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛抬虽,可吹牛的內(nèi)容都是我干的官觅。 我是一名探鬼主播,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼阐污,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼休涤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起笛辟,我...
    開封第一講書人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤功氨,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后手幢,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體捷凄,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年围来,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了跺涤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡监透,死狀恐怖桶错,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情胀蛮,我是刑警寧澤院刁,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站醇滥,受9級(jí)特大地震影響黎比,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜鸳玩,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一阅虫、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧不跟,春花似錦颓帝、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至虐译,卻和暖如春瘪板,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背漆诽。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工侮攀, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留锣枝,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓兰英,卻偏偏與公主長(zhǎng)得像撇叁,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子畦贸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353