1. 概述
说起isa,我想对于一些探索过底层的朋友,应该并不陌生,而对于一些没有探究过的朋友,就有些云里雾里了,这是什么东西,干什么用的,平时开发中也没用到啊?
我们平时开发确实没有直接用到isa,但是它确实时时刻刻地在底层发挥它的作用,那么既然它有用,那它是长什么样呢,又是怎么运作的呢?本篇文章,我们一探究竟。
作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS交流群:196800191,加群密码:112233,不管你是小白还是大牛欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长!
2. isa结构
首先我们先看一下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
};
由这个结构我们不难看出,isa是一个union,里面有个uintptr_t,其定义如下:
typedef unsigned long uintptr_t;
uintptr_t其实就是一个long类型,占8节。
还有个struct,struct里面有个宏定义ISA_BITFIELD,我们在具体看一下:
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# define ISA_BITFIELD \
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)
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
# 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
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
# else
# error unknown architecture for packed isa
# endif
由上面的宏定义可知,针对不同平台,其定义的内容略有不同,本人是用模拟器调试的,所以我们看一下x86_64下的宏定义,将isa结构中的宏定义替换掉后,简化如下:
union isa_t {
isa_t() { }
isa_t(long value) : bits(value) { }
Class cls;
long bits;
#if defined(ISA_BITFIELD)
struct {
long nonpointer : 1;
long has_assoc : 1;
long has_cxx_dtor : 1;
long shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/
long magic : 6;
long weakly_referenced : 1;
long deallocating : 1;
long has_sidetable_rc : 1;
long extra_rc : 8
};
#endif
};
简化后就好看多了,对了还有个cls成员变量,其类型是Class,这是什么,看下面:
typedef struct objc_class *Class;
所以cls是一个指针。
我们整体看一下isa的结构,不难看出,这是一个我们之前文章提到的联合体位域结构,联合体内的所有成员变量共享内存空间,其空间的大小取决于占用空间最大的成员变量,从结构中看,cls是指针,占8字节,bits是long类型,占8字节,所以整个isa占8字节,64位。
既然用到了联合体位域,那目的肯定是要节省内存空间,下面看看这64位都是存储哪些数据呢:
3. isa原理
上面了解了isa的结构,那么它究竟是干什么的呢?我们首先看一下对象的祖宗NSObject的底层结构:
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
由这段代码我们可知,任何继承NSObject的对象,默认都会有一个成员变量isa,而且这个isa永远在成员列表的第一位,即使内存有优化。
我们在查看底层代码的时候也能看到:
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
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
}
追踪底层代码可知,objc_object有isa变量,而objc_class继承objc_object,故而我们的类也是有isa成员变量的。关于类的探索,后续文章会更新。
知道isa的位置后,再看看isa的作用,isa的类型是Class,也就是isa指向的是一个类,那么这个类是什么呢?
之前的文章OC对象底层探索中提到对象的空间开辟完成之后,需要初始化对象的isa,如下:
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
assert(!cls->instancesRequireRawIsa());
assert(hasCxxDtor == cls->hasCxxDtor());
initIsa(cls, true, hasCxxDtor);
}
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
assert(!isTaggedPointer());
if (!nonpointer) {
isa.cls = cls;
} else {
assert(!DisableNonpointerIsa);
assert(!cls->instancesRequireRawIsa());
isa_t newisa(0);
#if SUPPORT_INDEXED_ISA
assert(cls->classArrayIndex() > 0);
newisa.bits = ISA_INDEX_MAGIC_VALUE;
newisa.has_cxx_dtor = hasCxxDtor;
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
newisa.bits = ISA_MAGIC_VALUE;
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
isa = newisa;
}
}
由上面第二个方法中,在调用这个方法的时候nonpointer为true,所以会走到else分支,同时又不支持SUPPORT_INDEXED_ISA,所以代码最终走到#else分支,在这里有个很重要的赋值,那就是:
newisa.shiftcls = (uintptr_t)cls >> 3;
为什么重要,因为shiftcls记录的类的相关信息,而其值是由cls右移3位得到的。当isa初始化完成,isa的bits值里面就包含了类的信息。
那么如果获取到对象isa里面的类的信息呢?首先请看下面这个运行时API:
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
这个方法里面调用了obj->getIsa()
inline Class
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];
}
}
这个方法中第一个判断!isTaggedPointer(),因为当前对象不是taggedPointer,所以执行 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
}
这个方法中很显然会走到#else分支,我们得到的是: return (Class)(isa.bits & ISA_MASK);
而ISA_MASK的定义如下:
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# else
# error unknown architecture for packed isa
# endif
最终我们可以理解为:对象的isa里面关联了类的信息,并且该类是通过isa的bits与ISA_MASK做按位与运算得来的。
下面做个简单的验证:
int main(int argc, const char * argv[]) {
@autoreleasepool {
GYMPerson *person = [GYMPerson alloc];
object_getClass(person);
}
return 0;
}
我们在object_getClass(person);这行打个断点进行lldb调试,结果如下:
由上面可知,对象的isa关联对象的类。那么问题来了类的isa关联的是什么呢?
咱们简单了解一些,万物皆对象,所以,类在底层也是一个对象,类对象,为什么这么说呢?请看底层代码:
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();
}
}
这里只是摘抄了一部分代码,底层的类也是继承objc_object,那么类对象在内存中会创建多少呢?又是谁创建的呢?
类对象是由系统在编译期创建的,而且在内存中只创建一份,而类对象的isa则是关联了它的元类。
- (void)classNumberTest {
Class class1 = [GYMPerson class];
Class class2 = [GYMPerson alloc].class;
Class class3 = [GYMPerson alloc].class;
Class class4 = object_getClass([GYMPerson alloc]);
NSLog(@"\nclass1 address = %p\nclass2 address = %p\nclass3 address = %p\nclass4 address = %p\n", class1,class2,class3,class4);
}
输出结果为:
class1 address = 0x10234c7d8
class2 address = 0x10234c7d8
class3 address = 0x10234c7d8
class4 address = 0x10234c7d8
由此可以证实类在内存中只有一份。
到此,我们可以简单总结一下isa的指向:对象的isa指向类,类的isa指向了元类。
4. isa走位
上面分析了对象的isa情况,又提到了类的isa情况,但是这只是一小部分,上面说类的isa指向了元类,那么元类也是类,元类的isa又指向哪里呢?在这里我们就不继续调试分析了,看看苹果官方给出的图,如下:
这个图中包含了2个走向,一个是superclass,一个是isa。
我们先来看看isa情况:
对象的isa指向类,类的isa指向元类,元类的isa指向根元类,根元类的isa指向了根元类(即自己)。
其次是superclass的情况:
继承关系大家都比较了解,这里主要强调两点,一是NSObject的父类是nil,而是根元类的父类是NSObject。
苹果的这个图已经非常简单明了,这里就不再赘述了。
5. 结束语
以上即是本人对isa的理解,记录自己学习过的知识,同时也希望能帮助到他人。文章如果有不对的地方,还请路过的各位朋友批评指正。
更多文章,请关注本人其他博客。
原文作者:Daniel_Coder
原文地址:https://blog.csdn.net/guoyongming925/article/details/109017761
网友评论