详解:Objective-C Class Ivar Layout

移动开发 iOS
这次探索源于一个朋友问的问题,当我们定义一个类的实例变量的时候,可以指定其修饰符:

这次探索源于一个朋友问的问题,当我们定义一个类的实例变量的时候,可以指定其修饰符:

  1. @interface Sark : NSObject { 
  2. __strong id _gayFriend; // 无修饰符的对象默认会加 __strong 
  3. __weak id _girlFriend; 
  4. __unsafe_unretained id _company; 
  5. @end 

这使得 ivar (instance variable) 可以像属性一样在 ARC 下进行正确的引用计数管理。

那么问题来了,假如这个类是动态生成的:

  1. Class class = objc_allocateClassPair(NSObject.class"Sark"0); 
  2. class_addIvar(class"_gayFriend", sizeof(id), log2(sizeof(id)), @encode(id)); 
  3. class_addIvar(class"_girlFriend", sizeof(id), log2(sizeof(id)), @encode(id)); 
  4. class_addIvar(class"_company", sizeof(id), log2(sizeof(id)), @encode(id)); 
  5. objc_registerClassPair(class); 

该如何像上面一样来添加 ivar 的属性修饰符呢?

刨根问底了一下,发现 ivar 的修饰信息存放在了 Class 的 Ivar Layout 中:

  1. struct class_ro_t { 
  2. uint32_t flags; 
  3. uint32_t instanceStart; 
  4. uint32_t instanceSize; 
  5. #ifdef __LP64__ 
  6. uint32_t reserved; 
  7. #endif 
  8. const uint8_t * ivarLayout; // <- 记录了哪些是 strong 的 ivar 
  9.  
  10. const char * name; 
  11. const method_list_t * baseMethods; 
  12. const protocol_list_t * baseProtocols; 
  13. const ivar_list_t * ivars; 
  14. const uint8_t * weakIvarLayout; // <- 记录了哪些是 weak 的 ivar 
  15. const property_list_t *baseProperties; 
  16. }; 

ivarLayout 和 weakIvarLayout 分别记录了哪些 ivar 是 strong 或是 weak,都未记录的就是基本类型和 __unsafe_unretained 的对象类型。

这两个值可以通过 runtime 提供的几个 API 来访问:

  1. const uint8_t *class_getIvarLayout(Class cls) 
  2. const uint8_t *class_getWeakIvarLayout(Class cls) 
  3. void class_setIvarLayout(Class cls, const uint8_t *layout) 
  4. void class_setWeakIvarLayout(Class cls, const uint8_t *layout) 

但我们几乎没可能用到这几个 API,IvarLayout 的值由 runtime 确定,没必要关心它的存在,但为了解决上述问题,我们试着破解了 IvarLayout 的编码方式。

举个例子说明,若类定义为:

  1. @interface Foo : NSObject { 
  2. __strong id ivar0; 
  3. __weak id ivar1; 
  4. __weak id ivar2; 
  5. @end 

则储存 strong ivar 的 ivarLayout 的值为 0x012000

储存 weak ivar 的 weakIvarLayout 的值为 0x1200

一个 uint8_t 在 16 进制下是两位,所以编码的值每两位一对儿,以上面的 ivarLayout 为例:

前两位 01 表示有 0 个非 strong 对象和 1 个 strong 对象

之后两位 20 表示有 2 个非 strong 对象和 0 个 strong 对象

***两位 00 为结束符,就像 cstring 的 \0 一样

同理,上面的 weakIvarLayout:

前两位 12 表示有 1 个非 weak 对象和接下来连续 2 个 weak 对象

00 结束符

这样,用两个 layout 编码值就可以排查出一个 ivar 是属于 strong 还是 weak 的,若都没有找到,就说明这个对象是 unsafe_unretained.

做个练习,若类定义为:

  1. @interface Bar : NSObject { 
  2. __weak id ivar0; 
  3. __strong id ivar1; 
  4. __unsafe_unretained id ivar2; 
  5. __weak id ivar3; 
  6. __strong id ivar4; 
  7. @end 

则储存 strong ivar 的 ivarLayout 的值为 0x012100

储存 weak ivar 的 weakIvarLayout 的值为 0x01211000

  1. Class class = objc_allocateClassPair(NSObject.class"Sark"0); 
  2. class_addIvar(class"_gayFriend", sizeof(id), log2(sizeof(id)), @encode(id)); 
  3. class_addIvar(class"_girlFriend", sizeof(id), log2(sizeof(id)), @encode(id)); 
  4. class_addIvar(class"_company", sizeof(id), log2(sizeof(id)), @encode(id)); 
  5. class_setIvarLayout(class, (const uint8_t *)"\x01\x12"); // <--- new 
  6. class_setWeakIvarLayout(class, (const uint8_t *)"\x11\x10"); // <--- new 
  7. objc_registerClassPair(class); 

本以为解决了这个问题,但是 runtime 继续打脸,strong 和 weak 的内存管理并没有生效,继续研究发现, class 的 flags 中有一个标记位记录这个类是否 ARC,正常编译的类,且标识了 -fobjc-arc flag 时,这个标记位为 1,而动态创建的类并没有设置它。所以只能继续黑魔法,运行时把这个标记位设置上,探索过程不赘述了,实现如下:
 

  1. static void fixup_class_arc(Class class) { 
  2. struct { 
  3. Class isa; 
  4. Class superclass; 
  5. struct { 
  6. void *_buckets; 
  7. uint32_t _mask; 
  8. uint32_t _occupied; 
  9. } cache; 
  10. uintptr_t bits; 
  11. } *objcClass = (__bridge typeof(objcClass))class
  12. #if !__LP64__ 
  13. #define FAST_DATA_MASK 0xfffffffcUL 
  14. #else 
  15. #define FAST_DATA_MASK 0x00007ffffffffff8UL 
  16. #endif 
  17. struct { 
  18. uint32_t flags; 
  19. uint32_t version; 
  20. struct { 
  21. uint32_t flags; 
  22. } *ro; 
  23. } *objcRWClass = (typeof(objcRWClass))(objcClass->bits & FAST_DATA_MASK); 
  24. #define RO_IS_ARR 1flags |= RO_IS_ARR; 

把这个 fixup 放在 objc_registerClassPair(class); 之后,这个动态的类终于可以像静态编译的类一样操作 ivar 了,可以测试一下:

  1. id sark = [class new]; 
  2. Ivar weakIvar = class_getInstanceVariable(class"_girlFriend"); 
  3. Ivar strongIvar = class_getInstanceVariable(class"_gayFriend"); 
  4. id girl = [NSObject new]; 
  5. id boy = [NSObject new]; 
  6. object_setIvar(sark, weakIvar, girl); 
  7. object_setIvar(sark, strongIvar, boy); 
  8. // ARC 在这里会释放大括号内的 girl,boy 
  9. // 输出:weakIvar 为 nil,strongIvar 有值 
  10. NSLog(@"%@, %@", object_getIvar(sark, weakIvar), object_getIvar(sark, strongIvar)); 

Done.

责任编辑:chenqingxiang 来源: sunnyxx的技术博客
相关推荐

2011-07-29 16:16:30

Objective-c block

2011-08-17 10:58:59

Objective-C构造函数

2014-04-01 10:50:42

iOS开发runtimeObjective-C

2011-07-18 16:36:51

Objective-C XCode

2011-08-17 10:29:39

Objective-C预处理

2011-08-09 15:53:28

2011-08-04 13:38:01

Objective-C C++

2011-08-17 11:05:22

Objective-C方法

2011-07-27 16:55:12

Objective-c 闭包

2011-08-01 17:11:43

Objective-C 函数

2011-08-15 14:32:42

Objective-C委托协议

2014-04-28 09:56:56

Objective-CiOS命名空间

2011-08-16 10:23:04

Objective-CNSAutoreleaXcode常用键

2011-08-16 13:43:40

Objective-C文件cocoa

2011-07-08 18:44:09

Objective-C Self Super

2011-07-29 15:47:21

iPhone开发 Objective- C

2011-08-04 18:14:42

Objective-C 消息

2011-07-27 16:36:03

iphone Objective- 静态库

2011-08-04 10:04:17

Objective-C 分类 协议

2011-08-16 13:34:23

Objective-C归档
点赞
收藏

51CTO技术栈公众号