OC类和方法的底层原理

222 阅读4分钟

一、实例对象、类对象、元类之间的关系

类对象在内存里只会存在一份

Person *p = [[Person alloc] init];
p.name = @"hehe";
p.age = 25;

Class cls1 = [Person class];
Class cls2 = p.class;
Class cls3 = object_getClass(p);
// cls1和cls2和cls3都是同一个类对象,类对象在内存里只会存在一份

Class cls4 = object_getClass(cls3);
// cls4 就是元类,是类对象所属的类
// 元类的所属是根元类
// 根元类的所属是自己

二、isa

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};
#   define ISA_MASK        0x00007ffffffffff8ULL


#if SUPPORT_NONPOINTER_ISA

inline Class 
objc_object::ISA() 
{
    assert(!isTaggedPointer()); 
#if SUPPORT_INDEXED_ISA
    if (isa.nonpointer) {
        uintptr_t slot = isa.indexcls;
        return classForIndex((unsigned)slot);
    }
    return (Class)isa.bits;
#else
    return (Class)(isa.bits & ISA_MASK);
#endif
}

三、类结构

Person *p = [[Person alloc] init];
p.name = @"hehe";
p.age = 25;

Class cls1 = object_getClass(p); 		// 类对象
Class cls2 = object_getClass(cls1); // 元类对象

类对象:

  • 第一个属性是isa // 隐藏属性 8

  • 第二个属性是superclass // 8

  • 第三个属性是cache // 16

  • ………………

    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)test
{
  Class TestClass = objc_allocateClassPair([NSObject class],"TestClass",0);
  // 添加属性
  class_addIvar(TestClass,@"name",sizeof(id),log2(sizeof(id)),@encode(id));
  
  objc_registerClassPair(TestClass);
  
  id *t = [TestClass alloc];
  [p setValue:@"hehe" forKey:@"name"];
  
  NSLog(@"name == %@",[p valueForKey:@"name"]);
}
// 会报错

- (void)test
{
  Class TestClass = objc_allocateClassPair([NSObject class],"TestClass",0);
  
  objc_registerClassPair(TestClass);
  
  // 添加属性
  class_addIvar(TestClass,@"name",sizeof(id),log2(sizeof(id)),@encode(id));
  
  id *t = [TestClass alloc];
  [p setValue:@"hehe" forKey:@"name"];
  
  NSLog(@"name == %@",[p valueForKey:@"name"]);
}
原因:
  
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

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;

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

"v16@0:8"

imp (id self SEL _cmd) 参数所占的总的字节数为16,下一个参数从8开始,即两个参数各占8

四、深入理解

1、方法获取

  1. 通过class_getInstanceMethod对象中拿实例方法

  2. 通过class_getInstanceMethod元类对象中拿实例方法

  3. 通过class_getInstanceMethod对象中拿方法

  4. 通过class_getInstanceMethod元类对象中拿方法

2、imp 获取

  1. 通过class_getMethodImplementation对象中拿实例方法的IMP

  2. 通过class_getMethodImplementation元类对象中拿实例方法的IMP

    有个假的 ( 消息转发函数 _objc_msgForward )

  3. 通过class_getMethodImplementation对象中拿方法的IMP

    有个假的 ( 消息转发函数 _objc_msgForward )

  4. 通过class_getMethodImplementation元类对象中拿方法的IMP

3、类方法获取

  1. 通过class_getClassMethod对象中拿实例方法

  2. 通过class_getClassMethod元类对象中拿实例方法

  3. 通过class_getClassMethod对象中拿方法

  4. 通过class_getClassMethod元类对象中拿方法

Method class_getClassMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;

    return class_getInstanceMethod(cls->getMeta(), sel);
}

//-----------------------

		//  如果已经是个元类,就会返回类本身
		Class getMeta() {
        if (isMetaClass()) return (Class)this;
        else return this->ISA();
    }

加载类、加载分类、添加属性、方法、协议会执行attachLists

五、后续补充

该部分资料来源https://github.com/draveness/analyze (这人真是个大神,看他博客能学不少东西)

ObjC 类中的属性方法还有遵循的协议 信息都保存在 class_rw_t 中:

struct class_rw_t {
    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;
};

其中还有一个指向常量的指针 ro,其中存储了当前类在编译期就已经确定的属性、方法以及遵循的协议

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
    uint32_t reserved;

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

在编译期间类的结构中的 class_data_bits_t *data 指向的是一个 class_ro_t * 指针

然后在加载 ObjC 运行时的时候调用 realizeClass 方法:

  1. class_data_bits_t 调用 data 方法,将结果从 class_rw_t 强制转换为 class_ro_t 指针
  2. 初始化一个 class_rw_t 结构体
  3. 设置结构体 ro 的值以及 flag
  4. 最后设置正确的 data
const class_ro_t *ro = (const class_ro_t *)cls->data();
class_rw_t *rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
rw->ro = ro;
rw->flags = RW_REALIZED|RW_REALIZING;
cls->setData(rw);

但是,在这段代码运行之后 class_rw_t 中的方法,属性以及协议列表均为空。这时需要 realizeClass 调用 methodizeClass 方法来将类自己实现的方法(包括分类)、属性和遵循的协议加载到 methods、 properties 和 protocols 列表中

调用了 method_array_tattachLists 方法,将 baseMethods 中的方法添加到 methods 数组之后。我们访问 methods 才会获取当前类的实例方法。

大佬d 总结:

  1. 类在内存中的位置是在编译期间决定的,在之后修改代码,也不会改变内存中的位置。
  2. 类的方法、属性以及协议在编译期间存放到了“错误”的位置,直到 realizeClass 执行之后,才放到了 class_rw_t 指向的只读区域 class_ro_t,这样我们即可以在运行时为 class_rw_t 添加方法,也不会影响类的只读结构。
  3. class_ro_t 中的属性在运行期间就不能改变了,再添加方法时,会修改 class_rw_t 中的 methods 列表,而不是 class_ro_t 中的 baseMethods,对于方法的添加会在之后的文章中分析。

我再补充一下:

ivar_list_tclass_ro_t中,而不在class_rw_t

property_list_tclass_ro_t中,而class_rw_t中有protocol_array_t