iOS OC底层中类的结构探索

引言

我们都知道,一个类有成员变量、对象方法、类方法,那么它们在底层是如何实现的呢?

前提

要想研究上面内容,首先要知道oc对象在c++层面对应的内容,新创建一个工程,里面创建一个Dog类,编译main.m文件,就会发现与main.m同级的目录下多了一个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搜索关键字dogName,可以看出Dog类是一个结构体Dog_IMPL,我们继续看NSObject_IMPL,发现也是一个结构体,里面只有isa指针,并没有我们定义的成员变量和方法。

image.png

image.png

NSObjectruntime中对应的源码为

image.png

类对象的源码为

image.png

接下来引出我们的重点,我们可以看出objc_class里最主要的三个成员变量。

 Class superclass;       //继承 objc_object 的ISA    8字节
 cache_t cache;             // formerly cache pointer and vtable   //16字节,下面有详解
 class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

其余的全是方法不用考虑,因为方法的存储是在别的地方。

首先,我们来研究class_data_bits_t bits,通过首地址+指针偏移量来获取到bits

struct cache_t {
   private:
   explicit_atomic<uintptr_t> _bucketsAndMaybeMask;  //8个字节
union {
    struct {
        explicit_atomic<mask_t>    _maybeMask;  //4字节
   #if __LP64__
        uint16_t                   _flags;                //2字节
   #endif
        uint16_t                   _occupied;      //2字节
    }; 
    explicit_atomic<preopt_cache_t *> _originalPreoptCache;   //8个字节
};
 ...省略的是一些static变量(全局区)和方法,没有影响。
}

由于联合体union共用内存,所以union8个字节,所以cache_t为 8 (uintptr_t)+ 8(union)=16字节。
结论:想获取bits的值,就是首地址偏移 8+8+16 = 32个字节。
创建一个类: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

那么,成员变量方法到底存储在哪里呢?
经过源码分析,我们发现class_data_bits_t里面有个data方法,返回的class_rw_t结构体类型。

 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};
    }
}

  //存储成员变量,比如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_tprotocol_array_t里面会不会存储着我们定义的方法成员变量呢?

  • property_array_t:

     (lldb) x/4gx WJPerson.class
     0x100004470: 0x0000000100004498 0x0000000100354140
     0x100004480: 0x000000010034b360 0x0000802000000000
    (lldb) p/x 0x100004470+0x20  //首地址(第一个地址)+ 偏移量32字节(16进制即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结构体
    (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 = {}//这个地方直接get()打印不出来我们想要的结果了,下面分析
      (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,同时还有nameheightset、get方法,但是我们并没有看到+ (void)eatFood,为什么,+ (void)eatFood到底在哪里,最后探究。

补充: method_tproperty_t调用get()分析

struct property_t {
      const char *name;

      const char *attributes; //这个里面有name和attributes可以输出相关的信息

  };
 struct method_t {  //这个里面什么都没有,但是里面有一个big结构体,它的里面有信息。可以通过big()获取

 struct big {

     SEL name;

     const char *types;

     MethodListIMP imp;

 };
 big &big() const {

 ASSERT(!isSmall());

  return *(struct big *)this;

 }

-vars(成员变量)存储

(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,同时还有_name_height
由上面也可得出一个结论:property =ivar + get + set

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,001评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,210评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,874评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,001评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,022评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,005评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,929评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,742评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,193评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,427评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,583评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,305评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,911评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,564评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,731评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,581评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,478评论 2 352

推荐阅读更多精彩内容