ios 底层 NSObject内部结构

2,149 阅读3分钟

前言

在上篇文章,我们看到NSObject是由objc_class组成,我objc_class都包含什么信息,我们平常用的属性,方法,协议都在哪里存储呢,今天来学习下

objc_class内部结构

先看下源码:

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    class_rw_t *data() { 
        return bits.data();
    }
    void setData(class_rw_t *newData) {
        bits.setData(newData);
    }
    ...
}

通过源码可以看出,objc_class 包含了isa,父类superclasscache_tclass_data_bits_t,从字面意思,可以看出class_data_bits_t是存储类数据信息的,而且还提供了方法 data() ,下面继续分析,data的返回值class_rw_t,有哪些信息

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;

    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;

    Class firstSubclass;
    Class nextSiblingClass;

    char *demangledName;

从里面信息可以看出,里面包含了 methodspropertiesprotocols,还有一个ro属性,进去class_ro_t看下源码:

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    const uint8_t * ivarLayout;
    
    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;

    method_list_t *baseMethods() const {
        return baseMethodList;
    }
};

这里面也有类的一些信息,那哪个才是真正存储信息的呢? 可以代码验证下

代码验证

先定义一个类,里面有变量,有属性,有类方法,和实例方法

@interface LGPerson : NSObject{
NSString *hobby;
}
@property (nonatomic, copy) NSString *nickName;

- (void)sayHello;
+ (void)sayHappy;
@end
// 定义方法,断点输出
 LGPerson *person = [LGPerson alloc];
    Class pClass = object_getClass(person);
    NSLog(@"%@", pClass);
 }

拿到class_rw_t

打开断点调试,我们先输出pClass的地址信息

(lldb) x/4gx pClass
0x100001200: 0x001d8001000011d9 0x0000000100afd140
0x100001210: 0x00000001003a0270 0x0000000000000000

通过源码可以看出我们想要拿到bits,得先找到地址

isa // 8个字节

Class superclass; // 8个字节

cache_t cache; // 16 个字节

可以看出,bits地址是objc_class的地址上偏移32个字节,此时objc_class地址是0x100001200,偏移32个字节,就是 0x100001220,那我们输出下内容

(lldb) p (class_data_bits_t*)0x100001220
(class_data_bits_t *) $1 = 0x0000000100001220
// 通过方法拿到class_rw_t指针
(lldb) p $1->data()
(class_rw_t *) $2 = 0x000000010183e7d0
// 获取指针里面内容
(lldb) p *$2
(class_rw_t) $3 = {
  flags = 2148139008
  version = 0
  ro = 0x0000000100001178
  methods = {
    list_array_tt<method_t, method_list_t> = {
       = {
        list = 0x00000001000010c8
        arrayAndFlag = 4294971592
      }
    }
  }
  properties = {
    list_array_tt<property_t, property_list_t> = {
       = {
        list = 0x0000000100001160
        arrayAndFlag = 4294971744
      }
    }
  }
  protocols = {
    list_array_tt<unsigned long, protocol_list_t> = {
       = {
        list = 0x0000000000000000
        arrayAndFlag = 0
      }
    }
  }
  firstSubclass = nil
  nextSiblingClass = NSDate
  demangledName = 0x0000000000000000
}

拿到class_rw_t里面的方法列表

可以看出,方法,属性都在这里,我们试着看方法列表:

lldb) p $3.methods
(method_array_t) $8 = {
  list_array_tt<method_t, method_list_t> = {
     = {
      list = 0x00000001000010c8
      arrayAndFlag = 4294971592
    }
  }
}
(lldb) p $8.list
(method_list_t *) $9 = 0x00000001000010c8
(lldb) p *$9
(method_list_t) $10 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 26
    count = 3
    first = {
      name = "nickName"
      types = 0x0000000100000f97 "@16@0:8"
      imp = 0x0000000100000de0 (LGTest`-[LGPerson nickName] at LGPerson.h:16)
    }
  }
}

拿到ro里面的信息

拿到了我们想要的内容,但是我们想找到hobby这个内部属性,发现在这里找不到,刚看源码发现ro里面也有信息,我们继续找

(lldb) p $3.ro
(const class_ro_t *) $4 = 0x0000000100001178
(lldb) p *$4
(const class_ro_t) $5 = {
  flags = 388
  instanceStart = 8
  instanceSize = 24
  reserved = 0
  ivarLayout = 0x0000000100000f59 "\x02"
  name = 0x0000000100000f50 "LGPerson"
  baseMethodList = 0x00000001000010c8
  baseProtocols = 0x0000000000000000
  ivars = 0x0000000100001118
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x0000000100001160
}

看出来,里面除了basePropertiesbaseMethodListbaseProtocols,还有ivars,我们验证下内容

(lldb) p $5.ivars
(const ivar_list_t *const) $16 = 0x0000000100001118
(lldb) p *$16
(const ivar_list_t) $17 = {
  entsize_list_tt<ivar_t, ivar_list_t, 0> = {
    entsizeAndFlags = 32
    count = 2
    first = {
      offset = 0x00000001000011c8
      name = 0x0000000100000f7f "hobby"
      type = 0x0000000100000faa "@\"NSString\""
      alignment_raw = 3
      size = 8
    }
  }
}

找到hobby这个属性变量了,当然方法列表也能在这里找到。所以,我们知道了类的属性,实例方法,都存贮在这里。

类方法存贮位置

我们发现 + (void)sayHappy; 这个还没有找到存贮,我们之前讲过类都有元类,那会不会在元类里存储呢,代码验证下

void testInstanceMethod_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getInstanceMethod(pClass, @selector(sayHello));
    Method method2 = class_getInstanceMethod(metaClass, @selector(sayHello));

    Method method3 = class_getInstanceMethod(pClass, @selector(sayHappy));
    Method method4 = class_getInstanceMethod(metaClass, @selector(sayHappy));
    
    NSLog(@"%p-%p-%p-%p",method1,method2,method3,method4);
    NSLog(@"%s",__func__);
}
void testClassMethod_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getClassMethod(pClass, @selector(sayHello));
    Method method2 = class_getClassMethod(metaClass, @selector(sayHello));

    Method method3 = class_getClassMethod(pClass, @selector(sayHappy));
    Method method4 = class_getClassMethod(metaClass, @selector(sayHappy)); // ?
    
    NSLog(@"%p-%p-%p-%p",method1,method2,method3,method4);
    NSLog(@"%s",__func__);
}

输出内容为:

0x100002248-0x0-0x0-0x1000021e0

0x0-0x0-0x1000021e0-0x1000021e0

可以看出,类方法是存在于元类的里面,我们也可以自己验证下

// 当前类信息
(lldb) x/4gx pClass
0x1000023b0: 0x001d800100002389 0x0000000100afe140
0x1000023c0: 0x00000001003a1290 0x0000000000000000
// 获取元类
(lldb) p 0x001d800100002389&0x00007ffffffffff8
(long) $1 = 4294976392
(lldb) po $1
LGPerson
// 获取元类地址
(lldb) x/4gx $1
0x100002388: 0x001d800100afe0f1 0x0000000100afe0f0
0x100002398: 0x000000010192ac00 0x0000000100000003
// 获取元类class_rw_t
(lldb) p (class_data_bits_t*)0x1000023a8
(class_data_bits_t *) $2 = 0x00000001000023a8
(lldb) p $2->data()
(class_rw_t *) $3 = 0x000000010192ab60
(lldb) p *$3
(class_rw_t) $4 = {
  flags = 2685075456
  version = 7
  ro = 0x00000001000021f8
  methods = {
    list_array_tt<method_t, method_list_t> = {
       = {
        list = 0x00000001000021d8
        arrayAndFlag = 4294975960
      }
    }
  }
  properties = {
    list_array_tt<property_t, property_list_t> = {
       = {
        list = 0x0000000000000000
        arrayAndFlag = 0
      }
    }
  }
  protocols = {
    list_array_tt<unsigned long, protocol_list_t> = {
       = {
        list = 0x0000000000000000
        arrayAndFlag = 0
      }
    }
  }
  firstSubclass = nil
  nextSiblingClass = 0x00007fff95935900
  demangledName = 0x0000000100001f80 "LGPerson"
}
// 获取元类ro
(lldb) p $4.ro
(const class_ro_t *) $5 = 0x00000001000021f8
(lldb) p *S5
error: use of undeclared identifier 'S5'
(lldb) p $5
(const class_ro_t *) $5 = 0x00000001000021f8
(lldb) p *$5
(const class_ro_t) $6 = {
  flags = 389
  instanceStart = 40
  instanceSize = 40
  reserved = 0
  ivarLayout = 0x0000000000000000
  name = 0x0000000100001f80 "LGPerson"
  baseMethodList = 0x00000001000021d8
  baseProtocols = 0x0000000000000000
  ivars = 0x0000000000000000
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x0000000000000000
}
// 获取元类方法列表
(lldb) p $6.baseMethodList
(method_list_t *const) $7 = 0x00000001000021d8
(lldb) p *$7
(method_list_t) $8 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 26
    count = 1
    first = {
      name = "sayHappy"
      types = 0x0000000100001f8b "v16@0:8"
      imp = 0x0000000100001bc0 (LGTest`+[LGPerson sayHappy] at LGPerson.m:17)
    }
  }
}

通过信息可以看出来,类方法确实存在于元类中。

总结下,类的内部结构

屏幕快照 2019-12-23 下午1.57.27.png
屏幕快照 2019-12-23 下午1.57.17.png