引入
众所周知Objective-C 属于一门动态语言,那什么是动态语言呢?
什么是runtime
动态语言就是在运行时来执行静态语言的编译链接的工作。这就要求除了编译器之外还要有一种运行时(
runtime
)系统来执行编译等功能,意味着要通过runtime
来实现动态创建类对象及实例对象,及消息的传递及转发。runtime
基本是由c和汇编来实现。源码地址
objc_object 与 objc_class:
----------objc2:---------------
struct objc_object {
isa_t isa;
};
struct objc_class : objc_object {
isa_t isa;
Class superclass;
cache_t cache;
class_data_bits_t bits;
};
----------objc(旧):------------
struct objc_object {
Class isa ;
};
struct objc_class {
Class isa ;
Class super_class ;
const char * name ;
long version ;
long info ;
long instance_size ;
struct objc_ivar_list * ivars ;
struct objc_method_list ** methodLists
struct objc_cache * cache ;
struct objc_protocol_list * protocols ;
} ;
差异:
- 类型上我们可以新版的objc_class是继承自objc_object,可以理解为类对象,这也更好的说明了万事万物皆对象。
- 旧版的实例对象中isa是指向他的类
Class isa
,新版isa替换成isa_t,接下来会介绍- 旧版的methodLists,cache,protocols,新版将有bits来完成
实现:
实例方法被调用时,会通过其持有 isa _t指针shiftcls寻找对应的类,然后在其中的 class_data_bits_t (存储调用类的方法,属性,协议等信息的地方)中查找对应的方法,如果已经缓存过cache 则从cache中寻找。我们先来介绍下isa_t及他的初始化实现。
isa_t
union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
struct {
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 19;
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
};
};
union
(联合体): 其中成员变量共用同一块地址空间,也就是说同一个内存段可以用来存放几种不同类型的成员,需要注意的是在每一瞬间只能存放其中的一种,而不是同时存放几种。
isa_t 结构体参数:
isa_t() : 提供一个方法 isa_t()来返回类指针
结构体参数
nonpointer
: 代表是否开启NONPOINTER isa指针优化,之前的版本又叫index的其实一个意思苹果后来给这类优化方案起了名字NONPOINTER
has_assoc
: 对象是否含有关联引用
has_cxx_dtor
: 对象是否含有 C++ 或者 Objc 的析构器
shiftcls
: 类的指针arm64下3bits,x86_64下44bits
magic
: 判断对象是否初始化完成 arm下0x16 ,x86_64下 0x3b
weakly_referenced
: 是否为弱引用的对象
deallocating
:对象是否正在执行析构函数(是否在释放内存)
has_sidetable_rc
:判断是否需要用sidetable去处理引用计数,(extra_rc的大小影响到这个变量)
extra_rc
:存储该对象的引用计数值减一后的结果
我们先看一下 isa_t的init初始化方法可能会容易理解一些(以arm64做例子):
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
if (!nonpointer) {
isa.cls = cls;
} else {
isa_t newisa(0);
newisa.bits = ISA_MAGIC_VALUE;
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;
//在某些情况下,这个写入必须在一个单独的存储中执行
// (例如,当实现一个类时,因为其他线程可能同时尝试使用该类)。
// fixme在这里使用atomics来保证单存储和保证内存顺序w.r.t.类索引表…但不要太原子化,
// 因为不想破坏实例化
isa = newisa;
}
}
TaggedPointer
和nonpointer
可以了解下这里先不讲了。默认objc2.0默认走 nonpointer
newisa.bits = ISA_MAGIC_VALUE
执行init时,先给
bits
赋了初值 ISA_MAGIC_VALUE# define ISA_MAGIC_VALUE 0x000001a000000001ULL
对照isa_t里结构体,转换为二进制(不足补齐至 64 位)我们可以看到下图:
ISA_MAGIC_VALUE 二进制
我们可以看到一下变化:
nonpointer
: 1 上面说过nonpointer决定了新老板本的替代,如果1开启,0则会使用老版本直接返回isa 指向的Classmagic
: 010110 -> 0x16 arm64中0x16是调试器判断当前对象是真的对象还是没有初始化的空间
newisa.has_cxx_dtor = hasCxxDtor;
hasCxxDtor 表示当前对象有 C++ 或者 ObjC 的析构函数,如果没有析构函数就会快速释放内存
newisa.shiftcls = (uintptr_t)cls >> 3;
作用是优化内存,iOS 中基本数据类型也好,指针也好需要按照他门自己的内存空间按照(8bits不同系统参数不一,也可设定)采取字节对齐。我们从图中看出后三位是没有意义的反而 浪费内存随意
shiftcls
右移三位清除减小内存的消耗。
以上便是isa_t的基本知识以及之前isa 和现在版本isa_t的区别