OC底层原理汇总
从探索isa
的初始化开始
在OC底层原理(一).alloc实际调用流程分析内容最后的流程图中,_class_createInstanceFromZone
,我们分为三步:
- 1、
size = cls->instanceSize(extraBytes);
获取对象需要分配的内存大小 - 2、
obj = (id)calloc(1, size);
如何申请内存 - 3、
obj->initInstanceIsa(cls, hasCxxDtor);
初始化isa
在上一篇中我们分析了获取需要分配的大小,以及具体如何分配内存,也就是第1步和第2步。本篇我们继续对第三步进行探索,也就是isa的创建以及初始化。
分析initInstanceIsa
方法的实现
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
ASSERT(!cls->instancesRequireRawIsa());
ASSERT(hasCxxDtor == cls->hasCxxDtor());
initIsa(cls, true, hasCxxDtor);
}
initInstanceIsa
内部调用了initIsa
,并且传入第一个参数为cls
(类的地址),第二个参数为true
,第三个为hasCxxDtor
,我们看看initIsa
实现
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
if (!nonpointer) {
//如果是纯指针的isa,那么isa仅仅用于存放类地址
isa = isa_t((uintptr_t)cls);
} else {
ASSERT(!DisableNonpointerIsa);
ASSERT(!cls->instancesRequireRawIsa());
//分配一个isa_t变量newisa,并把所有位清零
isa_t newisa(0);
/*
是否支持索引,默认苹果平台都不支持的
SUPPORT_INDEXED_ISA为1表示类表的索引
*/
#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
/*
0x001d 8000 0000 0001ULL
给isa一个初始值0x001d800000000001ULL
在这个初始值中,已经设置了nonpointer和magic的值
*/
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;
}
}
在initIsa
中,做了isa
位域中四个初始化,分别是给magic
、nonpointer
、has_cxx_dtor
,shiftcls
赋了值。
大致流程如下:
在OC底层原理(一).alloc实际调用流程分析内容中,我们知道initIsa
来自于最初的alloc调用
。也就是说,一个类调用alloc
,除了分配内存以及内存对齐外,它还对内部的成员变量isa
作了初始化,每个对象一定有一个叫isa
的成员变量,那么,isa
到底是什么呢?
isa
是什么
使用clang重写分析
我们在main.m
中创建一个继承自NSObject
的类LWPerson
,代码如下:
#import
@interface LWPerson : NSObject
@property (nonatomic,copy) NSString *name;
@property (nonatomic,assign) short age;
@property (nonatomic,assign) BOOL isMan;
@end
@implementation LWPerson
+ (void)LWPersonClassMethod{
NSLog(@"%s",__func__);
}
- (int)LWPersonInstanceMethod{
NSLog(@"%s",__func__);
NSArray *array = @[@1,@2,@3];
return [array[1] intValue];
}
@end
int main(int argc, const char * argv[]) {
LWPerson *person = [LWPerson alloc];
person.name = @"Jobs";
person.age = 5;
person.isMan = YES;
[person LWPersonInstanceMethod];
[LWPerson LWPersonClassMethod];
return 0;
}
我们在终端使用如下clang
命令将main.m
重写为C、C++实现
clang -rewrite-objc main.m -o main.cpp
执行命令后,在目录中生成一个main.cpp
文件,我们打开它,搜索LWPerson
,我们删除和main
中无关的数据后,得到最后的代码如下:
#ifndef _REWRITER_typedef_LWPerson
#define _REWRITER_typedef_LWPerson
typedef struct objc_object LWPerson;
typedef struct {} _objc_exc_LWPerson;
#endif
extern "C" unsigned long OBJC_IVAR_$_LWPerson$_name;
extern "C" unsigned long OBJC_IVAR_$_LWPerson$_age;
extern "C" unsigned long OBJC_IVAR_$_LWPerson$_isMan;
struct LWPerson_IMPL {
//这就是isa
struct NSObject_IMPL NSObject_IVARS;
BOOL _isMan;
short _age;
NSString *_name;
};
// @property (nonatomic,copy) NSString *name;
// @property (nonatomic,assign) short age;
// @property (nonatomic,assign) BOOL isMan;
/* @end */
// @implementation LWPerson
//类方法重写
static void _C_LWPerson_LWPersonClassMethod(Class self, SEL _cmd) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_h4_g62cv3xn0ys3gpzsj_wjmtmh0000gn_T_main_8bac4e_mi_0,__func__);
}
//实例方法重写
static int _I_LWPerson_LWPersonInstanceMethod(LWPerson * self, SEL _cmd) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_h4_g62cv3xn0ys3gpzsj_wjmtmh0000gn_T_main_8bac4e_mi_1,__func__);
NSArray *array = ((NSArray *(*)(Class, SEL, ObjectType _Nonnull const * _Nonnull, NSUInteger))(void *)objc_msgSend)(objc_getClass("NSArray"), sel_registerName("arrayWithObjects:count:"), (const id *)__NSContainer_literal(3U, ((NSNumber *(*)(Class, SEL, int))(void *)objc_msgSend)(objc_getClass("NSNumber"), sel_registerName("numberWithInt:"), 1), ((NSNumber *(*)(Class, SEL, int))(void *)objc_msgSend)(objc_getClass("NSNumber"), sel_registerName("numberWithInt:"), 2), ((NSNumber *(*)(Class, SEL, int))(void *)objc_msgSend)(objc_getClass("NSNumber"), sel_registerName("numberWithInt:"), 3)).arr, 3U);
return ((int (*)(id, SEL))(void *)objc_msgSend)((id)((id (*)(id, SEL, NSUInteger))(void *)objc_msgSend)((id)array, sel_registerName("objectAtIndexedSubscript:"), (NSUInteger)1), sel_registerName("intValue"));
}
//name的getter方法
static NSString * _I_LWPerson_name(LWPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LWPerson$_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
//name的setter方法
static void _I_LWPerson_setName_(LWPerson * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LWPerson, _name), (id)name, 0, 1); }
//age的getter方法
static short _I_LWPerson_age(LWPerson * self, SEL _cmd) { return (*(short *)((char *)self + OBJC_IVAR_$_LWPerson$_age)); }
//age的setter方法
static void _I_LWPerson_setAge_(LWPerson * self, SEL _cmd, short age) { (*(short *)((char *)self + OBJC_IVAR_$_LWPerson$_age)) = age; }
//isMan的setter方法
static BOOL _I_LWPerson_isMan(LWPerson * self, SEL _cmd) { return (*(BOOL *)((char *)self + OBJC_IVAR_$_LWPerson$_isMan)); }
//isMan的setter方法
static void _I_LWPerson_setIsMan_(LWPerson * self, SEL _cmd, BOOL isMan) { (*(BOOL *)((char *)self + OBJC_IVAR_$_LWPerson$_isMan)) = isMan; }
// @end
int main(int argc, const char * argv[]) {
//alloc方法调用
LWPerson *person = ((LWPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LWPerson"), sel_registerName("alloc"));
//调用setName:
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)person, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_h4_g62cv3xn0ys3gpzsj_wjmtmh0000gn_T_main_8bac4e_mi_2);
//调用setAge:
((void (*)(id, SEL, short))(void *)objc_msgSend)((id)person, sel_registerName("setAge:"), (short)5);
//调用setIsMan:
((void (*)(id, SEL, BOOL))(void *)objc_msgSend)((id)person, sel_registerName("setIsMan:"), ((bool)1));
//调用实例方法LWPersonInstanceMethod
((int (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("LWPersonInstanceMethod"));
//调用类方法LWPersonClassMethod
((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LWPerson"), sel_registerName("LWPersonClassMethod"));
return 0;
}
从上面的代码可以看出以下几点:
- 1.OC类和对象底层都是结构体
- 2.每个对象都有一个默认的
isa
变量,它由NSObject
继承而来 - 3.属性的本质,就是
实例变量+setter+getter
- 4.实例变量值的获取,就是对象首地址+地址偏移
- 5.属性值的设置,对于简单类型值,直接通过地址偏移设置,其他底层都是调用了
objc_setProperty
函数。 - 6.方法的调用,都是使用
objc_msgSend
发送消息
OC类和对象底层都是结构体
我们打开objc781
源码,可以在objc-private.h
中找到struct objc_object
的定义,如下所示:
struct objc_object {
private:
isa_t isa;
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA();
// rawISA() assumes this is NOT a tagged pointer object or a non pointer ISA
Class rawISA();
// getIsa() allows this to be a tagged pointer object
Class getIsa();
uintptr_t isaBits() const;
......
}
我们看到struct objc_object
中,有一个成员变量isa
,另外有三个获取isa
的成员方法:
-
Class ISA()
方法用于非taggedPointer对象
获取isa
-
Class rawISA();
方法用于非taggedPointer对象
与非nonpointer对象
获取isa
-
Class getIsa();
方法用于taggedPointer对象
获取isa
另外,它还提供了直接获取isa
中bits
成员的方法uintptr_t isaBits() const
,用const
在函数末尾修饰,代表一个常成员函数,仅能读取。
此外,我们可以看到我们常用于指向对象类型的id
类型,它是struct objc_object *
的别名。
/// A pointer to an instance of a class.
typedef struct objc_object *id;
在objc-runtime-new.h
文件中,我们可以找到struct objc_class
的定义,如下所示:
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() const {
return bits.data();
}
}
从上面可以看出,struct objc_class
继承于objc_object
,它有四个成员:
-
isa
,来自于它的父类,isa_t
类型 -
superclass
,它是一个指向父类的指针 -
cache
,cache_t
类型,存储缓存的结构体 -
bits
,class_data_bits_t
类型的结构体,存储了类中方法、协议、成员列表等的信息。
同样,我们可以找到关于Class
的定义,它是struct objc_class *
的别名
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
对象和类结构如下图
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_t
是一个联合体,它有两个成员,分别为Class cls
和uintptr_t bits
。
我们知道,所谓联合体,也叫共用体,它的所有成员变量共用一段内存,它的大小等于最大的成员变量的大小,所以,我们可以得出,isa_t
内存所占大小为8字节,64位
。
我们再看定义中更重要的一段定义ISA_BITFIELD;
,它在isa.h
中,如下所示
# 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)
我们先不讨论内部的执行结果,我们先谈谈关于联合体中定义struct
,而struct
中定义一连串的成员,这个是什么呢,在C语言中,我们将它叫做位域。
位域
位域,也叫位段,它是一种特殊的结构体类型,其所有成员的长度
均是以二进制位
为单位定义的,结构体的成员被称为位段
,位段定义的一般形式如下:
struct 结构名
{
类型 变量名1:长度
类型 变量名2:长度
……
类型 变量名n:长度
}
位段定义类型必须是int
、unsigned
或signed
中的一种,也就是必须为整形
位段的特性如下:
- 从低位到高位排列
- 一个位段必须存在一个存储单元中,不能跨两个存储单元,如果本单元不够容纳某位段,则从下一个单元开始存储该位段
- 可以用
%d
、%x
、%u
、%o
等格式字符,以整数形式输出位段 - 在数值表达式中引用位段时,系统自动将位段转换为整形数。
isa中的位域意义
isa
位段定义的意义如下图所示
在不同平台下,isa
的内部存储区域如下图
使用lldb
对验证isa
存储类地址
在之前,我们提到struct objc_obejct
的成员函数ISA()
是获取nonpointer 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
}
核心代码为(Class)(isa.bits & ISA_MASK);
,这一步将isa
的数据与一个ISA_MASK
进行按位与,就得到了类的地址信息。
define ISA_MASK 0x0000000ffffffff8ULL
我们使用一开始例子,打断点如下
然后断点执行到这一步时候,我们使用如下命令得到结果
(lldb) x/4gx person
0x10054aa30: 0x001d8001000032f5 0x0000000000050001
0x10054aa40: 0x0000000100002040 0x0000000000000000
(lldb) po 0x001d8001000032f5 & 0x0000000ffffffff8ULL
LWPerson
这样就验证了isa
是存储类信息的。
objc_setProperty
源码分析
objc_setProperty
源码如下:
void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy)
{
bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY);
bool mutableCopy = (shouldCopy == MUTABLE_COPY);
reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy);
}
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
if (offset == 0) {
//如果偏移量为0,就是设置isa,也就是设置类地址信息
object_setClass(self, newValue);
return;
}
id oldValue;
id *slot = (id*) ((char*)self + offset);
if (copy) {
newValue = [newValue copyWithZone:nil];
} else if (mutableCopy) {
newValue = [newValue mutableCopyWithZone:nil];
} else {
if (*slot == newValue) return;
//新值retain
newValue = objc_retain(newValue);
}
if (!atomic) {
oldValue = *slot;
//进行赋值
*slot = newValue;
} else {
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
//进行赋值
*slot = newValue;
slotlock.unlock();
}
//旧值release
objc_release(oldValue);
}
从源码中我们可以看到,retain
、release
的操作在底层已经帮我们完成了,所有涉及引用计数的属性
都要调用到objc_setProperty
方法,这是一种适配器模式的思想,它大大的节约了我们的代码量。