一步一个脚印地探索iOS的OC底层原理,经过前面的文章能够大概了解了OC对象建立的alloc原理和OC对象的内存字节对齐,可是这也只是知道了对象建立的底层过程和开辟内存空间的,这篇文章将介绍
对象的本质
和对象与类的关联---isabash
isa指针:在Objective-C中,任何类的定义都是对象。类和类的实例(对象)没有任何本质上的区别。任何对象都有isa指针。也就是说在对象建立的时候就会有isa指针初始化了。为了搞清楚仍是须要用到OC对象建立的alloc原理里面源码的_class_createInstanceFromZone
的方法的部分源码,而后跟着流程进去获得以下的部分源码数据结构
//_class_createInstanceFromZone的部分代码
//初始化实例的isa指针
obj->initInstanceIsa(cls, hasCxxDtor);
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;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
newisa.bits = ISA_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
// This write must be performed in a single store in some cases
// (for example when realizing a class because other threads
// may simultaneously try to use the class).
// fixme use atomics here to guarantee single-store and to
// guarantee memory order w.r.t. the class index table
// ...but not too atomic because we don't want to hurt instantiation isa = newisa; } } 复制代码
其中的nonpointer
字面的意思是没有指针的,通常状况下nonpointer是为true的,只有在例如实现了allocwithzone方法,retain,release等的时候会是false。若是为false是直接将传进来的cls为isa的关联的cls赋值。架构
其余的剩下的部分就是对isa
的初始化赋值了。可是具体的isa
内部是怎样的仍是不知道的,从源码中isa_t
点击进去能够查看。ide
经过源码能够知道isa
的isa_t
类型的内部结构函数
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
};
#ifndef _UINTPTR_T
#define _UINTPTR_T
typedef unsigned long uintptr_t;
#endif /* _UINTPTR_T */
复制代码
从中能够知道,isa
是一个联合体(union),里面有关联的类cls和long型的bits。源码分析
什么是联合体(union)呢?联合体是一种特殊的类,也是一种构造类型的数据结构。彻底就是共用一个内存首地址,而且各类变量名均可以同时使用,操做也是共同生效。因此也叫
共用体
。而且联合体(union)中是各变量是“互斥”的
,可是内存使用更为精细灵活,也节省了内存空间。post
由上面的概念能够知道,cls
和bits
之间是互斥的,即有cls
就没有bits
,有bits
就没有cls
。这就很好地解释了为何上面的源码在初始化isa
的时候会用nonpointer
来区分开。因此isa
的大小占8个字节,64位。其中这64位中分别存储了什么呢?经过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
# 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
复制代码
这两种是分别在arm64
和x86
系统架构下的,可是都是64位的,本文的说明是在x86
下介绍的。ui
isa
指针开启指针优化,0:表示纯指针;1:表示不止是类对象地址,isa中包含了类信息、对象的引用计数等。newisa.shiftcls = (uintptr_t)cls >> 3;
复制代码
为了方便介绍下面的内容须要定义一个什么属性都没有的TestJason类,而后经过objc4-756.2
苹果官方的源码。经过object_getClass
这个方法能够获取到类。this
TestJason *test2 = [TestJason alloc];
Class testClass = object_getClass(test2);
NSLog(@"%@",test2);
复制代码
经过源码找到object_getClass
的方法
/***********************************************************************
* object_getClass.
* Locking: None. If you add locking, tell gdb (rdar://7516456).
**********************************************************************/
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
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];
}
}
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
}
# define SUPPORT_INDEXED_ISA 0
# define ISA_MASK 0x00007ffffffffff8ULL
复制代码
从源码中能够知道返回的isa
最终是(Class)(isa.bits & ISA_MASK)
。 其中源码有一个判断isTaggedPointer()
,其中苹果对于Tagged Pointer
的概念引入,是为了节省内存和提升执行效率,对于 64 位程序,相关逻辑能减小一半的内存占用,以及 3 倍的访问速度提高,100 倍的建立、销毁速度提高。若是想了解这部分的内容能够看看深刻理解 Tagged Pointer 。
下面就是用lldb
的指令来验证一下的。首先用x/4gx test2
来打印出地址
p/x TestJason.class
打印出
TestJason
类的内存值
(Class)(isa.bits & ISA_MASK)
的,因此将
x/4gx test2
打印出来的
0x001d800100001749
&
ISA_MASK
的值获得以下:
$3
和
$4
的内存值是同样的,因此
isa
是关联着对象与类的。由前面的文章知道因为内存的优化对象的其余属性的位置实际会发生变化的,因此对象的第一个属性就是
isa
。
经过上面的介绍,能够知道了isa是关联着对象与类的,而且对象的isa指向类,由于万物皆对象,那么类的isa指向的是谁呢?能够经过苹果官方的isa的走位流程图 isa流程图.png
isa
的走位,实线表明的是
继承关系
的走位。图中有一个
meta class
元类的概念。
什么是元类?在OC中,对象的方法并无存储于对象的结构体中(若是每个对象都保存了本身能执行的方法,那么对内存的占用有极大的影响)。 当对象的实例方法被调用时,它经过本身的isa来查找对应的类,而后在所属类的 class_data_bits_t结构体中查找对应方法的实现。同时,每个objc_class 也有一个指向本身的父类的指针superclass用来查找继承的方法。 而当调用 类方法 时,它的查找流程是怎样的呢?对此OC的解决方案就是引入元类,来保证类方法也能经过相同的机制查找到。对于元类的解释转自OC源码分析之isa
是否是看着这张图的各类箭头指向一脸懵逼呢?下面就是来对这张图的isa
的走位验证一下。
仍是用TestJason
这个什么属性都没有的类来介绍。经过lldb
的指令来验证。由上面的图能够知道,对象的isa
指向类,类的isa
指向元类。
x/4gx test2
来打印出
isa
的内存值,而后用
isa
的内存值&
ISA_MASK
获得
$9
,而后
po $9
此时获得的是类。而后再用
x/4gx
来打印
$9
的值,此时就是至关于打印出类的
isa
的内存值了。最后能够看到两个
TestJason
,可是这两个的内存值是不同的,分别是类和元类。可是中能够看到
元类
里面还有
isa
值,那么就继续打印。
isa
指向了
NSObject
,可是这个
NSObject
究竟是类呢?仍是元类呢?为了搞清楚,能够用
x/4gx NSObject.class
来打印类的
isa
来验证。
$13
打印的内存值是等于
$16
的,那么就能够知道
元类
的
isa
指向的是
根元类
的
isa
。那么
根元类
的
isa
指向谁呢?继续打印
根元类
的
isa
指向了它的自己,这就造成了一个闭环。因此总体来讲的话,就是
对象的isa-->类的isa-->元类的isa-->根元类的isa-->根元类的isa(根元类自己)
复制代码
这就很好地验证了苹果的官网的isa
走位图。
为了介绍方便,添加多一个TestSuperJason
的类,而且让TestJason
是继承TestSuperJason
的。仍是经过lldb
的指令来的打印class_getSuperclass
方法。
这是类的继承关系的打印
类:
TestJason-->TestSuperJason-->NSObject-->nil
元类:
TestJason元类-->TestSuperJason元类-->NSObject元类-->NSObject类-->nil
复制代码
经过上面的知识能够大概了解了isa
的原理,可是对象的本质是什么还不是很了解的,能够经过clang
编译成cpp
文件来查看。实现的代码以下:
@interface Jason : NSObject{
NSString *nickName;
}
@property (nonatomic, copy) NSString *name;
@end
@implementation Jason
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"123");
}
return 0;
}
复制代码
在文件的路径下,能够终端输入命令,就能够查看main.cpp文件
clang -rewrite-objc main.m -o main.cpp
复制代码
从中能够看到
#ifndef _REWRITER_typedef_Jason
#define _REWRITER_typedef_Jason
typedef struct objc_object Jason;
typedef struct {} _objc_exc_Jason;
#endif
extern "C" unsigned long OBJC_IVAR_$_Jason$_name;
struct Jason_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *nickName;
NSString *_name;
};
// @property (nonatomic, copy) NSString *name;
/* @end */
// @implementation Jason
static NSString * _I_Jason_name(Jason * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_Jason$_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
static void _I_Jason_setName_(Jason * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Jason, _name), (id)name, 0, 1); }
// @end
struct NSObject_IMPL {
Class isa;
};
复制代码
从中能够看到,对象最终会被编译成结构体struct
,NSObject_IMPL
里面包含着isa
,在类里面定义的属性name
和成员变量nickName
也是在Jason_IMPL
结构体里面,可是属性变量name
是有getter
方法_I_Jason_name
和setter
方法_I_Jason_setName_
的,而成员变量nickName
是没有的。而且这些方法里面都有默认带有两个参数id self
和SEL _cmd
,这样就很好解释了咱们在方法中能够直接调用self
。
经过上面的内容能够了解到isa
是一个isa_t
类型的联合体(union),而且里面的属性是互斥的,isa
的大小占8字节。通常状况下都是在bits
下的位域来存储内容,其中ISA_BITFIELD
在x86
和arm64
架构下都是64位,可是里面的属性的占位有点区别的。isa
是关联着对象与类的,而且对象的本质就是一个结构体。至此有关isa
的原理介绍到此结束。