iOS底層原理 - 探尋Runtime本質 之 isa

面試題引發的思考:

Q: 簡述isa指針?

  • __arm64__架構開始,isa指針:
  • 不只是存儲了 class對象meta-class對象 的地址;
  • 而是使用 共用體結構 存儲了更多信息:
    其中 shiftcls 存儲了 class對象meta-class對象 的地址;
    需要 shiftclsISA_MASK 進行 按位& 運算取出。

Objective-C 擴展了 C 語言,并加入了面向對象特性和 Smalltalk 式的消息傳遞機制。而這個擴展的核心是一個用 C 和 編譯語言 寫的 Runtime 庫。它是 Objective-C 面向對象和動態機制的基石。

要想學習Runtime,首先要了解它底層的一些常用數據結構,比如isa指針:

iOS底層原理 - OC對象的本質(二)可知:

  • 每個OC對象都有一個isa指針;
  • instance對象的isa指向class對象;
  • class對象的isa指向meta-class對象;
  • OC對象的isa指針并不是直接指向class對象或者meta-class對象,而是需要&ISA_MASK通過位運算才能獲取到類對象或者元類對象的地址;
    a> 在__arm64__架構之前,isa就是一個普通的指針,存儲著Class對象、Meta-Class對象的內存地址;
    b> 從__arm64__架構開始,對isa進行了優化,變成了一個共用體(union)結構,還使用位域來存儲更多的信息。

進入OC源碼查看isa指針,進行更深入的了解:

isa結構

由上圖可知:
isa指針其實是一個isa_t類型的共用體;
此共用體中包含一個結構體;
此結構體內部定義了若干變量,變量后面的值則表示該變量占用的位數,即位域。

接下來一步步分析共用體的優勢。


(1) 探究

// TODO: -----------------  Person類  -----------------
@interface Person : NSObject
// 聲明屬性,系統會自動生成成員變量
@property (nonatomic, assign, getter=isTall) BOOL tall;
@property (nonatomic, assign, getter=isRich) BOOL rich;
@property (nonatomic, assign, getter=isHandsome) BOOL handsome;
@end

@implementation Person
@end

// TODO: -----------------  main  -----------------
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person  = [[Person alloc] init];
        person.tall = YES;
        person.rich = NO;
        person.handsome = YES;
        NSLog(@"%zd", class_getInstanceSize([Person class]));
     }
    return 0;
}

// 打印結果
Demo[1234:567890] 16

由以上代碼可知:
isa指針占8個字節,3個BOOL類型的分別占1個字節,一共11個字節。由于內存對齊原則,所以Person類對象所占內存應該為16個字節。

BOOL值包含01,只需要1個二進制位就可以表示出來,而現在需要占用1個字節共8個二進制位。
可以使用1個字節中的3個二進制位表示3個BOOL值,這樣可以很大程度上節省內存空間。

如果聲明屬性,系統就會自動生成成員變量,占用的內存會大于1位;所以不可以聲明屬性,需要手動實現每個屬性的set方法和get方法。

現在添加一個char類型的成員變量,char變量占用一個字節(8位)的內存空間,使用最后3位來存儲3個BOOL值。

1個char值存儲3個BOOL值

(2) 位運算

  1. 補碼(負數是以補碼的形式表示)
    十進制正整數轉換為二進制數:除2取余即可;
    十進制負整數轉換為二進制數:除2取余,取反加1
// -10用二進制表示
0b0000 0000 0000 1010 -(10除2取余)
0b1111 1111 1111 0101 -(取反)
0b1111 1111 1111 0110 -(加1)
0b1111 1111 1111 0110 -(得出-10的二進制)
  1. 按位與(&)
    同真為真,其余為假:清零特定位、取出特定位;
// 按位與(&)
  0b0000 1111 0000 1111
& 0b0000 0000 1111 1111
  ---------------------
  0b0000 0000 0000 1111
  1. 按位或(|)
    同假為假,其余為真:特定位置1
// 按位或(|)
  0b0000 1111 0000 1111
| 0b0000 0000 1111 1111
  ---------------------
  0b0000 1111 1111 1111
  1. 按位異或(^)
    異值為真,同值為假:特定位取反、交換兩變量的值;
// 按位異或(^)
  0b0000 1111 0000 1111
^ 0b0000 0000 1111 1111
  ---------------------
  0b0000 1111 1111 0000
  1. 取反(~)
    按位取反
// 取反(~)
~ 0b0000 1111 0000 1111
  ---------------------
  0b1111 0000 1111 0000
  1. 左移(<<)
    按位左移,高位丟棄,低位補0
// 左移(<<)兩位
  0b1111 0000 0000 1111 << 2
  ---------------------
  0b1100 0000 0011 1100
  1. 右移(>>)
    按位右移,高位正數補0,負數補1
// 右移(>>)兩位
  0b1111 0000 0000 1111 >> 2
  ---------------------
  0b0011 1100 0000 0011

(3) 手動實現屬性的set方法和get方法

// TODO: -----------------  Person類  -----------------
//#define TallMask 0b00000001 // 1
//#define RichMask 0b00000010 // 2
//#define HandsomeMask 0b0000100 // 4
#define TallMask (1<<0)
#define RichMask (1<<1)
#define HandsomeMask (1<<2)

@interface Person : NSObject {
    char _tallRichHandsome;  // 0b0000 0000
}
- (void)setTall:(BOOL)tall;
- (void)setRich:(BOOL)rich;
- (void)setHandsome:(BOOL)handsome;
- (BOOL)isTall;
- (BOOL)isRich;
- (BOOL)isHandsome;
@end

@implementation Person
- (void)setTall:(BOOL)tall {
    if (tall) { // 按位或 - 特定位置1
        _tallRichHandsome |= TallMask;
    } else { // 掩碼先取反,后按位與 - 特定位置0
        _tallRichHandsome &= ~TallMask;
    }
}
- (void)setRich:(BOOL)rich {
    if (rich) {
        _tallRichHandsome |= RichMask;
    } else {
        _tallRichHandsome &= ~RichMask;
    }
}
- (void)setHandsome:(BOOL)handsome {
    if (handsome) {
        _tallRichHandsome |= HandsomeMask;
    } else {
        _tallRichHandsome &= ~HandsomeMask;
    }
}
- (BOOL)isTall {
    // 按位與 - 取出特定位
    // 兩次取反!,強制轉換成BOOL類型
    return !!(_tallRichHandsome & TallMask);
}
- (BOOL)isRich {
    return !!(_tallRichHandsome & RichMask);
}
- (BOOL)isHandsome {
    return !!(_tallRichHandsome & HandsomeMask);
}
@end

// TODO: -----------------  main  -----------------
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person  = [[Person alloc] init];
        person.tall = YES;
        person.rich = NO;
        person.handsome = YES;
        NSLog(@"tall: %d, rich: %d, handsome: %d", person.isTall, person.isRich, person.isHandsome);
    }
    return 0;
}

// 打印結果
Demo[1234:567890] tall: 1, rich: 0, handsome: 1

上述代碼可以正常存值和取值,但是可拓展性和可讀性差。


(4) 位域

位域聲明:位域名 : 位域長度

使用位域需要注意以下3點:

  • 如果一個字節所剩空間不夠存放另一位域時,應從下一單元起存放該位域;
    也可以有意使某位域從下一單元開始。
  • 位域的長度不能大于數據類型本身的長度;
    比如int類型就不能超過32位二進制位。
  • 位域可以無位域名,這時它只用來作填充或調整位置;
    無名的位域是不能使用的。
// TODO: -----------------  Person類  -----------------
@interface Person : NSObject {
    // 位域,tall、rich、handsome按序各占1個二進制位
    struct {
        char tall : 1;
        char rich : 1;
        char handsome : 1;
    } _tallRichHandsome;
}
- (void)setTall:(BOOL)tall;
- (void)setRich:(BOOL)rich;
- (void)setHandsome:(BOOL)handsome;
- (BOOL)isTall;
- (BOOL)isRich;
- (BOOL)isHandsome;
@end

@implementation Person
- (void)setTall:(BOOL)tall {
    _tallRichHandsome.tall = tall;
}
- (void)setRich:(BOOL)rich {
    _tallRichHandsome.rich = rich;
}
- (void)setHandsome:(BOOL)handsome {
    _tallRichHandsome.handsome = handsome;
}
- (BOOL)isTall {
    // _tallRichHandsome.tall == 0b1
    // 0b1111 1111 == -1
    return !!_tallRichHandsome.tall;
}
- (BOOL)isRich {
    return !!_tallRichHandsome.rich;
}
- (BOOL)isHandsome {
    return !!_tallRichHandsome.handsome;
}
@end

// 打印結果
Demo[1234:567890] tall: 1, rich: 0, handsome: 1

上述代碼使用結構體的位域,不需要再使用掩碼,缺點是效率比使用位運算時低。


(5) 共用體

// TODO: -----------------  Person類  -----------------
#define TallMask (1<<0)
#define RichMask (1<<1)
#define HandsomeMask (1<<2)

@interface Person : NSObject {
    union { // 共用體
        char bits;
        //struct 增加代碼可讀性,可注釋掉
        struct {
            char tall : 1;
            char rich : 1;
            char handsome : 1;
        };
    }_tallRichHandsome;
}
- (void)setTall:(BOOL)tall;
- (void)setRich:(BOOL)rich;
- (void)setHandsome:(BOOL)handsome;
- (BOOL)isTall;
- (BOOL)isRich;
- (BOOL)isHandsome;
@end

@implementation Person
- (void)setTall:(BOOL)tall {
    if (tall) {
        _tallRichHandsome.bits |= TallMask;
    } else {
        _tallRichHandsome.bits &= ~TallMask;
    }
}
- (void)setRich:(BOOL)rich {
    if (rich) {
        _tallRichHandsome.bits |= RichMask;
    } else {
        _tallRichHandsome.bits &= ~RichMask;
    }
}
- (void)setHandsome:(BOOL)handsome {
    if (handsome) {
        _tallRichHandsome.bits |= HandsomeMask;
    } else {
        _tallRichHandsome.bits &= ~HandsomeMask;
    }
}
- (BOOL)isTall {
    // 兩次取反!,強制轉換成BOOL類型
    return !!(_tallRichHandsome.bits & TallMask);
}
- (BOOL)isRich {
    return !!(_tallRichHandsome.bits & RichMask);
}
- (BOOL)isHandsome {
    return !!(_tallRichHandsome.bits & HandsomeMask);
}
@end

// 打印結果
Demo[1234:567890] tall: 1, rich: 0, handsome: 1

上述代碼使用共用體對數據進行位運算取值和賦值,效率高同時占用內存少,代碼可讀性高。


(6) isa詳解

經過以上分析,我們可以清晰地了解到位運算、位域以及共用體的相關知識。
下面我們深入分析isa的結構:

isa結構

由上圖可知:
isa_t共用體存儲了8個字節64位的值,所有信息都存儲在bits中,這些值在結構體中展現出來,這些值通過對bits進行掩碼位運算取出。

名稱 作用
nonpointer 0代表普通的指針,存儲著Class,Meta-Class對象的內存地址;1代表優化過,使用位域存儲更多的信息
has_assoc 是否有設置過關聯對象,如果沒有,釋放時會更快
has_cxx_dtor 是否有C++析構函數,如果沒有,釋放時會更快
shiftcls 存儲著Class、Meta-Class對象的內存地址信息
magic 用于在調試時分辨對象是否未完成初始化
weakly_referenced 是否有被弱引用指向過,如果沒有,釋放時會更快
deallocating 對象是否正在釋放
has_sidetable_rc 引用計數器是否過大無法存儲在isa中;如果為1,那么引用計數會存儲在一個叫SideTable的類的屬性中
extra_rc 里面存儲的值是引用計數器減1

重點介紹一下shiftcls

  • shiftcls存儲著Class、Meta-Class對象的內存地址信息;
  • 通過bits & ISA_MASK取得shiftcls的值;
  • 而掩碼ISA_MASK值為0x0000000ffffffff8ULL,說明Class、Meta-Class對象的內存地址值后三位一定為0

接下來驗證一下:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person  = [[Person alloc] init];
        person.tall = YES;
        person.rich = NO;
        person.handsome = YES;
        NSLog(@"tall: %d, rich: %d, handsome: %d", person.isTall, person.isRich, person.isHandsome);
        NSLog(@"class: %p, meta-class: %p", [Person class], object_getClass([Person class]));
    }
    return 0;
}
// 打印結果
Demo[1234:567890] tall: 1, rich: 0, handsome: 1
Demo[1234:567890] class: 0x100001260, meta-class: 0x100001238

由打印結果可知:
class對象的地址值為0x100001260,尾數是0;meta-class對象的地址值為0x100001238,尾數是8;在16進制下,內存地址的后三位都為0;驗證結束。


總結可知:

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

推薦閱讀更多精彩內容