类的结构分析(二)

1,478 阅读5分钟

一、类的内存的ro数据

上篇文章,我们可以通过lldb看到类结构里的属性和实例方法,但是没看实例变量和类方法,接下来继续探究

@interface ApplePerson : NSObject{
    NSString *salary;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *workNumber;

- (void)sayHenXi;
+ (void)sayNiuBi;
@end

我们翻阅源码查找实例变量ivars(instance variables),在结构体class_ro_t里面

struct class_ro_t {
    …
     void *baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;
    …
}

我们翻阅苹果开发者视频,也验证了这个想法

image.png

再次查看源码,发现有返回class_ro_t结构的方法

const class_ro_t *ro() const {
    ...
}

二、实例变量和属性以及编码

接下来测试一下,打印内存

(lldb) x/6gx ApplePerson.class
0x100008498: 0x0000000100008470 0x0000000100379140
0x1000084a8: 0x000000010036d0e0 0x0000803000000000
0x1000084b8: 0x00000001007a3024 0x00000001000084e8

平移32个字节

(lldb) p/x 0x100008498 + 0x20
(long) $1 = 0x00000001000084b8

强转打印

(lldb) p (class_data_bits_t *)0x00000001000084b8
(class_data_bits_t *) $2 = 0x00000001000084b8

获取数据

p $2->data()
(class_rw_t *) $3 = 0x00000001007a3020

获取ro

(lldb) p *$3.ro()
(const class_ro_t) $5 = {
  ...
  ivars = 0x00000001000080d0
  ...
}

可以看到有ivars,继续获取

(lldb) p $5.ivars
(const ivar_list_t *const) $6 = 0x00000001000080d0

打印$6

(lldb) p *$6
(const ivar_list_t) $7 = {
  entsize_list_tt<ivar_t, ivar_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 32, count = 4)
}

可以看到$7是一个list类型,所以用get获取数据

(lldb) p $7.get(0)
(ivar_t) $8 = {
  offset = 0x0000000100008450
  name = 0x0000000100003e90 "salary"
  type = 0x0000000100003f4c "@\"NSString\""
  alignment_raw = 3
  size = 8
}
(lldb) p $7.get(1)
(ivar_t) $9 = {
  offset = 0x0000000100008454
  name = 0x0000000100003e97 "age"
  type = 0x0000000100003f58 "q"
  alignment_raw = 3
  size = 8
}
(lldb) p $7.get(2)
(ivar_t) $10 = {
  offset = 0x0000000100008458
  name = 0x0000000100003e9b "_name"
  type = 0x0000000100003f4c "@\"NSString\""
  alignment_raw = 3
  size = 8
}
(lldb) p $7.get(3)
(ivar_t) $11 = {
  offset = 0x000000010000845c
  name = 0x0000000100003ea1 "_workNumber"
  type = 0x0000000100003f4c "@\"NSString\""
  alignment_raw = 3
  size = 8
}
(lldb) p $7.get(4)
Assertion failed: (i < count), function get, file /Users/.../runtime/objc-runtime-new.h, line 624.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.

可以获取到salaryage_name_workNumber四个ivars,对比类的结构

@interface ApplePerson : NSObject{
    NSString *salary;
    NSInteger age;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *workNumber;

可以发现实例变量在ivars里面是原封不动的存在,而属性也存在ivars里面,但是加了下划线_。同时,可以ivars里面看到NSString@\"NSString\"类型 size = 8,是8字节,而NSInteger"q"类型,size = 8,说明NSInteger64位系统下也是8字节

三、查在C++代码底层的实现

由于源码代码太多,查看不方便,新建一个空的命令行工程

#import <Foundation/Foundation.h>

@interface ApplePerson : NSObject{
    NSString *salary;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *workNumber;

- (void)sayHenXi;
+ (void)sayNiuBi;
@end

@implementation ApplePerson

- (void)sayHenXi{
}
+ (void)sayNiuBi{
    
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
    }
    return 0;
}

clang一下 clang -rewrite-objc main.m -o niubi.cpp,会得到cpp文件,搜索ApplePerson

image.png

发现自己定义的属性和方法在底层以及被屏蔽,属性nameworkNumber在结构体ApplePerson_IMPL里面生成带下划线_的实例变量,并且带有set/get方法 image.png 还可以看到方法列表

image.png

里面有v16@0:8v24@0:8@16属于OC的类型编码

Objective-C Runtime Programming Guide

CodeMeaning
cchar
iAn int
sshort
llong``l is treated as a 32-bit quantity on 64-bit programs.
qlong long
CAn unsigned char
IAn unsigned int
SAn unsigned short
LAn unsigned long
QAn unsigned long long
ffloat
ddouble
BA C++ bool or a C99 _Bool
vvoid
*A character string (char *)
@An object (whether statically typed or typed id)
#A class object (Class)
:A method selector (SEL)
[array type]An array
{name=type... }A structure
(name=type... )A union
bnumA bit field of num bits
^typeA pointer to type
?An unknown type (among other things, this code is used for function pointers)

v16@0:8v代表void16代表参数占用内存大小;@代表一个对象(无论是静态类型id还是类型化id)其实就是 id self0代表从0号位置开始;方法选择器(SEL);8代表从8号位置开始,id self是结构体指针类型8字节,SEL8字节,刚好16字节

四、setter方法的底层实现


接下来我们研究属性对于底层代码的影响

@interface ApplePerson : NSObject{
    NSString *salary;
}
//@property (nonatomic, copy) NSString *name;
//@property (nonatomic, copy) NSString *workNumber;

@property (nonatomic, copy) NSString *nc_job_title;
@property (atomic, copy)    NSString *ac_job_title;
@property (nonatomic)       NSString *n_job_title;
@property (atomic)          NSString *a_job_title;

clang一下


static void _I_ApplePerson_setNc_job_title_(ApplePerson * self, SEL _cmd, NSString *nc_job_title) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct ApplePerson, _nc_job_title), (id)nc_job_title, 0, 1); }

static void _I_ApplePerson_setAc_job_title_(ApplePerson * self, SEL _cmd, NSString *ac_job_title) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct ApplePerson, _ac_job_title), (id)ac_job_title, 1, 1); }

static void _I_ApplePerson_setN_job_title_(ApplePerson * self, SEL _cmd, NSString *n_job_title) { (*(NSString **)((char *)self + OBJC_IVAR_$_ApplePerson$_n_job_title)) = n_job_title; }

static void _I_ApplePerson_setA_job_title_(ApplePerson * self, SEL _cmd, NSString *a_job_title) { (*(NSString **)((char *)self + OBJC_IVAR_$_ApplePerson$_a_job_title)) = a_job_title; }

可以看到,属性带copy的在底层会有objc_setProperty方法,不带copy的则没有,源码翻阅objc_setProperty

image.png

无论哪个方法,都会调用reallySetProperty(self, _cmd, newValue, offset, true, false, false); image.png 查看reallySetProperty

static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
    ...
    if (copy) {
        newValue = [newValue copyWithZone:nil];
    } else if (mutableCopy) {
        newValue = [newValue mutableCopyWithZone:nil];
    } else {
        if (*slot == newValue) return;
        newValue = objc_retain(newValue);
    }
    ...
}

可以看到copymutableCopy的底层深浅拷贝操作的不同,接着我们再定义strongcopy两个属性进行对比

@interface ApplePerson : NSObject{
    NSString *salary;
}
@property (nonatomic, copy) NSString *nc_job_title;
@property (nonatomic, strong) NSString *ns_job_title;

clang一下

static void _I_ApplePerson_setNc_job_title_(...) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct ApplePerson, _nc_job_title), (id)nc_job_title, 0, 1); }

static void _I_ApplePerson_setNs_job_title_(...) { (*(NSString **)((char *)self + OBJC_IVAR_$_ApplePerson$_ns_job_title)) = ns_job_title; }

可以看到copy走的是objc_setProperty,而strong走的是内存平移的方法,这里可以看到它们之间的区别

五、类方法的存储

上篇文章,我们只在底层看到实例方法,没有看到类方法,把执行文件拉进烂苹果,可以看到+号方法sayNiuBi

image.png

查看clang后的代码

static void _I_ApplePerson_sayHenXi(ApplePerson * self, SEL _cmd) {
}

static void _C_ApplePerson_sayNiuBi(Class self, SEL _cmd) {
}

I应该是Instance method,C应该是代表Class methods,可以看到其传入参数,一个是ApplePerson *,另一个是Class,也就是ApplePerson的上一层,就是ApplePerson的元类,接下来在源码做验证

//打印内存地址
(lldb) x/4gx ApplePerson.class
0x100008470: 0x0000000100008448 0x0000000100379140
0x100008480: 0x000000010036d0e0 0x0000802800000000
//拿到isa
(lldb) p/x 0x0000000100008448 & 0x007ffffffffffff8
(long) $1 = 0x0000000100008448
//拿到元类地址
(lldb) po 0x0000000100008448
ApplePerson

平移32位

(lldb) p/x 0x0000000100008448 + 0x20
(long) $3 = 0x0000000100008468

还原

(lldb) p/x (class_data_bits_t *)$3
(class_data_bits_t *) $4 = 0x0000000100008468

拿到data

(lldb) p $4->data()
(class_rw_t *) $5 = 0x00000001011a5600

查看

(lldb) p *$5
(class_rw_t) $6 = {
  flags = 2684878849
  witness = 1
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 4302404417
    }
  }
  firstSubclass = nil
  nextSiblingClass = 0x00000001fe029dc0
}

获取方法

(lldb) p $6.methods()
(const method_array_t) $7 = {
  list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
     = {
      list = {
        ptr = 0x0000000100003d20
      }
      arrayAndFlag = 4294982944
    }
  }
}

获取方法列表

(lldb) p $7.list
(const method_list_t_authed_ptr<method_list_t>) $8 = {
  ptr = 0x0000000100003d20
}

获取ptr

(lldb) p $8.ptr
(method_list_t *const) $9 = 0x0000000100003d20

查看$9

(lldb) p *$9
(method_list_t) $10 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 2147483660, count = 1)
}

获取方法名称

(lldb) p $10.get(0).name
(SEL) $11 = "sayNiuBi"
  Fix-it applied, fixed expression was: 
    $10.get(0).name()

最终在元类找到类方法sayNiuBi

六、类方法存储的API方式解析

通过API验证类和元类的方法,新工程先定义一个类

@interface ApplePerson : NSObject{
    NSString *salary;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *workNumber;

- (void)sayHenXi;
+ (void)sayNiuBi;

定义获取方法名的方法

void AppleObjc_copyMethodList(Class pClass){
    unsigned int count = 0;
    Method *methods = class_copyMethodList(pClass, &count);
    for (unsigned int i=0; i < count; i++) {
        Method const method = methods[i];
        //获取方法名
        NSString *key = NSStringFromSelector(method_getName(method));
        NSLog(@"Method, name: %@", key);
    }
    free(methods);
}

通过类和元类分别调用方法

ApplePerson *person = [ApplePerson alloc];
Class pClass     = object_getClass(person);
AppleObjc_copyMethodList(pClass);
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
NSLog(@"------------------------------");
AppleObjc_copyMethodList(metaClass);

打印输出

Method, name: sayHenXi
Method, name: name
Method, name: setName:
Method, name: workNumber
Method, name: setWorkNumber:
Method, name: .cxx_destruct
------------------------------
Method, name: sayNiuBi

上面类打印属性的set/get方法,c++析构函数方法和实例方法;元类打印了类方法,
接下来再通过实例方法验证

void AppleInstanceMethod_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    
    Method instance_Method1 = class_getInstanceMethod(pClass, @selector(sayHenXi));
    Method instance_Method2 = class_getInstanceMethod(metaClass, @selector(sayHenXi));

    Method instance_Method3 = class_getInstanceMethod(pClass, @selector(sayNiuBi));
    Method instance_Method4 = class_getInstanceMethod(metaClass, @selector(sayNiuBi));
    
    NSLog(@"\nfun : %s \ninstance_Method1 : %p \ninstance_Method1 : %p \ninstance_Method1 : %p\ninstance_Method1 : %p",__func__,instance_Method1,instance_Method2,instance_Method3,instance_Method4);
}

调用

ApplePerson *person = [ApplePerson alloc];
Class pClass     = object_getClass(person);
AppleInstanceMethod_classToMetaclass(pClass);

打印输出

fun : AppleInstanceMethod_classToMetaclass 
instance_Method1 : 0x100003df1 
instance_Method1 : 0x0 
instance_Method1 : 0x0
instance_Method1 : 0x100003dd9

类的实例方法输出地址0x100003df1 ,元类实例方法输出0x100003dd9
再用类方法验证

void AppleClassMethod_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    
    Method class_method1 = class_getClassMethod(pClass, @selector(sayHenXi));
    Method class_method2 = class_getClassMethod(metaClass, @selector(sayHenXi));

    Method class_method3 = class_getClassMethod(pClass, @selector(sayNiuBi));
    Method class_method4 = class_getClassMethod(metaClass, @selector(sayNiuBi));
    
    NSLog(@"\nfun : %s \nclass_method1 : %p \nclass_method2 : %p \nclass_method3 : %p\nclass_method4 : %p",__func__,class_method1,class_method2,class_method3,class_method4);
}

调用

ApplePerson *person = [ApplePerson alloc];
Class pClass     = object_getClass(person);
AppleClassMethod_classToMetaclass(pClass);

打印输出

fun : AppleClassMethod_classToMetaclass 
class_method1 : 0x0 
class_method2 : 0x0 
class_method3 : 0x100003d61
class_method4 : 0x100003d61

class_method4是元类的类方法,class_method4是元类的对象方法,为何可以拿到类方法,并且可以看类的类方法是0x100003d61,元类的类方法是0x100003d61返回地址相同,查看源码

Method class_getClassMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;
    return class_getInstanceMethod(cls->getMeta(), sel);
}

当传入是一个元类的时候,返回元类的实例方法,所以一致 通过获取imp查找方法的实现

void AppleIMP_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);

    IMP imp1 = class_getMethodImplementation(pClass, @selector(sayHenXi));
    IMP imp2 = class_getMethodImplementation(metaClass, @selector(sayHenXi));
    IMP imp3 = class_getMethodImplementation(pClass, @selector(sayNiuBi));
    IMP imp4 = class_getMethodImplementation(metaClass, @selector(sayNiuBi));

    NSLog(@"\n %s \n imp1:%p\n imp2:%p\n imp3:%p\n imp4:%p",__func__,imp1,imp2,imp3,imp4);
}

调用

ApplePerson *person = [ApplePerson alloc];
Class pClass     = object_getClass(person);
AppleIMP_classToMetaclass(pClass);

打印输出

 AppleIMP_classToMetaclass 
 imp1:0x100003a00
 imp2:0x19305f6c0
 imp3:0x19305f6c0
 imp4:0x100003a14

imp1imp4有时正确的,imp2imp3理论上应该是nil,为和会有地址,查看源码

IMP class_getMethodImplementation(Class cls, SEL sel)
{
    IMP imp;
…
    if (!imp) {
        return _objc_msgForward;
    }
    return imp;
}

原来imp不存的的时候,返回_objc_msgForward
最终结论:所以OC其实没有所谓的类方法,实际都是实例方法