本文可能篇幅比较长,主要为了本身往后温习知识所用。若是有幸被你发现这篇文章,而且引发了你的阅读兴趣,但愿这篇文章能对你有所帮助。如发现任何有误之处,肯请留言纠正,谢谢。️bash
iOS底层探究-浅谈alloc,init,new 前面的文章中咱们提到过,基类NSObject有个默认的属性isa:架构
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
复制代码
那么isa是什么呢?
苹果有段官方的描述: “A pointer to the class definition of which this object is an instance”。
isa实际上是指一个实例对象指向该对象的类的指针。没错,早期的时候isa的确是单纯的一个指针,只不事后期苹果进行了优化,不但保存了指针的地址,另外也存储了一些类的信息。下面咱们看下苹果isa的源代码:ide
union isa_t {
//两个默认的构造函数
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
//isa指向的类
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
//位域
ISA_BITFIELD; // defined in isa.h
};
#endif
};
复制代码
能够看出isa实际上是个union联合体,苹果这块用联合体优化内存占用,而且使用位域ISA_BITFIELD增长可读性。联合体和位域的结合使用,我会单独去写,请持续关注,咱们先看下 ISA_BITFIELD 位域的定义和使用。
定义:函数
# define ISA_BITFIELD \ uintptr_t nonpointer : 1; \ uintptr_t has_assoc : 1; \ uintptr_t has_cxx_dtor : 1; \ uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \ uintptr_t magic : 6; \ uintptr_t weakly_referenced : 1; \ uintptr_t deallocating : 1; \ uintptr_t has_sidetable_rc : 1; \ uintptr_t extra_rc : 8
复制代码
根据Union联合体的特性
isa_t
总共占用8个字节64位工具其中:
nonpointer
表明是不是纯指针 0 表明纯指针 1 表明不止是类对象地址,isa 中包含了类信息、对象的引用计数等
has_assoc
关联对象标志位
has_cxx_dtor
该对象是否有 C++ 或者 Objc 的析构器,若是有析构函数,则须要作析构逻辑, 若是没有,则能够更快的释放对象
shiftcls
存储类指针的值。开启指针优化的状况下,在 arm64 架构中有 33 位用来存储类指针
magic
用于调试器判断当前对象是真的对象仍是没有初始化的空间
weakly_referenced
存储对象是否被指向或者曾经指向一个 ARC 的弱变量, 没有弱引用的对象能够更快释放。
deallocating
标志对象是否正在释放内存
has_sidetable_rc
当对象引用技术大于 10 时,则须要借用该变量存储进位
extra_rc
当表示该对象的引用计数值,其实是引用计数值减 1, 例如,若是对象的引用计数为 10,那么 extra_rc 为 9。若是引用计数大于 10, 则须要使用到下面的has_sidetable_rc
post
前面咱们已经看到了isa_t结构体保存了类的不少信息,其中就包括了类指针的值。下面咱们就研究下isa的指针走位。先看一张大神画的图:优化
图中实线箭头表明类继承的走向,而虚线箭头是表明isa的走向ui
其实我第一次看这张图的时候是懵逼的,乱七八糟的线根本看不出是表达什么逻辑。后来随着对底层实现的了解和探索,才慢慢了解了图做者想表达的意思,下面我就和你们一块儿来研究一下this
###1:静态分析,咱们要研究isa,先看下系统是怎样获取类对象的: object_getClass()
是系统获取类对象的一个函数,内部是这样实现的spa
Class object_getClass(id obj) {
if (obj) return obj->getIsa();
else return Nil;
}
objc_object::getIsa()
{
if (!isTaggedPointer()) return ISA();
uintptr_t ptr = (uintptr_t)this;
if (isExtTaggedPointer()) {
uintptr_t slot =
(ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
return objc_tag_ext_classes[slot];
} else {
uintptr_t slot =
(ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
return objc_tag_classes[slot];
}
}
复制代码
咱们知道objc都是!isTaggedPointer的,能够直接定位到ISA()函数。
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
}
复制代码
SUPPORT_INDEXED_ISA这个宏定义在iOS是0.
(Class)(isa.bits & ISA_MASK)这句话就是咱们的重点了,系统经过&运算结合mask来获得isa类指针的值 最后(Class)作了步强转返回Class
###2:动态调试,模拟静态代码的步骤,经过lldb工具印证咱们的推测 咱们经过objc最新的源码,创建一个target方便咱们调试。
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
JSPerson *object = [JSPerson alloc];
// 断点位置
}
return 0;
}
复制代码
添加断点,咱们经过lldb来调试
(lldb) x/2xg object // 打印类的信息
0x101a4c8b0: 0x001d800100001101 0x0000000000000000
复制代码
其中0x001d800100001101
就是对象中的isa咱们坐下运算
(lldb) p/x 0x001d800100001101 & 0x0000000ffffffff8
(long) $2 = 0x0000000100001100
复制代码
而后对对象所属的类进行打印
(lldb) p/x object.class
(Class) $3 = 0x0000000100001100 JSPerson
复制代码
咱们看到了
$2
和$3
是相等的,也就印证了咱们的第一步,对象和类是靠isa创建绑定的
那么类的isa会指向何处呢?咱们先看下底层对类结构体的定义:
struct objc_class : objc_object // 类在底层实际上是个继承于objc_object的结构体,万物皆对象在这里获得印证
// 咱们看下objc_class这个结构体
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;
复制代码
说明objc_class第一个成员也是isa,那咱们继续打印
(lldb) x/2xg 0x0000000100001100
0x100001100: 0x001d8001000010d9 0x0000000100b00140
(lldb) p/x 0x001d8001000010d9 & 0x0000000ffffffff8
(long) $4 = 0x00000001000010d8
(lldb) po 0x00000001000010d8
JSPerson
复制代码
咱们看到输出的又是JSPerson,可是内存地址却不相同,其实他是JSPerson的元类(metaClass),metaClass 是 Class 对象的类,一样也是个对象。每一个类都必须有一个惟一的 metaClass.
(lldb) x/2xg 0x00000001000010d8
0x1000010d8: 0x001d800100b000f1 0x0000000100b000f0
(lldb) p/x 0x001d800100b000f1 & 0x0000000ffffffff8
(long) $6 = 0x0000000100b000f0
(lldb) po 0x0000000100b000f0
NSObject
复制代码
输出了NSObject的元类也就是全部类的根元类
(lldb) x/2xg 0x0000000100b000f0
0x100b000f0: 0x001d800100b000f1 0x0000000100b00140
(lldb) p/x 0x001d800100b000f1 & 0x0000000ffffffff8
(long) $8 = 0x0000000100b000f0
(lldb) po 0x0000000100b000f0
NSObject
复制代码
你会发现不管你打印多少次获得的结果都是NSObject内存地址也不会变,造成了循环。
咱们在把这张图拿过来,如今感受是否是清晰了不少?
下一篇文章 将对类的结构作跟细致的研究,若是以为喜欢的话还请加个关注,我会常常保持更新。