OC对象的本质
总结性文章,如有问题,请评论
image.png从以下几个维度分析
1.对象的分类
- 首先按分类来说,OC对象分为实例对象、类对象、元类对象三种。
他们的底层实现都是结构体.
2.内部实现
- 实例对象
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
Class的内部实现
typedef struct objc_class *Class;
所以ISA是一个指向结构体的指针变量
当继承发生时
struct NSobject_imp{
Class imp;
};
struct Person {
struct NSobject_imp imp_isa;
int _age;
int _num;
int _on;
};
通俗理解,生成的person实例对象中,包含三个ivar + NSObject的ISA指针
实例对象的结构如下:
- ISA //指向父类的指针
- _ivar //成员变量
- _ivar //成员变量
- _ivar //成员变量
类对象class
通过xcode查看的类对象已经过时,下边是之前的定义,最新的定义可以查看objc的源码,在下边列出几个重要的点
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
新版
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
最新版的ISA被注释掉了,不要被迷惑,只要是对象,就会有ISA,实际是objc_class继承于objc_object
而...
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
然后展开class_data_bits_t里边public的 class_rw_t, 看通过 rw可以了解,这部分是可以读写的
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint16_t version;
uint16_t witness;
const class_ro_t *ro; //不可写部分
method_array_t methods; //方法列表
property_array_t properties;// 属性列表
protocol_array_t protocols; //协议列表
Class firstSubclass;
Class nextSiblingClass;
char *demangledName;
}
这是 class_ro_t 可读不可写的部分
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize; //实例的大小
#ifdef __LP64__
uint32_t reserved;
#endif
t 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;
}
通过上述几个机构提可以看出,类对象的基本结构如下
- ISA // 指向元类指针
- superclass //指向父类的指针
- cache //方法缓存列表,注意是对象方法
- bits //里边有基本的可变的一堆属性和不可变的一堆属性
针对bits里边可变不可变的属性做个补充:
- method 和 protocol 这些通过运行时可以动态添加;properties可以在分类中添加(只不过值是存储在一个全局的散列表中的)
- ivars这个是不可变的,也就是说,不能向一个已经创建好的类中添加ivar,这是因为,在实例化实例对象的时候,内存大小是固定的。(instanceSize)
实例结构体中包含的就是ISA指针和ivars的列表。
元类对象
元类对象是类对象的一种,内部结构和类对象一样,在创建类对象的时候,会同时创建一个元类对象出来。
只不过内部存储的值和类对象是不一样的,元类对象中存储的是类方法。
- ISA // 指向基类元类指针(注:此处的ISA指针直接指向NSObject的元类)
- superclass //指向父类的元类的指针
- cache //方法缓存列表,注意类象方法
- bits //类方法
总结:起始根据这三种对象,体现了程序的分层设计的原则,最上层包含的是成员变量、下一层包含各种方法和缓存、再进一层就是只有类才能调用的类方法。
3.占用空间的大小
我们创建一个实例对象,占用的内存空间的大小是怎么样的,找到最上边实例的结构体样式。
- ISA //指向父类的指针
- _ivar //成员变量
- _ivar //成员变量
- _ivar //成员变量
我们可以看到,包含一个ISA指针,还有不等的ivar,占用的大小可以直接算出,指针变量8个字节,int 类型 4个字节... 相加就得出了。
但是注意:结构体在创建的时候,有内存对齐的概念,必须是8的整数倍,也就是当内存占用20个字节,会给你分配24个字节。
C函数还有一层内存对齐的概念,他分配内存大小是16的整数倍
4.方法调用
直接拿网上的图
查找流程.png
先看ISA 和 superclass这两个指针的指向
ISA - > 父类
父类ISA - >元类
元类ISA - > Root Meta Class
Subclass 的superclass指针 - > Superclass
Superclass 的 superclass指针 - > NSObject
对象方法:根据ISA指针找到自己的父类-- >查找父类cache缓存 -->查找父类methodlist --> 根据superclass指针 -- > Superclass的cache --- > Superclass的methodlist ---> NSObject...
类方法:根据ISA指针找到Metaclass的cache -- > Metaclass 的methodlist -- > 根据superclass指针周到 Superclass Meta的cache ....
其他补充
1.关于ISA指针中存储的地址不是类对象首地址的原因
由于在新版ISA指针做了优化,8个字节 64位的存储空间,不仅存储指向类对象的地址,还保存了一些标志位信息,包括是否有关联对象、是否有弱引用、引用计数存储等。所以要取出这个地址还是要 & 上下边的地址。
ISA_MASK2020-02-29上午8.31.58.png
2.类方法在寻找的时候,找到NSObject的元类中,如果还是没有,还会去NSObject类中寻找是否有相同符号的对象方法。
3.获取元类的方式object_getClass([NSObject class]);传递一个类对象进去,就可以获取。