OC底层探究 - OC对象的分类

325 阅读8分钟

OC对象的分类

  • instance 对象(实例对象):通过类alloc出来的对象,每次调用alloc都会产生新的instance对象

    Student *p1 = [[Student alloc] init];
    Student *p2 = [[Student alloc] init];
    

    instance对象在内存中存储的信息包括:

    • isa指针
    • 其他成员变量
  • class 对象(类对象):每个类在内存中有且只有一个class对象

    NSObject *obj1 = [[NSObject alloc] init];
    Class objClass1 = [obj1 class];
    Class objClass2 = [NSObject class];
    //class 方法返回的一直是class对象,类对象,而不是元类对象
    Class objClass3 = [[NSObject class] class];
    Class objClass4 = object_getClass(obj1);
    NSLog(@"%p-%p-%p-%p",objClass1,objClass2,objClass3,objClass4);
    

    class对象在内存中存储的信息主要包括:

    • isa指针
    • superclass指针
    • 类的属性信息(@property)
    • 类的对象方法信息(instance method)
    • 类的协议信息(protocol)
    • 类的成员变量信息(ivar):存储的成员变量的类型,名字等,相当于存储的描述信息,只需要存储一份
    • ......
  • meta-class 对象(元类对象):每个类在内存中有且只有一个meta-class对象

    Class metaClass = object_getClass([NSObject class]);
    

    meta-class对象和class对象的内存结构是一样的,但是用途不一样,在内存中存储的信息主要包括:

    • isa指针
    • superclass指针
    • 类的类方法信息(class method)
    • ......

class、objc_getClass、object_getClass 的区别

1.Class objc_getClass(const char *aClassName)
 		1> 传入字符串类名
		2> 返回对应的类对象
 
2.Class object_getClass(id obj)
 		1> 传入的obj可能是instance对象、class对象、meta-class对象
		2> 返回值
 				a) 如果是instance对象,返回class对象
 				b) 如果是class对象,返回meta-class对象
 				c) 如果是meta-class对象,返回NSObject(基类)的meta-class对象
 
3.- (Class)class、+ (Class)class
 		1> 返回的就是类对象
 
 - (Class) {
     return self->isa;
 }
 
 + (Class) {
     return self;
 }

isa和superclass

isa指针

isa指针

  • instance 的 isa 指向 class,当调用对象方法时,通过 instance 的 isa 找到 class,最后找到对象方法的实现进行调用
  • class 的 isa 指向 meta-class,当调用类方法时,通过 class 的 isa 找到 meta-class,最后找到类方法的实现进行调用

类对象的superclass指针

class的superclass指针

当 Student 的 instance 对象要调用 Person 的对象方法时,会先通过 isa 找到 Student 的 class,然后通过 superclass 找到 Person 的 class,最后找到对象方法的实现进行调用

meta-class对象的superclass指针

meta-class对象的superclass指针

当 Student 的 class 要调用 Person 的类方法时,会先通过 isa 找到 Student 的 meta-class,然后通过 superclass 找到 Person 的 meta-class,最后找到类方法的实现进行调用

isa、superclass总结

isa、superclass指向

  • Instance 的 isa 指向 Class

  • Class 的 isa 指向 Meta-Class

  • Meta-class 的 isa 指向基类的 Meta-Class

  • Class 的 Superclass 指向父类的 Class,如果没有父类,Superclass指针为 nil

  • Meta-Class 的 Superclass 指向父类的 Meta-Class,基类的 Meta-Class 的 Superclass 指向基类的 Class

  • Instance 调用实例方法的轨迹,isa 找到 Class,方法不存在,就通过 Superclass 找父类

  • Class 调用类方法的轨迹;isa 找 Meta-Class,方法不存在,就通过 Superclass 找父类

面试题

1.对象的isa指针指向哪里?

  • instance对象的isa指向class对象
  • class对象的isa指向meta-class对象
  • meta-class对象的isa指向基类的meta-class对象

2.OC的类信息存放在哪里?

  • 对象方法、属性、成员变量、协议信息,存放在class对象中
  • 类方法,存放在meta-class对象中
  • 成员变量的具体值,存放在instance对象

3.如果调用的类方法没有实现,是否会调用同名的实例方法?

会调用基类中同名的实例方法。

  1. Person 继承自 NSObject,都有 +test() 类方法
#Person文件
@interface MJPerson : NSObject
+ (void)test;
@end

@implementation MJPerson
+ (void)test {
    NSLog(@"+[MJPerson test] - %p", self);
}
@end

#NSObject+Test文件
@interface NSObject (Test)
+ (void)test;
@end

@implementation NSObject (Test)
+ (void)test {
    NSLog(@"+[NSObject test] - %p", self);
}
@end

#调用方式:
NSLog(@"[MJPerson class] - %p", [MJPerson class]);
NSLog(@"[NSObject class] - %p", [NSObject class]);
[MJPerson test];
[NSObject test];

结果输出:

[MJPerson class] - 0x100001260
[NSObject class] - 0x7fff925be118
+[MJPerson test] - 0x100001260
+[NSObject test] - 0x7fff925be118
  1. 如果将 MJPerson 的类方法的实现注释掉,其他代码不变,结果输出:
[MJPerson class] - 0x100001220
[NSObject class] - 0x7fff925be118
+[NSObject test] - 0x100001220
+[NSObject test] - 0x7fff925be118

因为 MJPerson 里找不到 test 实现方法,即找父类的实现方法。

  1. 如果将 MJPerson 和 NSObject 的类方法的实现都注释掉,结果输出:
[MJPerson class] - 0x1000011a0
[NSObject class] - 0x7fff925be118
+[MJPerson test]: unrecognized selector sent to class 0x1000011a0
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[MJPerson test]: unrecognized selector sent to class 0x1000011a0'
  1. 如果将 MJPerson 和 NSObject 的类方法的实现都注释掉,给 MJPerson 添加同名对象方法
#Person文件
@interface MJPerson : NSObject
+ (void)test;
@end

@implementation MJPerson
+ (void)test {
    NSLog(@"+[MJPerson test] - %p", self);
}
- (void)test {
    NSLog(@"-[MJPerson test] - %p", self);
}
@end

#NSObject+Test文件
@interface NSObject (Test)
+ (void)test;
@end

@implementation NSObject (Test)
+ (void)test {
    NSLog(@"+[NSObject test] - %p", self);
}
@end

#调用方式:
NSLog(@"[MJPerson class] - %p", [MJPerson class]);
NSLog(@"[NSObject class] - %p", [NSObject class]);
[MJPerson test];
[NSObject test];

结果输出:

[MJPerson class] - 0x1000011e0
[NSObject class] - 0x7fff925be118
+[MJPerson test]: unrecognized selector sent to class 0x1000011e0
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[MJPerson test]: unrecognized selector sent to class 0x1000011e0'
  1. 如果将 MJPerson 和 NSObject 的类方法的实现都注释掉,给 NSObject 添加同名对象方法
#Person文件
@interface MJPerson : NSObject
+ (void)test;
@end

@implementation MJPerson
+ (void)test {
    NSLog(@"+[MJPerson test] - %p", self);
}
@end

#NSObject+Test文件
@interface NSObject (Test)
+ (void)test;
@end

@implementation NSObject (Test)
+ (void)test {
    NSLog(@"+[NSObject test] - %p", self);
}
- (void)test {
    NSLog(@"-[NSObject test] - %p", self);
}
@end

#调用方式:
NSLog(@"[MJPerson class] - %p", [MJPerson class]);
NSLog(@"[NSObject class] - %p", [NSObject class]);
[MJPerson test];
[NSObject test];

结果输出:

[MJPerson class] - 0x100001220
[NSObject class] - 0x7fff925be118
-[NSObject test] - 0x100001220
-[NSObject test] - 0x7fff925be118

由此可见,当类方法无实现时,会调用基类中同名的对象方法,上述辩证方法验证了 isa、superclass 指向图中的 Meta-Root Class 的 Superclass 指向 Root class(上图实线转弯处)。

调用方法实质是 Runtime 的消息转发机制,Runtime只会根据方法名寻找而不会在意是类方法还是实例方法。

4.讨论isa指针值

struct mj_objc_class {
    Class isa;
};

MJPerson *person = [[MJPerson alloc] init];
Class personClass = [MJPerson class];
struct mj_objc_class *personClass2 = (__bridge struct mj_objc_class *)(personClass);
Class personMetaClass = object_getClass(personClass);
NSLog(@"%p %p %p", person, personClass, personMetaClass);

通过 LLDB 指令来查看 isa 值:

Printing description of person->isa:
(Class) isa = MJPerson

(lldb) p person->isa  // 获取不到真正的isa指针
(Class) $0 = MJPerson
(lldb) p (long)person->isa 	// 10进制
(long) $1 = 8303516107936977
(lldb) p/x (long)person->isa 	// 16进制,实例对象isa指针值
(long) $2 = 0x001d8001000014d1
(lldb) p/x personClass	// 类对象地址
(Class) $3 = 0x00000001000014d0 MJPerson
(lldb) p/x 0x001d8001000014d1 & 0x0000000ffffffff8	// 进行位运算
(long) $4 = 0x00000001000014d0

(lldb) p/x personClass->isa
error: member reference base type 'Class' is not a structure or union
// 类并没有暴露出isa指针值,定义了一个和类相似的结构体mj_objc_class,强制转换后,才获取到类对象的isa指针
(lldb) p/x personClass2->isa
(Class) $0 = 0x000001a10402d2e9	// 类对象的isa指针值
(lldb) p/x personMetaClass
(Class) $1 = 0x000000010402d2e8	// 元类对象地址
(lldb) p/x 0x000001a10402d2e9 & 0x0000000ffffffff8	// 进行位运算
(long) $2 = 0x000000010402d2e8

总结:

  • 从 64bit 开始,isa 需要进行一次位运算,才能计算出该 isa 指针指向的真实地址。

  • isa指针需要 & ISA_MASK 值才能得出最终指向的真实地址

  • Runtime 中的 ISA_MASK :

    # if __arm64__//iPhone
    #   define ISA_MASK        0x0000000ffffffff8ULL
    # elif __x86_64__//mac
    #   define ISA_MASK        0x00007ffffffffff8ULL
    

5.讨论superclass指针值,是否会像isa指针值一样?

Student 继承自 Person,自定义结构体来强制转换类

struct mj_objc_class {
    Class isa;
    Class superclass;
};

struct mj_objc_class *personClass = (__bridge struct mj_objc_class *)([MJPerson class]);
struct mj_objc_class *studentClass = (__bridge struct mj_objc_class *)([MJStudent class]);
NSLog(@"1111");

LLDB 指令调试:

(lldb) p/x studentClass->superclass
(Class) $0 = 0x00000001000014b8 MJPerson
(lldb) p/x personClass
(mj_objc_class *) $1 = 0x00000001000014b8

由此可见,studentClass 的 superclass 的值与 personClass 的地址相同

6.类的本质结构

Runtime 中 objc_class 的源码

# objc_object
struct objc_object {
private:
    isa_t isa;// 类的isa指针是私有的

public:
      // 诸多方法
}

# objc_class:objc_object
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();
    }
    // 诸多方法
}

# 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_ro_t
struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;// instance对象占用的内存空间
#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;
    }
};

窥探 Struct objc_class 的结构

objc_class结构

想LLDB命令查看相关类结构,可以采取仿写类结构进行打印查看,MJ大神的总结:

#import <Foundation/Foundation.h>

#ifndef MJClassInfo_h
#define MJClassInfo_h

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
# endif

#if __LP64__
typedef uint32_t mask_t;
#else
typedef uint16_t mask_t;
#endif
typedef uintptr_t cache_key_t;

struct bucket_t {
    cache_key_t _key;
    IMP _imp;
};

struct cache_t {
    bucket_t *_buckets;
    mask_t _mask;
    mask_t _occupied;
};

struct entsize_list_tt {
    uint32_t entsizeAndFlags;
    uint32_t count;
};

struct method_t {
    SEL name;
    const char *types;
    IMP imp;
};

struct method_list_t : entsize_list_tt {
    method_t first;
};

struct ivar_t {
    int32_t *offset;
    const char *name;
    const char *type;
    uint32_t alignment_raw;
    uint32_t size;
};

struct ivar_list_t : entsize_list_tt {
    ivar_t first;
};

struct property_t {
    const char *name;
    const char *attributes;
};

struct property_list_t : entsize_list_tt {
    property_t first;
};

struct chained_property_list {
    chained_property_list *next;
    uint32_t count;
    property_t list[0];
};

typedef uintptr_t protocol_ref_t;
struct protocol_list_t {
    uintptr_t count;
    protocol_ref_t list[0];
};

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;  // instance对象占用的内存空间
#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;
};

struct class_rw_t {
    uint32_t flags;
    uint32_t version;
    const class_ro_t *ro;
    method_list_t * methods;    // 方法列表
    property_list_t *properties;    // 属性列表
    const protocol_list_t * protocols;  // 协议列表
    Class firstSubclass;
    Class nextSiblingClass;
    char *demangledName;
};

#define FAST_DATA_MASK          0x00007ffffffffff8UL
struct class_data_bits_t {
    uintptr_t bits;
public:
    class_rw_t* data() {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
};

/* OC对象 */
struct mj_objc_object {
    void *isa;
};

/* 类对象 */
struct mj_objc_class : mj_objc_object {
    Class superclass;
    cache_t cache;
    class_data_bits_t bits;
public:
    class_rw_t* data() {
        return bits.data();
    }
    
    mj_objc_class* metaClass() {
        return (mj_objc_class *)((long long)isa & ISA_MASK);
    }
};

#endif /* MJClassInfo_h */