美文网首页
探索通过runtime能否给类添加成员变量

探索通过runtime能否给类添加成员变量

作者: Hamiltion | 来源:发表于2020-11-05 19:36 被阅读0次

先看看添加成员变量的API

class_addIvar(Class cls,const char *name, size_t size,
uint8_t alignment, const char *type)

  • 第一个参数 Class cls: 需要添加成员变量的类
  • 第二个参数 const char *name:要添加的成员变量名称
  • 第三个参数 size_t size:成员变量占用内存大小
  • 第四个参数 uint8_t alignment: 这个没做过多研究,大概是跟内存对齐有关的
  • 第五个参数 const char *type:表示要添加的成员变量类型,数据类型在底层的表示符号,可以用@encode()获取,比如 int 用 i 表示

测试是否能给类添加成员变量

  • 比如我们写一个Person类继承自NSObject,测试代码如下:
    Class cls = [Person class];
    BOOL isSuccess = class_addIvar(cls, "_age", sizeof(int), 0, @encode(int));
    NSLog(@"%@", isSuccess ? @"添加成员变量成功" : @"添加成员变量失败");

打印结果:

   2020-11-05 17:16:00.752547+0800 ZZTest[72797:9439487] 添加成员变量失败

经测试添加成员变量失败了

  • 如果使用runtime创建一个类,能添加成员变量吗,测试代码如下:
    Class cls = objc_allocateClassPair([NSObject class], "TestClass", 8);
    BOOL isSuccess = class_addIvar(cls, "_test_ivar", sizeof(int), 0, @encode(int));
    NSLog(@"%@", isSuccess ? @"添加成员变量成功" : @"添加成员变量失败");

打印结果如下:

    2020-11-05 17:20:56.746436+0800 ZZTest[72917:9443074] 添加成员变量成功

经测试runtime创建的类可以添加成员变量

为什么runtime创建的类可以添加成员变量,常规的类不能添加成员变量?

  • 要想解开谜底,当然需要看添加成员变量这个方法的源码,我从源码中找出了关键代码,如下:
    #define RW_CONSTRUCTING       (1<<26)
    // Can only add ivars to in-construction classes.
    if (!(cls->data()->flags & RW_CONSTRUCTING)) {
        return NO;
    }

从注释“Can only add ivars to in-construction classes”可以看出,只有当类构建过程中才能添加成员变量,显然我们常规的类,在APP启动的时候就已经构建完成了,所以不能添加成员变量,而通过objc_allocateClassPair方法创建的类,如果不去修改是否在构建过程的标识,始终处于构建过程中。
从源码中就可以看出 cls->data()->flags 中存储着是否是在构建中的标记, cls->data()->flags & RW_CONSTRUCTING 表示是否是在构建中,RW_CONSTRUCTING 等于 1左移26位,也就是说cls->data()->flags从右到左第27位表示类是否在构建过程中(1表示在构建过程中,0表示不在构建过程中)

  • 验证cls->data()->flags从右到左第27位是否表示处于构建过程中的标记,把常规的类的flags构建标志位改掉,看是否可以添加成员变量,先看下flags的值,及第27位的值,常规方式我们无法获取到flags,我们需要模拟Class的底层结构,把class转成我们自己写的结构体,才能获取到flags,我们先导入写好的Class底层结构关键代码,这个代码出自李明杰,代码如下:
// MJClassInfo.h文件内容

#import <Foundation/Foundation.h>
#ifndef MJClassInfo_h
#define MJClassInfo_h

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
# endif

#if __LP64__
typedef uint32_t mask_t;
#else
typedef uint16_t mask_t;
#endif
typedef uintptr_t cache_key_t;

struct bucket_t {
    cache_key_t _key;
    IMP _imp;
};

struct cache_t {
    bucket_t *_buckets;
    mask_t _mask;
    mask_t _occupied;
};

struct entsize_list_tt {
    uint32_t entsizeAndFlags;
    uint32_t count;
};

struct method_t {
    SEL name;
    const char *types;
    IMP imp;
};

struct method_list_t : entsize_list_tt {
    method_t first;
};

struct ivar_t {
    int32_t *offset;
    const char *name;
    const char *type;
    uint32_t alignment_raw;
    uint32_t size;
};

struct ivar_list_t : entsize_list_tt {
    ivar_t first;
};

struct property_t {
    const char *name;
    const char *attributes;
};

struct property_list_t : entsize_list_tt {
    property_t first;
};

struct chained_property_list {
    chained_property_list *next;
    uint32_t count;
    property_t list[0];
};

typedef uintptr_t protocol_ref_t;
struct protocol_list_t {
    uintptr_t count;
    protocol_ref_t list[0];
};

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;  // instance对象占用的内存空间
#ifdef __LP64__
    uint32_t reserved;
#endif
    const uint8_t * ivarLayout;
    const char * name;  // 类名
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;  // 成员变量列表
    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;
};

struct class_rw_t {
    uint32_t flags;
    uint32_t version;
    const class_ro_t *ro;
    method_list_t * methods;    // 方法列表
    property_list_t *properties;    // 属性列表
    const protocol_list_t * protocols;  // 协议列表
    Class firstSubclass;
    Class nextSiblingClass;
    char *demangledName;
};

#define FAST_DATA_MASK          0x00007ffffffffff8UL
struct class_data_bits_t {
    uintptr_t bits;
public:
    class_rw_t* data() {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
};

/* OC对象 */
struct mj_objc_object {
    void *isa;
};

/* 类对象 */
struct mj_objc_class : mj_objc_object {
    Class superclass;
    cache_t cache;
    class_data_bits_t bits;
public:
    class_rw_t* data() {
        return bits.data();
    }
    
    mj_objc_class* metaClass() {
        return (mj_objc_class *)((long long)isa & ISA_MASK);
    }
};

#endif /* MJClassInfo_h */

在测试的代码里面导入MJClassInfo.h,并将.m文件改成.mm文件,打印flags的代码如下:

    Class cls = [Person class];
    NSLog(@"Person 的 flags = %p", ((__bridge struct mj_objc_class *)cls)->data()->flags);
    
    Class cls1 = [UIViewController class];
    NSLog(@"UIViewController 的 flags = %p", ((__bridge struct mj_objc_class *)cls1)->data()->flags);

    Class cls2 = objc_allocateClassPair([NSObject class], "TestClass", 8);
    NSLog(@"runtime创建的TestClass 的 flags = %p", ((__bridge struct mj_objc_class *)cls2)->data()->flags);
    
    Class cls3 = objc_allocateClassPair([NSObject class], "TestClass1", 8);
    NSLog(@"runtime创建的TestClass1 的 flags = %p", ((__bridge struct mj_objc_class *)cls3)->data()->flags);

带引结果如下:

2020-11-05 18:23:40.756127+0800 ZZTest[74302:9484306] Person 的 flags = 0x80080000
2020-11-05 18:23:40.756291+0800 ZZTest[74302:9484306] UIViewController 的 flags = 0x80080000
2020-11-05 18:23:40.756411+0800 ZZTest[74302:9484306] runtime创建的TestClass 的 flags = 0x8c080000
2020-11-05 18:23:40.756524+0800 ZZTest[74302:9484306] runtime创建的TestClass1 的 flags = 0x8c080000

从打印结果可以看出:
常规的类Person、UIViewController的flags = 0x80080000,第27位是0
runtime创建的类TestClass、TestClass1的flags = 0x8c080000,第27位是1
两种类flags的第27位是不一样的

如果我们把runtime创建的类的flags的27位由1改成0,看是否可以添加成员变量,代码如下:

    Class cls = objc_allocateClassPair([NSObject class], "TestClass", 8);
    NSLog(@"改之前的 flags = %p", ((__bridge struct mj_objc_class *)cls)->data()->flags);
    BOOL isSuccess = class_addIvar(cls, "_test_ivar", sizeof(int), 0, @encode(int));
    NSLog(@"修改flags前,%@", isSuccess ? @"添加成员变量成功" : @"添加成员变量失败");
    ((__bridge struct mj_objc_class *)cls)->data()->flags = 0x88080000;
    NSLog(@"改之前的 flags = %p", ((__bridge struct mj_objc_class *)cls)->data()->flags);
    BOOL isSuccess1 = class_addIvar(cls, "_test_ivar_1", sizeof(int), 0, @encode(int));
    NSLog(@"修改flags后,%@", isSuccess1 ? @"添加成员变量成功" : @"添加成员变量失败");

打印结果如下:

2020-11-05 18:40:26.430563+0800 ZZTest[74710:9495999] 改之前的 flags = 0x8c080000
2020-11-05 18:40:26.430715+0800 ZZTest[74710:9495999] 修改flags前,添加成员变量成功
2020-11-05 18:40:26.430841+0800 ZZTest[74710:9495999] 改之前的 flags = 0x88080000
2020-11-05 18:40:26.430955+0800 ZZTest[74710:9495999] 修改flags后,添加成员变量失败

从打印结果可以看出,runtime创建的类在修改flags的27前是可以添加成员变量的,当把27位由1改成0后(从RW_CONSTRUCTING的意思和前面那段注释来看,大概就是从类构建中改成了类构建完成),就不能添加成员变量了

结论

从以上分析,充分证实了,类能不能在运行时添加成员变量,class内部中的flags从右到左第27位(也就是calss是否在构建过程中)是很关键的因素(至于是不是绝对因素还有待进一步分析)
在不修改class内部成员变量的情况下,常规的类不能添加成员变量,runtime创建的类可以添加成员变量

相关文章

网友评论

      本文标题:探索通过runtime能否给类添加成员变量

      本文链接:https://www.haomeiwen.com/subject/eopavktx.html