iOS OC底層中類的結(jié)構(gòu)探索

引言

我們都知道争舞,一個(gè)類有成員變量凛忿、對(duì)象方法、類方法,那么它們?cè)诘讓邮侨绾螌?shí)現(xiàn)的呢竞川?

前提

要想研究上面內(nèi)容店溢,首先要知道oc對(duì)象在c++層面對(duì)應(yīng)的內(nèi)容叁熔,新創(chuàng)建一個(gè)工程,里面創(chuàng)建一個(gè)Dog類床牧,編譯main.m文件荣回,就會(huì)發(fā)現(xiàn)與main.m同級(jí)的目錄下多了一個(gè)cpp文件。

clang main.m.gif

#import <Foundation/Foundation.h>
@interface Dog : NSObject
@property(nonatomic,copy)NSString*dogName;
-(void)eatFood;
-(void)playWithWhom:(NSString*)name;
@end


#import "Dog.h"
@implementation Dog
-(void)eatFood
{
 NSLog(@"eatFood");
}

-(void)playWithWhom:(NSString*)name
{
 NSLog(@"--%@",name);
}
@end

打開cpp搜索關(guān)鍵字dogName戈咳,可以看出Dog類是一個(gè)結(jié)構(gòu)體Dog_IMPL驹马,我們繼續(xù)看NSObject_IMPL,發(fā)現(xiàn)也是一個(gè)結(jié)構(gòu)體除秀,里面只有isa指針活翩,并沒有我們定義的成員變量和方法挂脑。

image.png

image.png

NSObjectruntime中對(duì)應(yīng)的源碼為

image.png

類對(duì)象的源碼為

image.png

接下來引出我們的重點(diǎn)呐萌,我們可以看出objc_class里最主要的三個(gè)成員變量骑篙。

 Class superclass;       //繼承 objc_object 的ISA    8字節(jié)
 cache_t cache;             // formerly cache pointer and vtable   //16字節(jié)崩瓤,下面有詳解
 class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

其余的全是方法不用考慮俊戳,因?yàn)榉椒ǖ拇鎯?chǔ)是在別的地方陆蟆。

首先寇窑,我們來研究class_data_bits_t bits缎患,通過首地址+指針偏移量來獲取到bits

struct cache_t {
   private:
   explicit_atomic<uintptr_t> _bucketsAndMaybeMask;  //8個(gè)字節(jié)
union {
    struct {
        explicit_atomic<mask_t>    _maybeMask;  //4字節(jié)
   #if __LP64__
        uint16_t                   _flags;                //2字節(jié)
   #endif
        uint16_t                   _occupied;      //2字節(jié)
    }; 
    explicit_atomic<preopt_cache_t *> _originalPreoptCache;   //8個(gè)字節(jié)
};
 ...省略的是一些static變量(全局區(qū))和方法慕的,沒有影響。
}

由于聯(lián)合體union共用內(nèi)存挤渔,所以union8個(gè)字節(jié)肮街,所以cache_t為 8 (uintptr_t)+ 8(union)=16字節(jié)。
結(jié)論:想獲取bits的值判导,就是首地址偏移 8+8+16 = 32個(gè)字節(jié)嫉父。
創(chuàng)建一個(gè)類:WJPerson

  #import <Foundation/Foundation.h>

  NS_ASSUME_NONNULL_BEGIN

 @interface WJPerson : NSObject{
     int age;
  }
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) float height;

- (void)playGuitar;
+ (void)eatFood;

// 1: 類的探究分析
// 2: 類方法 + ivar 在哪里

@end

 NS_ASSUME_NONNULL_END


  #import "WJPerson.h"

  @implementation WJPerson

  - (instancetype)init{
     if (self = [super init]) {
          self.name = @"wuji";
       }
   return self;
 }

- (void)playGuitar{

 }
+ (void)eatFood{

 }

 @end

那么,成員變量方法到底存儲(chǔ)在哪里呢眼刃?
經(jīng)過源碼分析绕辖,我們發(fā)現(xiàn)class_data_bits_t里面有個(gè)data方法,返回的class_rw_t結(jié)構(gòu)體類型擂红。

 class_rw_t* data() const {
    return (class_rw_t *)(bits & FAST_DATA_MASK);
}

那么class_rw_t里面有什么呢仪际?

 struct class_rw_t {

  const method_array_t methods() const {
       auto v = get_ro_or_rwe();
      if (v.is<class_rw_ext_t *>()) {
        return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->methods;
    } else {
        return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseMethods()};
    }
}

const property_array_t properties() const {
    auto v = get_ro_or_rwe();
    if (v.is<class_rw_ext_t *>()) {
        return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->properties;
    } else {
        return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProperties};
    }
}

const protocol_array_t protocols() const {
    auto v = get_ro_or_rwe();
    if (v.is<class_rw_ext_t *>()) {
        return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->protocols;
    } else {
        return protocol_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProtocols};
    }
}

  //存儲(chǔ)成員變量,比如age
const class_ro_t *ro() const {
    auto v = get_ro_or_rwe();
    if (slowpath(v.is<class_rw_ext_t *>())) {
        return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro;
    }
    return v.get<const class_ro_t *>(&ro_or_rw_ext);
}
   ...//里面省略的包括一些copy之類的方法昵骤,這里不做研究
};

那么树碱,method_array_tproperty_array_t变秦、protocol_array_t里面會(huì)不會(huì)存儲(chǔ)著我們定義的方法成員變量呢赴恨?

  • property_array_t:

     (lldb) x/4gx WJPerson.class
     0x100004470: 0x0000000100004498 0x0000000100354140
     0x100004480: 0x000000010034b360 0x0000802000000000
    (lldb) p/x 0x100004470+0x20  //首地址(第一個(gè)地址)+ 偏移量32字節(jié)(16進(jìn)制即0x20)
     (long) $5 = 0x0000000100004490
     (lldb) p (class_data_bits_t*)0x0000000100004490
     (class_data_bits_t *) $6 = 0x0000000100004490
    
     (lldb) p $6->data()
     (class_rw_t *) $9 = 0x0000000100706010  //獲取class_rw_t結(jié)構(gòu)體
    (lldb)  p $9->properties()   //獲取properties()
    (const property_array_t) $11 = {
    list_array_tt<property_t, property_list_t, RawPtr> = {
     = {
         list = {
            ptr = 0x0000000100004348
              }
         arrayAndFlag = 4294984520
       }
     }
    }
     (lldb) p $11.list
     (const RawPtr<property_list_t>) $12 = {
      ptr = 0x0000000100004348
     }
      (lldb) p $12.ptr
      (property_list_t *const) $13 = 0x0000000100004348
      (lldb) p   *$13
      (property_list_t) $14 = {
       entsize_list_tt<property_t, property_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 16, count = 2)
    }
     (lldb) p $14.get(0)
     (property_t) $15 = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
     (lldb) p $14.get(1)
     (property_t) $16 = (name = "height", attributes = "Tf,N,V_height")
     (lldb) p $14.get(2)
      Assertion failed: (i < count), function get, file
    

    至此,我們獲取到了屬性nameheight,但是我們并沒有看到成員變量age伴栓,為什么伦连,age到底在哪里雨饺,這里放到最后探究。

  • method_array_t:

    (lldb) p $9->methods()
    (const method_array_t) $17 = {
    list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
           = {
             list = {
               ptr = 0x0000000100004248
            }
         arrayAndFlag = 4294984264
        }
      }
    }
     (lldb) p $17.list.ptr
     (method_list_t *const) $18 = 0x0000000100004248
     (lldb) p *$18
      (method_list_t) $19 = {
       entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> =             (entsizeAndFlags = 27, count = 6)
      }
      (lldb) p $19.get(0)
      (method_t) $20 = {}//這個(gè)地方直接get()打印不出來我們想要的結(jié)果了惑淳,下面分析
      (lldb) p $19.get(0).big()
      (method_t::big) $21 = {
        name = "playGuitar"
        types = 0x0000000100003f72 "v16@0:8"
        imp = 0x0000000100003d20 (KCObjcBuild`-[WJPerson playGuitar])
    }
    
    (lldb) p $19.get(1).big()
     (method_t::big) $22 = {
      name = "init"
     types = 0x0000000100003f6a "@16@0:8"
     imp = 0x0000000100003cc0 (KCObjcBuild`-[WJPerson init])
    }
    (lldb) p $19.get(2).big()
    (method_t::big) $23 = {
       name = "name"
       types = 0x0000000100003f6a "@16@0:8"
      imp = 0x0000000100003d30 (KCObjcBuild`-[WJPerson name])
    }
    (lldb) p $19.get(3).big()
    (method_t::big) $24 = {
       name = "height"
       types = 0x0000000100003f89 "f16@0:8"
      imp = 0x0000000100003d90 (KCObjcBuild`-[WJPerson height])
    }
    (lldb) p $19.get(4).big()
    (method_t::big) $25 = {
        name = "setName:"
        types = 0x0000000100003f7a "v24@0:8@16"
        imp = 0x0000000100003d60 (KCObjcBuild`-[WJPerson setName:])
        }
    (lldb) p $19.get(5).big()
    (method_t::big) $26 = {
        name = "setHeight:"
        types = 0x0000000100003f91 "v20@0:8f16"
        imp = 0x0000000100003db0 (KCObjcBuild`-[WJPerson setHeight:])
    }
    (lldb) p $19.get(6).big()
    Assertion failed: (i < count), function get, file
    

至此额港,我們獲取到了- (void)playGuitar,同時(shí)還有nameheightset歧焦、get方法,但是我們并沒有看到+ (void)eatFood移斩,為什么,+ (void)eatFood到底在哪里绢馍,最后探究向瓷。

補(bǔ)充: method_tproperty_t調(diào)用get()分析

struct property_t {
      const char *name;

      const char *attributes; //這個(gè)里面有name和attributes可以輸出相關(guān)的信息

  };
 struct method_t {  //這個(gè)里面什么都沒有,但是里面有一個(gè)big結(jié)構(gòu)體舰涌,它的里面有信息猖任。可以通過big()獲取

 struct big {

     SEL name;

     const char *types;

     MethodListIMP imp;

 };
 big &big() const {

 ASSERT(!isSmall());

  return *(struct big *)this;

 }

-vars(成員變量)存儲(chǔ)

(lldb) p $9->ro()
(const class_ro_t *) $34 = 0x0000000100004200
(lldb) p *$34
(const class_ro_t) $35 = {
flags = 0
instanceStart = 8
instanceSize = 24
reserved = 0
  = {
   ivarLayout = 0x0000000000000000
    nonMetaclass = nil
 }
name = {
std::__1::atomic<const char *> = "WJPerson" {
  Value = 0x0000000100003ee4 "WJPerson"
   }
 }
 baseMethodList = 0x0000000100004248
 baseProtocols = 0x0000000000000000
ivars = 0x00000001000042e0
weakIvarLayout = 0x0000000000000000
baseProperties = 0x0000000100004348
_swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $35.ivars
(const ivar_list_t *const) $36 = 0x00000001000042e0
(lldb) p *$36
(const ivar_list_t) $37 = {
   entsize_list_tt<ivar_t, ivar_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 32, count = 3)
}
 (lldb) p $37.get(0)
 (ivar_t) $38 = {
 offset = 0x0000000100004408
 name = 0x0000000100003e95 "age"  //這里輸出了age成員變量
 type = 0x0000000100003f85 "i"
  alignment_raw = 2
 size = 4
 }
(lldb) p $37.get(1)
(ivar_t) $39 = {
 offset = 0x0000000100004410
 name = 0x0000000100003e99 "_height"
 type = 0x0000000100003f87 "f"
 alignment_raw = 2
 size = 4
}
(lldb) p $37.get(2)
(ivar_t) $40 = {
 offset = 0x0000000100004418
 name = 0x0000000100003ea1 "_name"
 type = 0x0000000100003f5e "@\"NSString\""
alignment_raw = 3
size = 8
}
(lldb) p $37.get(3)
Assertion failed: (i < count), function get, file

至此瓷耙,我們從ivars獲取到了age朱躺,同時(shí)還有_name_height
由上面也可得出一個(gè)結(jié)論:property =ivar + get + set

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末搁痛,一起剝皮案震驚了整個(gè)濱河市长搀,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌鸡典,老刑警劉巖源请,帶你破解...
    沈念sama閱讀 210,914評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異彻况,居然都是意外死亡巢钓,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評(píng)論 2 383
  • 文/潘曉璐 我一進(jìn)店門疗垛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來症汹,“玉大人,你說我怎么就攤上這事贷腕”痴颍” “怎么了?”我有些...
    開封第一講書人閱讀 156,531評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵泽裳,是天一觀的道長(zhǎng)瞒斩。 經(jīng)常有香客問我,道長(zhǎng)涮总,這世上最難降的妖魔是什么胸囱? 我笑而不...
    開封第一講書人閱讀 56,309評(píng)論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮瀑梗,結(jié)果婚禮上烹笔,老公的妹妹穿的比我還像新娘裳扯。我一直安慰自己,他們只是感情好谤职,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,381評(píng)論 5 384
  • 文/花漫 我一把揭開白布饰豺。 她就那樣靜靜地躺著,像睡著了一般允蜈。 火紅的嫁衣襯著肌膚如雪冤吨。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,730評(píng)論 1 289
  • 那天饶套,我揣著相機(jī)與錄音漩蟆,去河邊找鬼。 笑死妓蛮,一個(gè)胖子當(dāng)著我的面吹牛怠李,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播仔引,決...
    沈念sama閱讀 38,882評(píng)論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼褐奥!你這毒婦竟也來了咖耘?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,643評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤撬码,失蹤者是張志新(化名)和其女友劉穎儿倒,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體呜笑,經(jīng)...
    沈念sama閱讀 44,095評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡夫否,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,448評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了叫胁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片凰慈。...
    茶點(diǎn)故事閱讀 38,566評(píng)論 1 339
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖驼鹅,靈堂內(nèi)的尸體忽然破棺而出微谓,到底是詐尸還是另有隱情,我是刑警寧澤输钩,帶...
    沈念sama閱讀 34,253評(píng)論 4 328
  • 正文 年R本政府宣布豺型,位于F島的核電站,受9級(jí)特大地震影響买乃,放射性物質(zhì)發(fā)生泄漏姻氨。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,829評(píng)論 3 312
  • 文/蒙蒙 一剪验、第九天 我趴在偏房一處隱蔽的房頂上張望肴焊。 院中可真熱鬧前联,春花似錦、人聲如沸抖韩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽茂浮。三九已至双谆,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間席揽,已是汗流浹背顽馋。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評(píng)論 1 264
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留幌羞,地道東北人寸谜。 一個(gè)月前我還...
    沈念sama閱讀 46,248評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像属桦,于是被迫代替她去往敵國(guó)和親熊痴。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,440評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容