CaptainHook 源码分析(六):动态地创建类 && 获取与设置成员变量的值 && 动态地添加属性

本文介绍了如何使用CaptainHook在运行时动态创建类,添加成员变量、属性及访问方法,包括CHRegisterClass、CHAddIvar、CHPropertyRetainNonatomic、CHPrimitiveProperty等宏接口的使用和区别。

动态地创建类(CHRegisterClass)

CaptainHook.h 中,宏接口 CHRegisterClass 用于在运行时动态地创建类,其定义为:

// @param.name		要动态创建的类的名称(不用加 "" 或者 @"")
// @param.superName	要动态创建的类的父类的名称(不用加 "" 或者 @"")
#define CHRegisterClass(name, superName) for (int _tmp = ({ CHClass(name) = objc_allocateClassPair(CHClass(superName), #name, 0); CHMetaClass(name) = object_getClass(CHClass(name)); CHSuperClass(name) = class_getSuperclass(CHClass(name)); 1; }); _tmp; _tmp = ({ objc_registerClassPair(CHClass(name)), 0; }))

以动态创建 HcgPerson 类的子类 HcgStudent 类为例,如下代码:

#import "HcgPerson.h"
#import "CaptainHook.h"

CHDeclareClass(HcgPerson)
CHDeclareClass(HcgStudent)

CHConstructor
{
    // 需要先加载 HcgStudent 类的父类 HcgPerson 类,才能动态创建 HcgStudent 类
    CHLoadLateClass(HcgPerson);
    
    // 动态创建 HcgPerson 类的子类 HcgStudent 类
    CHRegisterClass(HcgStudent, HcgPerson);
    
    // 因为在宏接口 CHRegisterClass 中已经对 HcgStudent 类的 static CHClassDeclaration_ HcgStudent$ 结构体进行了赋值
    // 所以在调用完宏接口 CHRegisterClass 后,不需要再调用宏接口 CHLoadLateClass 加载 HcgStudent 类
    // CHLoadLateClass(HcgStudent);
}

预处理后的结果为:

#pragma clang module import Foundation                  /* clang -E: implicit import for #import <Foundation/Foundation.h> */

#pragma clang assume_nonnull begin

@interface HcgPerson : NSObject

@property (nonatomic, strong) NSString* name;
@property (nonatomic, assign) int age;

-(instancetype)initWithName:(NSString *)aName age:(int)anAge;
+(instancetype)personWithName:(NSString *)aName age:(int)anAge;

-(NSString *)eatFood:(NSString *)aFood inPlace:(NSString *)aPlace;
+(int)doAdditionWithNum1:(int)num1 num2:(int)num2;

+(instancetype)sharedInstance;

@end

#pragma clang assume_nonnull end
#pragma clang module import ObjectiveC.runtime          /* clang -E: implicit import for #import <objc/runtime.h> */
#pragma clang module import ObjectiveC.message          /* clang -E: implicit import for #import <objc/message.h> */
#pragma clang module import Foundation.NSObject         /* clang -E: implicit import for #import <Foundation/NSObject.h> */
#pragma clang module import Foundation.NSObjCRuntime    /* clang -E: implicit import for #import <Foundation/NSObjCRuntime.h> */

struct CHClassDeclaration_
{
    Class class_;
    Class metaClass_;
    Class superClass_;
};
typedef struct CHClassDeclaration_ CHClassDeclaration_;

static inline Class CHLoadClass_(CHClassDeclaration_* declaration, Class value)
{
    declaration->class_ = value;
    declaration->metaClass_ = object_getClass(value);
    declaration->superClass_ = class_getSuperclass(value);
    return value;
}

__attribute__((unused)) inline __attribute__((always_inline))
static void* CHIvar_(id object, const char * name)
{
    Ivar ivar = class_getInstanceVariable(object_getClass(object), name);
    if (ivar)
        return (void *)&((char *)(__bridge void *)object)[ivar_getOffset(ivar)];
    return ((void*)0);
}
@class HcgPerson;
static CHClassDeclaration_ HcgPerson$;

@class HcgStudent;
static CHClassDeclaration_ HcgStudent$;
static __attribute__((constructor)) void CHConstructor7()
{
    CHLoadClass_(&HcgPerson$, objc_getClass("HcgPerson"));
	
    for
    (
        // 在运行时动态地创建 HcgPerson 类的子类 HcgStudent 类
        // 并对 HcgStudent 类的 static CHClassDeclaration_ HcgStudent$ 结构体进行赋值
        int _tmp =
        ({
            HcgStudent$.class_ = objc_allocateClassPair(HcgPerson$.class_, "HcgStudent", 0);
            HcgStudent$.metaClass_ = object_getClass(HcgStudent$.class_);
            HcgStudent$.superClass_ = class_getSuperclass(HcgStudent$.class_);
            1;
        });
		
        _tmp;
		
        // 将动态创建的 HcgStudent 类注册到运行时中
        _tmp =
        ({
            objc_registerClassPair(HcgStudent$.class_), 0;
        })
    );
}

向动态创建的类中添加成员变量(CHAddIvar)

CaptainHook.h 中,宏接口 CHAddIvar 用于向动态创建的类中添加成员变量,其定义为:

// @param.targetClass	动态创建的类对象
// @param.name			要添加的成员变量的名称(不用加 "" 或者 @"")
// @param.type			要添加的成员变量的类型
#define CHAddIvar(targetClass, name, type) \
	class_addIvar(targetClass, #name, sizeof(type), CHAlignmentForSize_(sizeof(type)), @encode(type))

宏接口 CHAddIvar 会组织传入的宏参数,生成对 RunTime 函数 class_addIvar 的调用:

/*
向类中添加一个新的成员变量

@param.cls 			要修改的类(必须是一个动态创建的类,不能是一个已存在的现有类)
@param.name 		成员变量的名称
@param.size 		成员变量的大小
@param.alignment	成员变量的内存对齐
@param.types 		成员变量的类型编码
@return 			如果成功添加成员变量则返回 YES。否则返回 NO(例如:该类已经包含了一个具有该名称的成员变量)
@note 此函数只能在 objc_allocateClassPair() 之后和 objc_registerClassPair() 之前调用
	  不支持向现有的类中添加成员变量(此函数只能向动态生成的类中添加成员变量)
@note 该类一定不能是一个元类,不支持将成员变量添加到元类中
@note 成员变量的最小字节对齐值为 1<<align
	  成员变量的最小字节对齐取决于成员变量的类型和 CPU 的架构
	  对于任何指针类型的成员变量,请传递 log2(sizeof(pointer_type))
*/
BOOL class_addIvar(Class _Nullable cls, 
				   const char * _Nonnull name, 
				   size_t size, 
				   uint8_t alignment, 
				   const char * _Nullable types);

其中,宏接口 CHAlignmentForSize_ 用于根据(成员变量的大小)计算(成员变量的内存对齐),其定义为:

// @param.size 成员变量的大小
// @note 这里的 __builtin_constant_p(s) 是编译器的内置函数,用于判断给定的表达式 s 在编译时是否是一个常量
//		 如果给定的表达式 s 在编译时是一个常量,则返回 true(1);否则,返回 false(0)
// @note 这里计算出来的值为 1 左移的位数!!!举个例子:
//		 如果成员变量的大小在 0 ~ 1   字节之间,则计算出来的左移位数为 0,则成员变量的最小字节对齐值为 1 << 0 == 1  字节
//		 如果成员变量的大小在 2 ~ 3   字节之间,则计算出来的左移位数为 1,则成员变量的最小字节对齐值为 1 << 1 == 2  字节
//		 如果成员变量的大小在 4 ~ 7   字节之间,则计算出来的左移位数为 2,则成员变量的最小字节对齐值为 1 << 2 == 4  字节
//		 如果成员变量的大小在 8 ~ 15  字节之间,则计算出来的左移位数为 3,则成员变量的最小字节对齐值为 1 << 3 == 8  字节
//		 如果成员变量的大小在 16 ~ 31 字节之间,则计算出来的左移位数为 4,则成员变量的最小字节对齐值为 1 << 4 == 16 字节
//		 如果成员变量的大小在 32 ~ 63 字节之间,则计算出来的左移位数为 5,则成员变量的最小字节对齐值为 1 << 5 == 32 字节
//		 ... ...
#define CHAlignmentForSize_(size) ({ \
	size_t s = size; \
	__builtin_constant_p(s) ? ( \
		(s) & (1 << 31) ? 31 : \
		(s) & (1 << 30) ? 30 : \
		(s) & (1 << 29) ? 29 : \
		(s) & (1 << 28) ? 28 : \
		(s) & (1 << 27) ? 27 : \
		(s) & (1 << 26) ? 26 : \
		(s) & (1 << 25) ? 25 : \
		(s) & (1 << 24) ? 24 : \
		(s) & (1 << 23) ? 23 : \
		(s) & (1 << 22) ? 22 : \
		(s) & (1 << 21) ? 21 : \
		(s) & (1 << 20) ? 20 : \
		(s) & (1 << 19) ? 19 : \
		(s) & (1 << 18) ? 18 : \
		(s) & (1 << 17) ? 17 : \
		(s) & (1 << 16) ? 16 : \
		(s) & (1 << 15) ? 15 : \
		(s) & (1 << 14) ? 14 : \
		(s) & (1 << 13) ? 13 : \
		(s) & (1 << 12) ? 12 : \
		(s) & (1 << 11) ? 11 : \
		(s) & (1 << 10) ? 10 : \
		(s) & (1 <<  9) ?  9 : \
		(s) & (1 <<  8) ?  8 : \
		(s) & (1 <<  7) ?  7 : \
		(s) & (1 <<  6) ?  6 : \
		(s) & (1 <<  5) ?  5 : \
		(s) & (1 <<  4) ?  4 : \
		(s) & (1 <<  3) ?  3 : \
		(s) & (1 <<  2) ?  2 : \
		(s) & (1 <<  1) ?  1 : \
		(s) & (1 <<  0) ?  0 : \
		0 \
	) : (uint32_t)log2f(s); \
})

以向动态创建的 HcgStudent 类中添加 NSString* 类型的成员变量 _addr 为例,如下代码:

#import "HcgPerson.h"
#import "CaptainHook.h"

CHDeclareClass(HcgPerson)
CHDeclareClass(HcgStudent)

CHConstructor
{
    CHLoadLateClass(HcgPerson);
    
    CHRegisterClass(HcgStudent, HcgPerson)
    {
        CHAddIvar(CHClass(HcgStudent), _addr, NSString*);
    }
}

预处理后的结果为:

#pragma clang module import Foundation                  /* clang -E: implicit import for #import <Foundation/Foundation.h> */

#pragma clang assume_nonnull begin

@interface HcgPerson : NSObject

@property (nonatomic, strong) NSString* name;
@property (nonatomic, assign) int age;

-(instancetype)initWithName:(NSString *)aName age:(int)anAge;
+(instancetype)personWithName:(NSString *)aName age:(int)anAge;

-(NSString *)eatFood:(NSString *)aFood inPlace:(NSString *)aPlace;
+(int)doAdditionWithNum1:(int)num1 num2:(int)num2;

+(instancetype)sharedInstance;

@end

#pragma clang assume_nonnull end
#pragma clang module import ObjectiveC.runtime          /* clang -E: implicit import for #import <objc/runtime.h> */
#pragma clang module import ObjectiveC.message          /* clang -E: implicit import for #import <objc/message.h> */
#pragma clang module import Foundation.NSObject         /* clang -E: implicit import for #import <Foundation/NSObject.h> */
#pragma clang module import Foundation.NSObjCRuntime    /* clang -E: implicit import for #import <Foundation/NSObjCRuntime.h> */

struct CHClassDeclaration_
{
    Class class_;
    Class metaClass_;
    Class superClass_;
};
typedef struct CHClassDeclaration_ CHClassDeclaration_;

static inline Class CHLoadClass_(CHClassDeclaration_* declaration, Class value)
{
    declaration->class_ = value;
    declaration->metaClass_ = object_getClass(value);
    declaration->superClass_ = class_getSuperclass(value);
    return value;
}

__attribute__((unused)) inline __attribute__((always_inline))
static void* CHIvar_(id object, const char * name)
{
    Ivar ivar = class_getInstanceVariable(object_getClass(object), name);
    if (ivar)
        return (void *)&((char *)(__bridge void *)object)[ivar_getOffset(ivar)];
    return ((void*)0);
}
@class HcgPerson;
static CHClassDeclaration_ HcgPerson$;

@class HcgStudent;
static CHClassDeclaration_ HcgStudent$;
static __attribute__((constructor)) void CHConstructor7()
{
    CHLoadClass_(&HcgPerson$, objc_getClass("HcgPerson"));

    for
    (
    	// 第一步:在运行时动态地创建 HcgPerson 类的子类 HcgStudent 类
        int _tmp =
        ({
            HcgStudent$.class_ = objc_allocateClassPair(HcgPerson$.class_, "HcgStudent", 0);
            HcgStudent$.metaClass_ = object_getClass(HcgStudent$.class_);
            HcgStudent$.superClass_ = class_getSuperclass(HcgStudent$.class_);
            1;
        });
        
        _tmp;
        
        // 第三步:将动态创建的 HcgStudent 类注册到运行时中
        _tmp = ({ objc_registerClassPair(HcgStudent$.class_), 0; })
     )
	 {
	 	// 第二步:向动态创建的 HcgStudent 类中添加成员变量
        class_addIvar(HcgStudent$.class_,
                      "_addr",
                      sizeof(NSString*),
                      ({
                            size_t s = sizeof(NSString*);
                            __builtin_constant_p(s) ? (
	                            (s) & (1 << 31) ? 31 :
	                            (s) & (1 << 30) ? 30 :
	                            (s) & (1 << 29) ? 29 :
	                            (s) & (1 << 28) ? 28 :
	                            (s) & (1 << 27) ? 27 :
	                            (s) & (1 << 26) ? 26 :
	                            (s) & (1 << 25) ? 25 :
	                            (s) & (1 << 24) ? 24 :
	                            (s) & (1 << 23) ? 23 :
	                            (s) & (1 << 22) ? 22 :
	                            (s) & (1 << 21) ? 21 :
	                            (s) & (1 << 20) ? 20 :
	                            (s) & (1 << 19) ? 19 :
	                            (s) & (1 << 18) ? 18 :
	                            (s) & (1 << 17) ? 17 :
	                            (s) & (1 << 16) ? 16 :
	                            (s) & (1 << 15) ? 15 :
	                            (s) & (1 << 14) ? 14 :
	                            (s) & (1 << 13) ? 13 :
	                            (s) & (1 << 12) ? 12 :
	                            (s) & (1 << 11) ? 11 :
	                            (s) & (1 << 10) ? 10 :
	                            (s) & (1 << 9) ? 9 :
	                            (s) & (1 << 8) ? 8 :
	                            (s) & (1 << 7) ? 7 :
	                            (s) & (1 << 6) ? 6 :
	                            (s) & (1 << 5) ? 5 :
	                            (s) & (1 << 4) ? 4 :
	                            (s) & (1 << 3) ? 3 :
	                            (s) & (1 << 2) ? 2 :
	                            (s) & (1 << 1) ? 1 :
	                            (s) & (1 << 0) ? 0 :
	                            0
                            ) : (uint32_t)log2f(s);
                      }),
                      @encode(NSString*));
	 }
}

获取与设置实例对象中成员变量的值(CHIvar)

CaptainHook.h 中,宏接口 CHIvar 用于获取或者设置实例对象中成员变量的值,其定义为:

// @param.object 实例对象
// @param.name	 成员变量的名称(不用加 "" 或者 @"")
// @param.type	 成员变量的类型
#define CHIvar(object, name, type) \
	(*CHIvarRef(object, name, type))

宏接口 CHIvar 会组织传入的宏参数,生成对宏接口 CHIvarRef 的调用

// @param.object 实例对象
// @param.name 	 成员变量的名称(不用加 "" 或者 @"")
// @param.type	 成员变量的类型
#define CHIvarRef(object, name, type) \
	((type *)CHIvar_(object, #name))

宏接口 CHIvarRef 会组织传入的宏参数,生成对静态函数 CHIvar_ 的调用

// @param.object 实例对象
// @param.name 	 成员变量的名称
__attribute__((unused)) CHInline
static void* CHIvar_(id object, const char * name)
{
	// 调用 RunTime 的 class_getInstanceVariable 函数:获取给定类中给定名称的成员变量的 Ivar 结构体
	Ivar ivar = class_getInstanceVariable(object_getClass(object), name);
	// 如果给定的类中存在给定名称的成员变量,则获取该成员变量的偏移量,并根据该成员变量的偏移量获取指向当前实例对象中该成员变量的值的指针
	// 如果给定的类中不存在给定名称的成员变量,则返回 NULL
	if (ivar)
#ifdef CHHasARC
		return (void*)&((char*)(__bridge void*)object)[ivar_getOffset(ivar)];
#else
		return (void*)&((char*)object)[ivar_getOffset(ivar)];
#endif
	return NULL;
}

如下代码:

#import "HcgPerson.h"
#import "CaptainHook.h"

CHDeclareClass(HcgPerson)

CHConstructor
{
    CHLoadLateClass(HcgPerson);
    
    HcgPerson* person = [CHAlloc(HcgPerson) initWithName:@"hcg" age:20];
    
    // 因为在 ARC 环境下,不允许将非 Objective-C 指针类型(void *)隐式地转换为 Objective-C 指针类型(NSString *)
    // 所以当前源文件需要使用 MRC 的方式编译:Target - Build Phases - 当前源文件 - 添加编译标识(-fno-objc-arc)
    
    NSString* originalName = CHIvar(person, _name, NSString*);
    int originalAge = CHIvar(person, _age, int);
    NSLog(@"original : person.name = %@, person.age = %d", originalName, originalAge);
    
    CHIvar(person, _name, NSString*) = @"hzp";
    CHIvar(person, _age, int) = 30;
    NSLog(@"change : person.name = %@, person.age = %d", person.name, person.age);
}

等价于

#import "HcgPerson.h"
#import "CaptainHook.h"

CHDeclareClass(HcgPerson)

CHConstructor
{
    CHLoadLateClass(HcgPerson);

    HcgPerson* person = [CHAlloc(HcgPerson) initWithName:@"hcg" age:20];

    NSString* originalName = (*CHIvarRef(person, _name, NSString*));
    int originalAge = (*CHIvarRef(person, _age, int));
    NSLog(@"original : person.name = %@, person.age = %d", originalName, originalAge);

    (*CHIvarRef(person, _name, NSString*)) = @"hzp";
    (*CHIvarRef(person, _age, int)) = 30;
    NSLog(@"change : person.name = %@, person.age = %d", person.name, person.age);
}

等价于

#import "HcgPerson.h"
#import "CaptainHook.h"

CHDeclareClass(HcgPerson)

CHConstructor
{
    CHLoadLateClass(HcgPerson);

    HcgPerson* person = [CHAlloc(HcgPerson) initWithName:@"hcg" age:20];

    NSString* originalName = (*((NSString* *)CHIvar_(person, "_name")));
    int originalAge = (*((int *)CHIvar_(person, "_age")));
    NSLog(@"original : person.name = %@, person.age = %d", originalName, originalAge);

    (*((NSString* *)CHIvar_(person, "_name"))) = @"hzp";
    (*((int *)CHIvar_(person, "_age"))) = 30;
    NSLog(@"change : person.name = %@, person.age = %d", person.name, person.age);
}

预处理后的结果为:

#pragma clang module import Foundation                  /* clang -E: implicit import for #import <Foundation/Foundation.h> */

#pragma clang assume_nonnull begin

@interface HcgPerson : NSObject

@property (nonatomic, strong) NSString* name;
@property (nonatomic, assign) int age;

-(instancetype)initWithName:(NSString *)aName age:(int)anAge;
+(instancetype)personWithName:(NSString *)aName age:(int)anAge;

-(NSString *)eatFood:(NSString *)aFood inPlace:(NSString *)aPlace;
+(int)doAdditionWithNum1:(int)num1 num2:(int)num2;

+(instancetype)sharedInstance;

@end

#pragma clang assume_nonnull end
#pragma clang module import ObjectiveC.runtime          /* clang -E: implicit import for #import <objc/runtime.h> */
#pragma clang module import ObjectiveC.message          /* clang -E: implicit import for #import <objc/message.h> */
#pragma clang module import Foundation.NSObject         /* clang -E: implicit import for #import <Foundation/NSObject.h> */
#pragma clang module import Foundation.NSObjCRuntime    /* clang -E: implicit import for #import <Foundation/NSObjCRuntime.h> */

struct CHClassDeclaration_
{
    Class class_;
    Class metaClass_;
    Class superClass_;
};
typedef struct CHClassDeclaration_ CHClassDeclaration_;

static inline Class CHLoadClass_(CHClassDeclaration_* declaration, Class value)
{
    declaration->class_ = value;
    declaration->metaClass_ = object_getClass(value);
    declaration->superClass_ = class_getSuperclass(value);
    return value;
}

__attribute__((unused)) inline __attribute__((always_inline))
static void* CHIvar_(id object, const char * name)
{
    Ivar ivar = class_getInstanceVariable(object_getClass(object), name);
    if (ivar)
        return (void *)&((char *)object)[ivar_getOffset(ivar)];
    return ((void*)0);
}

__attribute__((unused)) inline __attribute__((always_inline))
static void CHScopeReleased(id *sro)
{
    [*sro release];
}
@class HcgPerson;
static CHClassDeclaration_ HcgPerson$;
static __attribute__((constructor)) void CHConstructor6()
{
    CHLoadClass_(&HcgPerson$, objc_getClass("HcgPerson"));

    HcgPerson* person = [((HcgPerson *)[HcgPerson$.class_ alloc]) initWithName:@"hcg" age:20];

    NSString* originalName = (*((NSString* *)CHIvar_(person, "_name")));
    int originalAge = (*((int *)CHIvar_(person, "_age")));
    NSLog(@"original : person.name = %@, person.age = %d", originalName, originalAge);

    (*((NSString* *)CHIvar_(person, "_name"))) = @"hzp";
    (*((int *)CHIvar_(person, "_age"))) = 30;
    NSLog(@"change : person.name = %@, person.age = %d", person.name, person.age);
}

动态地添加属性(CHPropertyRetainNonatomic + CHHookProperty)

CaptainHook.h 中,以下 5 个宏接口都用于动态地向类中添加属性,其定义为

// @param.class	 要添加属性的类的名称(不用加 "" 或者 @"")
// @param.type	 要添加的属性的类型
// @param.getter 要添加的属性的 getter 方法名称(直接写 getter 方法名,不用加关键字 @selector)
// @param.setter 要添加的属性的 setter 方法名称(直接写 setter 方法名,不用加关键字 @selector,不用加冒号 : )

// 向给定的类中动态地添加一个 (atomic, retain) 的属性
#define CHPropertyRetain(class, type, getter, setter) CHProperty(class, type, getter, setter, OBJC_ASSOCIATION_RETAIN)
// 向给定的类中动态地添加一个 (nonatomic, retain) 的属性
#define CHPropertyRetainNonatomic(class, type, getter, setter) CHProperty(class, type, getter, setter, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
// 向给定的类中动态地添加一个 (atomic, copy) 的属性
#define CHPropertyCopy(class, type, getter, setter) CHProperty(class, type, getter, setter, OBJC_ASSOCIATION_COPY)
// 向给定的类中动态地添加一个 (nonatomic, copy) 的属性
#define CHPropertyCopyNonatomic(class, type, getter, setter) CHProperty(class, type, getter, setter, OBJC_ASSOCIATION_COPY_NONATOMIC)
// 向给定的类中动态地添加一个 (nonatomic, assign) 的属性
#define CHPropertyAssign(class, type, getter, setter) CHProperty(class, type, getter, setter, OBJC_ASSOCIATION_ASSIGN)

这 5 个宏接口会根据添加属性时所需的关联对象的存储策略,组织传入的宏参数,生成对宏接口 CHProperty 的调用

// @param.class	 要添加属性的类的名称(不用加 "" 或者 @"")
// @param.type	 要添加的属性的类型
// @param.getter 要添加的属性的 getter 方法名称(直接写 getter 方法名,不用加关键字 @selector)
// @param.setter 要添加的属性的 setter 方法名称(直接写 setter 方法名,不用加关键字 @selector,不用加冒号 : )
// @param.policy 关联对象的存储策略
#define CHProperty(class, type, getter, setter, policy) \
	CHDeclareProperty(class, getter) \
	CHPropertyGetter(class, getter, type) { \
		return CHPropertyGetValue(class, getter); \
	} \
	CHPropertySetter(class, setter, type, getter) { \
		CHPropertySetValue(class, getter, getter, policy); \
	}

宏接口 CHDeclarePropertyCHPropertyGetValueCHPropertySetValue 用于:定义关联对象所需要的键、获取关联对象、设置关联对象

#define CHDeclareProperty(class, name) static const char k ## class ## _ ## name;
#define CHPropertyGetValue(class, name) objc_getAssociatedObject(self, &k ## class ## _ ## name )
#define CHPropertySetValue(class, name, value, policy) objc_setAssociatedObject(self, &k ## class ## _ ## name , value, policy)

宏接口 CHPropertyGetterCHPropertySetter 用于生成(添加属性 gettersetter 方法的代码)

#define CHPropertyGetter(class, getter, type) CHOptimizedMethod0(new, type, class, getter)
#define CHPropertySetter(class, setter, type, value) CHOptimizedMethod1(new, void, class, setter, type, value)

宏接口 CHHookProperty 用于触发(添加属性 gettersetter 方法的代码)

#define CHHookProperty(class, getter, setter) \
	do { \
		CHHook0(class, getter); \
		CHHook1(class, setter); \
	} while(0)

以向 HcgPerson 类添加属性 @property (nonatomic, retain) NSString* addr; 为例,如下代码:

#import "HcgPerson.h"
#import "CaptainHook.h"

CHDeclareClass(HcgPerson)

CHPropertyRetainNonatomic(HcgPerson, NSString*, addr, setAddr)

CHConstructor
{
    CHLoadLateClass(HcgPerson);
    CHHookProperty(HcgPerson, addr, setAddr);
}

等价于

#import "HcgPerson.h"
#import "CaptainHook.h"

CHDeclareClass(HcgPerson)

CHProperty(HcgPerson, NSString*, addr, setAddr, OBJC_ASSOCIATION_RETAIN_NONATOMIC)

CHConstructor
{
    CHLoadLateClass(HcgPerson);
    CHHookProperty(HcgPerson, addr, setAddr);
}

等价于

#import "HcgPerson.h"
#import "CaptainHook.h"

CHDeclareClass(HcgPerson)

CHDeclareProperty(HcgPerson, addr)

CHPropertyGetter(HcgPerson, addr, NSString*)
{
    return CHPropertyGetValue(HcgPerson, addr);
}

CHPropertySetter(HcgPerson, setAddr, NSString*, addr)
{
    CHPropertySetValue(HcgPerson, addr, addr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

CHConstructor
{
    CHLoadLateClass(HcgPerson);
    CHHookProperty(HcgPerson, addr, setAddr);
}

等价于

#import "HcgPerson.h"
#import "CaptainHook.h"

CHDeclareClass(HcgPerson)

static const char kHcgPerson_addr = '\0';

CHPropertyGetter(HcgPerson, addr, NSString*)
{
    return objc_getAssociatedObject(self, &kHcgPerson_addr);
}

CHPropertySetter(HcgPerson, setAddr, NSString*, addr)
{
    objc_setAssociatedObject(self, &kHcgPerson_addr , addr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

CHConstructor
{
    CHLoadLateClass(HcgPerson);
    CHHookProperty(HcgPerson, addr, setAddr);
}

等价于

#import "HcgPerson.h"
#import "CaptainHook.h"

CHDeclareClass(HcgPerson)

static const char kHcgPerson_addr = '\0';

CHOptimizedMethod0(new, NSString*, HcgPerson, addr)
{
    return objc_getAssociatedObject(self, &kHcgPerson_addr );
}

CHOptimizedMethod1(new, void, HcgPerson, setAddr, NSString*, addr)
{
    objc_setAssociatedObject(self, &kHcgPerson_addr , addr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

CHConstructor
{
    CHLoadLateClass(HcgPerson);
    CHHookProperty(HcgPerson, addr, setAddr);
}

预处理后的结果为:

#pragma clang module import Foundation                  /* clang -E: implicit import for #import <Foundation/Foundation.h> */

#pragma clang assume_nonnull begin

@interface HcgPerson : NSObject

@property (nonatomic, strong) NSString* name;
@property (nonatomic, assign) int age;

-(instancetype)initWithName:(NSString *)aName age:(int)anAge;
+(instancetype)personWithName:(NSString *)aName age:(int)anAge;

-(NSString *)eatFood:(NSString *)aFood inPlace:(NSString *)aPlace;
+(int)doAdditionWithNum1:(int)num1 num2:(int)num2;

+(instancetype)sharedInstance;

@end

#pragma clang assume_nonnull end
#pragma clang module import ObjectiveC.runtime          /* clang -E: implicit import for #import <objc/runtime.h> */
#pragma clang module import ObjectiveC.message          /* clang -E: implicit import for #import <objc/message.h> */
#pragma clang module import Foundation.NSObject         /* clang -E: implicit import for #import <Foundation/NSObject.h> */
#pragma clang module import Foundation.NSObjCRuntime    /* clang -E: implicit import for #import <Foundation/NSObjCRuntime.h> */

struct CHClassDeclaration_
{
    Class class_;
    Class metaClass_;
    Class superClass_;
};
typedef struct CHClassDeclaration_ CHClassDeclaration_;

static inline Class CHLoadClass_(CHClassDeclaration_* declaration, Class value)
{
    declaration->class_ = value;
    declaration->metaClass_ = object_getClass(value);
    declaration->superClass_ = class_getSuperclass(value);
    return value;
}

__attribute__((unused)) inline __attribute__((always_inline))
static void* CHIvar_(id object, const char * name)
{
    Ivar ivar = class_getInstanceVariable(object_getClass(object), name);
    if (ivar)
        return (void *)&((char *)(__bridge void *)object)[ivar_getOffset(ivar)];
    return ((void*)0);
}
@class HcgPerson;
static CHClassDeclaration_ HcgPerson$;
// 关联对象的 key
static const char kHcgPerson_addr = '\0';

// 添加 getter 方法
static NSString* $HcgPerson_addr_method(HcgPerson * self, SEL _cmd);
__attribute__((always_inline)) static inline void $HcgPerson_addr_register()
{
    const char *return_ = @encode(NSString*);
    size_t return_len = __builtin_strlen(return_);
    char sig[return_len+2+1];
    __builtin_memcpy(sig, return_, return_len);
    sig[return_len] = '@';
    sig[return_len+1] = ':';
    sig[return_len+2] = '\0';;
    
    class_addMethod(HcgPerson$.class_,
                    @selector(addr),
                    (IMP)&$HcgPerson_addr_method,
                    sig);
}
static NSString* $HcgPerson_addr_method(HcgPerson * self, SEL _cmd)
{
    return objc_getAssociatedObject(self, &kHcgPerson_addr );
}

// 添加 setter 方法
static void $HcgPerson_setAddr$_method(HcgPerson * self, SEL _cmd, NSString* addr);
__attribute__((always_inline)) static inline void $HcgPerson_setAddr$_register()
{
    const char *return_ = @encode(void);
    size_t return_len = __builtin_strlen(return_);
    const char *type1_ = @encode(NSString*);
    size_t type1_len = __builtin_strlen(type1_);
    char sig[return_len+2+type1_len+1];
    __builtin_memcpy(sig, return_, return_len);
    sig[return_len] = '@';
    sig[return_len+1] = ':';
    __builtin_memcpy(&sig[return_len+2], type1_, type1_len);
    sig[return_len+type1_len+2] = '\0';;
    
    class_addMethod(HcgPerson$.class_,
                    @selector(setAddr:),
                    (IMP)&$HcgPerson_setAddr$_method,
                    sig);
}
static void $HcgPerson_setAddr$_method(HcgPerson * self, SEL _cmd, NSString* addr)
{
    objc_setAssociatedObject(self, &kHcgPerson_addr , addr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
static __attribute__((constructor)) void CHConstructor8()
{
    CHLoadClass_(&HcgPerson$, objc_getClass("HcgPerson"));
    do {
    	$HcgPerson_addr_register(); 
    	$HcgPerson_setAddr$_register(); 
    } while(0);
}

动态地添加属性(CHPrimitiveProperty + CHHookProperty)

CaptainHook.h 中,宏接口 CHPrimitiveProperty 用于动态地向类中添加属性,其定义为

// @param.class	  要添加属性的类的名称(不用加 "" 或者 @"")
// @param.type	  要添加的属性的类型
// @param.getter  要添加的属性的 getter 方法名称(直接写 getter 方法名,不用加关键字 @selector)
// @param.setter  要添加的属性的 setter 方法名称(直接写 setter 方法名,不用加关键字 @selector,不用加冒号 : )
// @param.default 属性的默认值
#define CHPrimitiveProperty(class, type, getter, setter, default) \
	CHDeclareProperty(class, getter) \
	CHOptimizedMethod0(new, type, class, getter) { \
		CHPrimitivePropertyGetValue( class , getter , type , val , default ); \
		return val; \
	} \
	CHOptimizedMethod1(new, void, class, setter, type, getter) { \
		CHPrimitivePropertySetValue( class , getter, type , getter ); \
	}

宏接口 CHPrimitivePropertyGetValueCHPrimitivePropertySetValue 用于生成:获取和设置属性值的方法

#define CHPrimitivePropertyGetValue(class, name, type, val, default) \
	type val = default; \
	do { \
		NSNumber * objVal = CHPropertyGetValue(class, name); \
		[objVal getValue:& val ]; \
	} while(0)
#define CHPrimitivePropertySetValue(class, name, type, val) \
	do { \
		NSValue *objVal = [NSValue value:& val withObjCType:@encode( type )]; \
		CHPropertySetValue(class, name, objVal, OBJC_ASSOCIATION_RETAIN_NONATOMIC); \
	} while(0)

宏接口 CHDeclarePropertyCHPropertyGetValueCHPropertySetValue 用于:定义关联对象所需要的键、获取关联对象、设置关联对象

#define CHDeclareProperty(class, name) static const char k ## class ## _ ## name;
#define CHPropertyGetValue(class, name) objc_getAssociatedObject(self, &k ## class ## _ ## name )
#define CHPropertySetValue(class, name, value, policy) objc_setAssociatedObject(self, &k ## class ## _ ## name , value, policy)

宏接口 CHHookProperty 用于触发(添加属性 gettersetter 方法的代码)

#define CHHookProperty(class, getter, setter) \
	do { \
		CHHook0(class, getter); \
		CHHook1(class, setter); \
	} while(0)

以向 HcgPerson 类添加属性 @property (nonatomic, retain) NSString* addr; 为例,如下代码:

#import "HcgPerson.h"
#import "CaptainHook.h"

CHDeclareClass(HcgPerson)

CHPrimitiveProperty(HcgPerson, NSString*, addr, setAddr, @"XiaMen")

CHConstructor
{
    CHLoadLateClass(HcgPerson);
    CHHookProperty(HcgPerson, addr, setAddr);
}

等价于

#import "HcgPerson.h"
#import "CaptainHook.h"

CHDeclareClass(HcgPerson)

CHDeclareProperty(HcgPerson, addr)

CHOptimizedMethod0(new, NSString*, HcgPerson, addr)
{
    CHPrimitivePropertyGetValue( HcgPerson , addr , NSString* , val , @"XiaMen" );
    return val;
}

CHOptimizedMethod1(new, void, HcgPerson, setAddr, NSString*, addr)
{
    CHPrimitivePropertySetValue( HcgPerson , addr, NSString* , addr );
}

CHConstructor
{
    CHLoadLateClass(HcgPerson);
    CHHookProperty(HcgPerson, addr, setAddr);
}

等价于

#import "HcgPerson.h"
#import "CaptainHook.h"

CHDeclareClass(HcgPerson)

CHDeclareProperty(HcgPerson, addr)

CHOptimizedMethod0(new, NSString*, HcgPerson, addr)
{
    NSString* val = @"XiaMen";
    do {
        NSNumber * objVal = CHPropertyGetValue(HcgPerson, addr);
        [objVal getValue:& val ];
    } while(0);
    return val;
}

CHOptimizedMethod1(new, void, HcgPerson, setAddr, NSString*, addr)
{
    do {
        NSValue *objVal = [NSValue value:& addr withObjCType:@encode( NSString* )];
        CHPropertySetValue(HcgPerson, addr, objVal, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    } while(0);
}

CHConstructor
{
    CHLoadLateClass(HcgPerson);
    CHHookProperty(HcgPerson, addr, setAddr);
}

等价于

#import "HcgPerson.h"
#import "CaptainHook.h"

CHDeclareClass(HcgPerson)

static const char kHcgPerson_addr = '\0';

CHOptimizedMethod0(new, NSString*, HcgPerson, addr)
{
    NSString* val = @"XiaMen";
    do {
        NSNumber * objVal = objc_getAssociatedObject(self, &kHcgPerson_addr );
        [objVal getValue:& val ];
    } while(0);
    return val;
}

CHOptimizedMethod1(new, void, HcgPerson, setAddr, NSString*, addr)
{
    do {
        NSValue *objVal = [NSValue value:& addr withObjCType:@encode( NSString* )];
        objc_setAssociatedObject(self, &kHcgPerson_addr , objVal, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    } while(0);
}

CHConstructor
{
    CHLoadLateClass(HcgPerson);
    CHHookProperty(HcgPerson, addr, setAddr);
}

预处理后的结果为:

#pragma clang module import Foundation                  /* clang -E: implicit import for #import <Foundation/Foundation.h> */

#pragma clang assume_nonnull begin

@interface HcgPerson : NSObject

@property (nonatomic, strong) NSString* name;
@property (nonatomic, assign) int age;

-(instancetype)initWithName:(NSString *)aName age:(int)anAge;
+(instancetype)personWithName:(NSString *)aName age:(int)anAge;

-(NSString *)eatFood:(NSString *)aFood inPlace:(NSString *)aPlace;
+(int)doAdditionWithNum1:(int)num1 num2:(int)num2;

+(instancetype)sharedInstance;

@end

#pragma clang assume_nonnull end
#pragma clang module import ObjectiveC.runtime          /* clang -E: implicit import for #import <objc/runtime.h> */
#pragma clang module import ObjectiveC.message          /* clang -E: implicit import for #import <objc/message.h> */
#pragma clang module import Foundation.NSObject         /* clang -E: implicit import for #import <Foundation/NSObject.h> */
#pragma clang module import Foundation.NSObjCRuntime    /* clang -E: implicit import for #import <Foundation/NSObjCRuntime.h> */

struct CHClassDeclaration_
{
    Class class_;
    Class metaClass_;
    Class superClass_;
};
typedef struct CHClassDeclaration_ CHClassDeclaration_;

static inline Class CHLoadClass_(CHClassDeclaration_* declaration, Class value)
{
    declaration->class_ = value;
    declaration->metaClass_ = object_getClass(value);
    declaration->superClass_ = class_getSuperclass(value);
    return value;
}

__attribute__((unused)) inline __attribute__((always_inline))
static void* CHIvar_(id object, const char * name)
{
    Ivar ivar = class_getInstanceVariable(object_getClass(object), name);
    if (ivar)
        return (void *)&((char *)(__bridge void *)object)[ivar_getOffset(ivar)];
    return ((void*)0);
}
@class HcgPerson;
static CHClassDeclaration_ HcgPerson$;
// 关联对象的 key
static const char kHcgPerson_addr = '\0';

// 添加 getter 方法
static NSString* $HcgPerson_addr_method(HcgPerson * self, SEL _cmd);
__attribute__((always_inline)) static inline void $HcgPerson_addr_register()
{
    const char *return_ = @encode(NSString*);
    size_t return_len = __builtin_strlen(return_);
    char sig[return_len+2+1];
    __builtin_memcpy(sig, return_, return_len);
    sig[return_len] = '@';
    sig[return_len+1] = ':';
    sig[return_len+2] = '\0';;
    
    class_addMethod(HcgPerson$.class_,
                    @selector(addr),
                    (IMP)&$HcgPerson_addr_method, 
                    sig);
}
static NSString* $HcgPerson_addr_method(HcgPerson * self, SEL _cmd)
{
    NSString* val = @"XiaMen";
    do {
        NSNumber * objVal = objc_getAssociatedObject(self, &kHcgPerson_addr );
        [objVal getValue:& val ];
    } while(0);
    return val;
}

// 添加 setter 方法
static void $HcgPerson_setAddr$_method(HcgPerson * self, SEL _cmd, NSString* addr);
__attribute__((always_inline)) static inline void $HcgPerson_setAddr$_register()
{
    const char *return_ = @encode(void);
    size_t return_len = __builtin_strlen(return_);
    const char *type1_ = @encode(NSString*);
    size_t type1_len = __builtin_strlen(type1_);
    char sig[return_len+2+type1_len+1];
    __builtin_memcpy(sig, return_, return_len);
    sig[return_len] = '@';
    sig[return_len+1] = ':';
    __builtin_memcpy(&sig[return_len+2], type1_, type1_len);
    sig[return_len+type1_len+2] = '\0';;
    
    class_addMethod(HcgPerson$.class_,
                    @selector(setAddr:),
                    (IMP)&$HcgPerson_setAddr$_method,
                    sig);
}
static void $HcgPerson_setAddr$_method(HcgPerson * self, SEL _cmd, NSString* addr)
{
    do {
        NSValue *objVal = [NSValue value:& addr withObjCType:@encode( NSString* )];
        objc_setAssociatedObject(self, &kHcgPerson_addr , objVal, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    } while(0);
}
static __attribute__((constructor)) void CHConstructor8()
{
    CHLoadClass_(&HcgPerson$, objc_getClass("HcgPerson"));
    do {
        $HcgPerson_addr_register();
        $HcgPerson_setAddr$_register();
    } while(0);
}

各种动态添加属性的宏接口的区别与联系

CHPropertyRetain:用于动态地向类中添加使用关联策略 OBJC_ASSOCIATION_RETAIN 的属性,底层调用 CHProperty

CHPropertyRetainNonatomic:用于动态地向类中添加使用关联策略 OBJC_ASSOCIATION_RETAIN_NONATOMIC 的属性,底层调用 CHProperty

CHPropertyCopy:用于动态地向类中添加使用关联策略 OBJC_ASSOCIATION_COPY 的属性,底层调用 CHProperty

CHPropertyCopyNonatomic:用于动态地向类中添加使用关联策略 OBJC_ASSOCIATION_COPY_NONATOMIC 的属性,底层调用 CHProperty

CHPropertyAssign:用于动态地向类中添加使用关联策略 OBJC_ASSOCIATION_ASSIGN 的属性,底层调用 CHProperty

CHPrimitiveProperty:用于动态地向类中添加使用关联策略 OBJC_ASSOCIATION_RETAIN_NONATOMIC 的属性,其作用等同于 CHPropertyRetainNonatomic

访问动态添加的属性

方式一:

#import "HcgPerson.h"
#import "CaptainHook.h"

// 声明 HcgPerson 类
CHDeclareClass(HcgPerson)

// 向 HcgPerson 类中动态地添加属性 @property (nonatomic, retain) NSString* addr;
CHPropertyRetainNonatomic(HcgPerson, NSString*, addr, setAddr)

// 向 HcgPerson 类中动态地添加对象方法 -[HcgPerson propertyTest]
CHMethod(0, void, HcgPerson, propertyTest)
{
    CHPropertySetValue(HcgPerson, addr, @"XiaMen", OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    NSString* addr = CHPropertyGetValue(HcgPerson, addr);
    NSLog(@"addr = %@", addr);
}

// 构造函数 1:
// 1.1 加载 HcgPerson 类
// 1.2 向 HcgPerson 类中动态地添加属性 @property (nonatomic, retain) NSString* addr;
// 1.3 向 HcgPerson 类中动态地添加对象方法 -[HcgPerson propertyTest]
CHConstructor
{
    CHLoadLateClass(HcgPerson);
    CHHookProperty(HcgPerson, addr, setAddr);
    CHHook(0, HcgPerson, propertyTest);
}

// 构造函数 2:
// 创建 HcgPerson 实例对象,并调用 -[HcgPerson propertyTest] 方法
// -[HcgPerson propertyTest] 方法会通过宏接口 CHPropertyGetValue 和 CHPropertySetValue 获取和设置 person.addr 属性的值
CHConstructor
{
    HcgPerson* person = [CHAlloc(HcgPerson) initWithName:@"hcg" age:20];
    ((void(*)(id, SEL))objc_msgSend)(person, @selector(propertyTest));
}

// 构造函数 3:
// 创建 HcgPerson 实例对象,并通过 KVC 获取和设置 person.addr 属性的值
CHConstructor
{
    HcgPerson* person = [CHAlloc(HcgPerson) initWithName:@"hcg" age:20];
    [person setValue:@"XiaMen" forKey:@"addr"];
    NSString* addr = [person valueForKey:@"addr"];
    NSLog(@"addr = %@", addr);
}

方式二:

#import "HcgPerson.h"
#import "CaptainHook.h"

// 声明 HcgPerson 类
CHDeclareClass(HcgPerson)

// 向 HcgPerson 类中动态地添加属性 @property (nonatomic, retain) NSString* addr;
CHPrimitiveProperty(HcgPerson, NSString*, addr, setAddr, @"ZhangZhou")

// 向 HcgPerson 类中动态地添加对象方法 -[HcgPerson propertyTest]
CHMethod(0, void, HcgPerson, propertyTest)
{
    NSString* addr0 = @"XiaMen";
    CHPrimitivePropertySetValue(HcgPerson, addr, NSString*, addr0);
    CHPrimitivePropertyGetValue(HcgPerson, addr, NSString*, addr1, @"ZhangZhou");
    NSLog(@"addr1 = %@", addr1);
}

// 构造函数 1:
// 1.1 加载 HcgPerson 类
// 1.2 向 HcgPerson 类中动态地添加属性 @property (nonatomic, retain) NSString* addr;
// 1.3 向 HcgPerson 类中动态地添加对象方法 -[HcgPerson propertyTest]
CHConstructor
{
    CHLoadLateClass(HcgPerson);
    CHHookProperty(HcgPerson, addr, setAddr);
    CHHook(0, HcgPerson, propertyTest);
}

// 构造函数 2:
// 创建 HcgPerson 实例对象,并调用 -[HcgPerson propertyTest] 方法
// -[HcgPerson propertyTest] 方法会通过宏接口 CHPrimitivePropertyGetValue 和 CHPrimitivePropertySetValue 获取和设置 person.addr 属性的值
CHConstructor
{
    HcgPerson* person = [CHAlloc(HcgPerson) initWithName:@"hcg" age:20];
    ((void(*)(id, SEL))objc_msgSend)(person, @selector(propertyTest));
}

// 构造函数 3:
// 创建 HcgPerson 实例对象,并通过 KVC 获取和设置 person.addr 属性的值
CHConstructor
{
    HcgPerson* person = [CHAlloc(HcgPerson) initWithName:@"hcg" age:20];
    [person setValue:@"XiaMen" forKey:@"addr"];
    NSString* addr = [person valueForKey:@"addr"];
    NSLog(@"addr = %@", addr);
}

总结:

在当前类的对象方法中:
既可以使用宏接口 CHPropertyGetValueCHPropertySetValue 获取和设置(动态添加的属性的值)
也可以使用 KVC 的 valueForKey:setValue:forKey: 获取和设置(动态添加的属性的值)

在其他的外部方法中:
只能使用 KVC 的 valueForKey:setValue:forKey: 获取和设置(动态添加的属性的值)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值