一.前言
基于objc4-781
基于64bit运行环境
二.抛砖引玉:起源的问题
1.下面的代码输出什么?
@implementation Son : Father
- (instancetype)init {
self = [super init];
if (self) {
NSLog(@"%@", NSStringFromClass([self class]));
NSLog(@"%@", NSStringFromClass([super class]));
}
return self;
}
@end
2.下面代码的结果
BOOL res1 = [(id)NSObject class] isKindOfClass:[NSObject class]];
BOOL res2 = [(id)NSObject class] isMemberfClass:[NSObject class]];
BOOL res3 = [(id)Sark class] isKindOfClass:[Sark class]];
BOOL res4 = [(id)Sark class] isMemberfClass:[Sark class]];
3.下面的代码会?compile Error / Runtime Crash / NSLog…?
@interface NSObject (Sark)
- (void)foo;
@end
@implementation NSObject (Sark)
- (void)foo {
NSLog(@"IMP:%@",NSStringFromSelector(_cmd));
}
@end
// 测试代码
[NSObject foo];
[[NSObject new] foo];
4.下面的代码会?compile Error / Runtime Crash / NSLog…?
@interface Sark : NSObject
@property (nonatomic, copy) NSString *name;
@end
@implementation Sark
- (void)speak {
NSLog(@"my name's %@", self.name);
}
@end
// 测试代码
id cls = [Sark class];
void *obj = &cls;
[(__bridge id)obj speak];
以上问题转载
https://www.jianshu.com/p/9d649ce6d0b8
三.认识OC对象类型与结构
在iOS当中,一共有3种对象,分别为
实例对象(
instance
)
类对象(class
)
元类对象(meta-class
)
通过基于objc2
源码分析得到NSObject
的结构
typedef struct objc_class *Class;
typedef struct objc_object *id;
// NSObject转为C++的结构,实质只有一个Class的成员
struct NSObject_IMPL {
Class isa;
};
// Class是objc_class别名,是继承于objc_object
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
}
struct objc_object {
private:
isa_t isa;
public:
// initIsa() should be used to init the isa of new objects only.
// If this object already has an isa, use changeIsa() for correctness.
// initInstanceIsa(): objects with no custom RR/AWZ
// initClassIsa(): class objects
// initProtocolIsa(): protocol objects
// initIsa(): other objects
void initIsa(Class cls /*nonpointer=false*/);
void initClassIsa(Class cls /*nonpointer=maybe*/);
void initProtocolIsa(Class cls /*nonpointer=maybe*/);
void initInstanceIsa(Class cls, bool hasCxxDtor);
}
(1)id
类型实际上是objc_object
类型的别称
(2)Class
类型实际上是objc_class
类型的别称
(3)objc_class
是继承于objc_object
(4)类
描述了对象
的结构,元类
描述了类
的结构,根元类
描述了元类
的结构,根元类
描述了自己的结构。
(5)所有衍生的对象或类,都是objc_object
的子类,objc_object
的superclass
为nil
如此一来:所有的对象(实例对象、类对象、元类对象)的查找方法的机制就完全统一
实例对象、类对象、元类对象之间的关系图
(1)Instance对象
的isa
指向类对象
,类对象
的isa
指向元类对象
,元类对象
的isa
指向根元类对象
,根元类对象
的isa
指向自己
(2)Root class (class)
其实就是NSObject
,其superclass
为nil
(3)所有的Meta class
的isa
指针都指向Root class (meta)
(4)类对象和元类对象是唯一
的,类的实例对象是不限的
四.认识isa_t
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
};
objc_objec
t里面的isa
是isa_t
类型。通过查看源码,我们可以知道isa_t
是一个union
联合体。
union
叫共用体,又叫联合、联合体。“联合体”是一种特殊的类,也是一种构造类型的数据结构。在一个“联合体”内能够定义多种不同的数据类型。一个被说明为该“联合体”类型的变量中。同意装入该“联合体”所定义的不论什么一种数据。这些数据共享同一段内存,以达到节省空间的目的。
inline void
objc_object::initIsa(Class cls)
{
initIsa(cls, false, false);
}
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
ASSERT(!cls->instancesRequireRawIsa());
ASSERT(hasCxxDtor == cls->hasCxxDtor());
initIsa(cls, true, hasCxxDtor);
}
inline void
objc_object::initClassIsa(Class cls)
{
if (DisableNonpointerIsa || cls->instancesRequireRawIsa()) {
initIsa(cls, false/*not nonpointer*/, false);
} else {
initIsa(cls, true/*nonpointer*/, false);
}
}
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
if (!nonpointer) {
isa = isa_t((uintptr_t)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;
}
}
# 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
ISA_MAGIC_VALUE = 0x000001a000000001ULL转换成二进制是11010000000000000000000000000000000000001,结构如下图:
为了节省内存和提高执行效率,苹果提出了
Tagged Pointer
的概念。对于 64 位程序,引入Tagged Pointer
后,相关逻辑能减少一半的内存占用,以及 3 倍的访问速度提升,100 倍的创建、销毁速度提升。
正常情况下,如果这个整数只是一个NSInteger
的普通变量,那么它所占用的内存是与CPU的位数有关,在32位CPU下占4个字节,在64位CPU下是占8个字节的。而指针类型的大小通常也是与CPU位数相关,一个指针所占用的内存在32位CPU下为4个字节,在64位CPU下也是8个字节。如果没有Tagged Pointer
对象,从32位机器迁移到64位机器中后,虽然逻辑没有任何变化,但这种NSNumber
、NSDate
一类的对象所占用的内存会翻倍。如下图所示:
苹果提出了Tagged Pointer
对象。由于NSNumber
、NSDate
一类的变量本身的值需要占用的内存大小常常不需要8个字节,拿整数来说,4个字节所能表示的有符号整数就可以达到20多亿(注:2^31=2147483648,另外1位作为符号位),对于绝大多数情况都是可以处理的。所以,引入了Tagged Pointer
对象之后,64位CPU下NSNumber
的内存图变成了以下这样:
Tagged Pointer
的引入也带来了问题,即Tagged Pointer
因为并不是真正的对象,而是一个伪对象,所以你如果完全把它当成对象来使,可能会让它露马脚。比如我在 《Objective-C 对象模型及应用》 一文中就写道,所有对象都有isa
指针,而Tagged Pointer
其实是没有的,因为它不是真正的对象。
(1)has_assoc
:对象含有或者曾经含有关联引用,没有关联引用的可以更快地释放内存
(2)has_cxx_dtor
:表示该对象是否有 C++ 或者 Objc 的析构器
(3)shiftcls
:类的指针,arm64架构中有33位可以存储类指针。
源码中isa.shiftcls = (uintptr_t)cls >> 3;
将当前地址右移三位的主要原因是用于将 Class 指针中无用的后三位清除减小内存的消耗,因为类的指针要按照字节(8 bits)对齐内存,其指针后三位都是没有意义的 0。具体可以看从NSObject
的初始化了解 isa这篇文章里面的shiftcls分析。
(4)magic
:判断对象是否初始化完成,在arm64中0x16是调试器判断当前对象是真的对象还是没有初始化的空间。
(5)weakly_referenced
:对象被指向或者曾经指向一个 ARC 的弱变量,没有弱引用的对象可以更快释放
(6)deallocating
:对象是否正在释放内存
(7)has_sidetable_rc
:判断该对象的引用计数是否过大,如果过大则需要其他散列表来进行存储。
(8)extra_rc
:放该对象的引用计数值减一后的结果,对象的引用计数超过 1,会存在这个这个里面,如果引用计数为 10,extra_rc
的值就为 9。
(9)ISA_MAGIC_MASK
和 ISA_MASK
分别是通过掩码的方式获取MAGIC值 和 isa类指针。
五.cache_t的具体实现
Cache
的作用主要是为了优化方法调用的性能。当对象receiver
调用方法message
时,首先根据对象receiver
的isa
指针查找到它对应的类,然后在类的methodLists
中搜索方法,如果没有找到,就使用super_class
指针到父类中的methodLists
查找,一旦找到就调用方法。如果没有找到,有可能消息转发,也可能忽略它。但这样查找方式效率太低,因为往往一个类大概只有20%的方法经常被调用,占总调用次数的80%。所以使用Cache
来缓存经常调用的方法,当调用方法时,优先在Cache
查找,如果没有找到,再到methodLists
查找。
objc_class
中的cache
是cache_t
类型,如下:
(1)cache_t
能缓存调用过的方法。
(2)cache_t
的三个成员变量中,
_buckets
的类型是struct bucket_t *
,也就是指针数组,它表示一系列的哈希桶(已调用的方法的SEL
和IMP
就缓存在哈希桶中),一个桶可以缓存一个方法。
_mask
的类型是mask_t
(mask_t
在64位架构下就是uint32_t
,长度为4个字节),它的值等于哈希桶的总数-1
(capacity - 1
),侧面反映了哈希桶的总数。
_occupied
的类型也是mask_t
,它代表的是当前_buckets
已缓存的方法数。
(3)当缓存的方法数到达临界点(桶总数的3/4
)时,下次再缓存新的方法时,首先会丢弃旧的桶,同时开辟新的内存,也就是扩容(扩容后都是全新的桶,以后每个方法都要重新缓存的),然后再把新的方法缓存下来,此时_occupied
为1
。
当多个线程同时调用一个方法时,可分以下几种情况:
多线程读缓存
:读缓存由汇编实现,无锁且高效,由于并没有改变_buckets
和_mask
,所以并无安全隐患。
多线程写缓存
:OC用了个全局的互斥锁(cacheUpdateLock.assertLocked()
)来保证不会出现写两次缓存的情况。
多线程读写缓存
:OC使用了ldp汇编指令
、编译内存屏障技术
、内存垃圾回收技术
等多种手段来解决多线程读写的无锁处理方案,既保证了安全,又提升了系统的性能。
更详细的分析可参考OC源码分析之方法的缓存原理
六.class_data_bits_t的具体实现
struct objc_class : objc_object {
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() const {
return bits.data();
}
}
struct class_data_bits_t {
class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
}