写在前边
上篇文章中,介绍了Objc对象的分类:实例对象、类对象、元类对象;也介绍了对象分类中通过isa
或superclass
进行方法查找的流程。今天通过对苹果官方源码objc4-750
对其详细说明。
源码
我们知道,Objective-C
中,通常情况下,我们新建类都会继承于NSObject
。那么,我们就从NSObject开始吧。
我们看到在NSObject
中,包含一个Class isa
成员变量;
Class
接上图,然后我们点击能进入查看Class
的定义。发现她是一结构体objc_class
指针。
同时,我们也看到了我们常用的id
其实是一个叫objc_object
的结构体指针。继续,查看objc_class
的实现
我们看到,objc_class
继承于objc_object
,而且还包含了其他一些信息,稍后再详细介绍这些字段具体作用。
而从前边的截图中,我们也看到了objc_object
就是id
类型!也就是说 Class
继承于id
。
那么objc_object
中包含什么信息呢?
id
继续,查看objc_object
信息。
我们在objc_object
中看到了一个isa_t
类别的成员变量,终于找到了传说中的isa
(异常兴奋!)
isa_t
那么,isa_t
具体是什么类型呢,继续我们发现,她原来是一个union 共用体
,不仅可以用来存储类的指针信息,还可以用来表示位域
(或者位段
,可以用来存储更多的信息)。
位域
详情定义如下:
这里介绍下各个位的作用
- nonpointer:0,代表普通的指针,存储着Class、Meta-Class对象的内存地址;1,代表优化过,使用位域存储更多的信息
- has_assoc:是否有设置过关联对象,如果没有,释放时会更快
- has_cxx_dtor:是否有C++的析构函数(.cxx_destruct),如果没有,释放时会更快
- shiftcls:存储着Class、Meta-Class对象的内存地址信息
- magic:用于在调试时分辨对象是否未完成初始化
- weakly_referenced:是否有被弱引用指向过,如果没有,释放时会更快
- deallocating:对象是否正在释放
- extra_rc:里面存储的值是引用计数器
- has_sidetable_rc:引用计数器是否过大无法存储在isa中;如果为1,那么引用计数会存储在一个叫SideTable的类的属性中
分析
isa_t的位域
首先,使用使用位域
可以节约资源。例如,我们比较两个类实例占用的空间。
@interface Person1 : NSObject
@property (nonatomic, assign) BOOL tall;
@property (nonatomic, assign) BOOL handsome;
@property (nonatomic, assign) NSInteger age;
@end
@interface Person2 : NSObject{
union{
NSInteger bits;
struct {
char tall : 1;
char handsome: 1;
NSInteger age: 7;
} ;
}tallHandsomeAge;
}
-(void)setTall:(BOOL)tall;
-(BOOL)tall;
-(void)setHandsome:(BOOL)handsome;
-(BOOL)handsome;
-(void)setAge:(NSInteger)age;
-(NSInteger)age;
-(NSInteger)bits;
@end
实现代码,如下:
@implementation Person1
@end
#define Tall_Mask (1<<0)
#define handsome_Mask (1<<1)
#define age_offset 2
#define age_Mask (1<<8)
@implementation Person2
-(void)setTall:(BOOL)tall{
if (tall) {
tallHandsomeAge.bits |= Tall_Mask;
}else{
tallHandsomeAge.bits &= ~Tall_Mask;
}
}
-(BOOL)tall{
return !!(tallHandsomeAge.bits & Tall_Mask);
}
-(void)setHandsome:(BOOL)handsome{
if (handsome) {
tallHandsomeAge.bits |= handsome_Mask;
}else{
tallHandsomeAge.bits &= ~handsome_Mask;
}
}
-(BOOL)handsome{
return !!(tallHandsomeAge.bits & handsome_Mask);
}
-(void)setAge:(NSInteger)age{
tallHandsomeAge.bits &= (age_Mask | Tall_Mask | handsome_Mask);
tallHandsomeAge.bits |= (age << age_offset);
}
-(NSInteger)age{
return tallHandsomeAge.bits & age_Mask;
}
-(NSInteger)bits{
return tallHandsomeAge.bits;
}
@end
首先,占用空间大小比较优势很明显;其次,位域
使用的位运算
对处理器来说,效率是很高的
isa_t的Class(objc_class)
首先,objc_class
继承自 objc_object
(objc_object包含一个isa_t的成员变量isa)。objc_class
结构体中包含的主要信息如下:
// 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();
}
- ISA指针
- superclass
- cache方法缓存
- bits具体的类信息(结构体class_rw_t)
- 方法列表
- 属性列表
- 协议列表
- class_ro_t *ro
- instance对象占用的内存大小等
- 成员变量列表
cache方法缓存
Class
中有个方法缓存cache_t
,用哈希表来缓存曾经调用过的方法,以提高方法的查找速度。
struct cache_t {
struct bucket_t *_buckets;//散列表
mask_t _mask;//总长度
mask_t _occupied;//已缓存的方法数量
//other information
}
struct bucket_t {
private:
// IMP-first is better for arm64e ptrauth and no worse for arm64.
// SEL-first is better for armv7* and i386 and x86_64.
#if __arm64__
MethodCacheIMP _imp;//函数的内存地址
cache_key_t _key;//SEL
#else
cache_key_t _key;//SEL
MethodCacheIMP _imp;//函数的内存地址
#endif
//other information
}
之前,我们介绍过objc_msgSend
消息对应方法查找流程。这里再简单介绍下:
- 先查找自己类的缓存中是否有对应的方法,如果能找到直接发消息调用
- 查找自己类中的bits类信息中的方法列表,如果能找到,则发消息,将方法放入方法缓存中。
- 遍历父类的方法。
bucket_t * cache_t::find(cache_key_t k, id receiver)
{
assert(k != 0);
bucket_t *b = buckets();
mask_t m = mask();
mask_t begin = cache_hash(k, m);
mask_t i = begin;
do {
if (b[i].key() == 0 || b[i].key() == k) {
return &b[i];
}
} while ((i = cache_next(i, m)) != begin);
// hack
Class cls = (Class)((uintptr_t)this - offsetof(objc_class, cache));
cache_t::bad_cache(receiver, (SEL)k, cls);
}
#if __arm__ || __x86_64__ || __i386__
// objc_msgSend has few registers available.
// Cache scan increments and wraps at special end-marking bucket.
#define CACHE_END_MARKER 1
static inline mask_t cache_next(mask_t i, mask_t mask) {
return (i+1) & mask;
}
#elif __arm64__
// objc_msgSend has lots of registers available.
// Cache scan decrements. No end marker needed.
#define CACHE_END_MARKER 0
static inline mask_t cache_next(mask_t i, mask_t mask) {
return i ? i-1 : mask;
}
#else
#error unknown architecture
#endif
bits具体的类信息class_rw_t
这里主要介绍的是方法列表 method_array_t methods
。具体method_t的结构如下:
struct method_t {
SEL name;//函数名称
const char *types;//函数编码,在swazzing中用的比较多
MethodListIMP imp;//函数指针(函数地址)
struct SortBySELAddress :
public std::binary_function
{
bool operator() (const method_t& lhs,
const method_t& rhs)
{ return lhs.name < rhs.name; }
};
};
参照
https://opensource.apple.com/tarballs/objc4/