写在前边

上篇文章中,介绍了Objc对象的分类:实例对象、类对象、元类对象;也介绍了对象分类中通过isasuperclass进行方法查找的流程。今天通过对苹果官方源码objc4-750对其详细说明。

源码

我们知道,Objective-C中,通常情况下,我们新建类都会继承于NSObject。那么,我们就从NSObject开始吧。

NSObject.png

我们看到在NSObject中,包含一个Class isa成员变量;

Class

接上图,然后我们点击能进入查看Class的定义。发现她是一结构体objc_class指针。

Class.png

同时,我们也看到了我们常用的id其实是一个叫objc_object的结构体指针。继续,查看objc_class的实现

objc_class.png

我们看到,objc_class继承于objc_object,而且还包含了其他一些信息,稍后再详细介绍这些字段具体作用。

而从前边的截图中,我们也看到了objc_object就是id类型!也就是说 **Class继承于id**。

那么objc_object中包含什么信息呢?

id

继续,查看objc_object信息。

objc_object.png

我们在objc_object中看到了一个isa_t类别的成员变量,终于找到了传说中的isa异常兴奋!

isa_t

那么,isa_t具体是什么类型呢,继续我们发现,她原来是一个union 共用体,不仅可以用来存储类的指针信息,还可以用来表示位域(或者位段,可以用来存储更多的信息)。

isa_t-1.png

位域详情定义如下:

isa_t-2.png

这里介绍下各个位的作用

  • 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的位域

首先,使用使用位域可以节约资源。例如,我们比较两个类实例占用的空间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@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

实现代码,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@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

首先,占用空间大小比较优势很明显;其次,位域使用的位运算对处理器来说,效率是很高的

bit_field.png

isa_t的Class(objc_class)

首先,objc_class继承自 objc_object(objc_object包含一个isa_t的成员变量isa)。objc_class结构体中包含的主要信息如下:

1
2
3
4
5
6
7
8
// 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();
}

class_rw_t.png

class_ro_t.png

  • ISA指针
  • superclass
  • cache方法缓存
  • bits具体的类信息(结构体class_rw_t)
    • 方法列表
    • 属性列表
    • 协议列表
    • class_ro_t *ro
      • instance对象占用的内存大小等
      • 成员变量列表
cache方法缓存

Class中有个方法缓存cache_t,用哈希表来缓存曾经调用过的方法,以提高方法的查找速度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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类信息中的方法列表,如果能找到,则发消息,将方法放入方法缓存中。
  • 遍历父类的方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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的结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct method_t {
SEL name;//函数名称
const char *types;//函数编码,在swazzing中用的比较多
MethodListIMP imp;//函数指针(函数地址)

struct SortBySELAddress :
public std::binary_function<const method_t&,
const method_t&, bool>
{
bool operator() (const method_t& lhs,
const method_t& rhs)
{ return lhs.name < rhs.name; }
};
};

参照

https://opensource.apple.com/tarballs/objc4/