CaptainHook source code analysis: dynamically create classes & & get and set the value of member variables & & dynamically add attributes

Dynamically create class (CHRegisterClass)

In CaptainHook.h, the macro interface CHRegisterClass is used to dynamically create a class at runtime, which is defined as:

// @param.name 		 The name of the class to be dynamically created (do not add "" or @ "")
// @param.superName 	 The name of the parent class of the class to be dynamically created (do not add "" or @ "")
#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; }))

Take the dynamic creation of the subclass HcgStudent of HcgPerson class as an example, as shown in the following code:

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

CHDeclareClass(HcgPerson)
CHDeclareClass(HcgStudent)

CHConstructor
{
    // You need to load the HcgPerson class, the parent class of the HcgStudent class, before you can dynamically create the HcgStudent class
    CHLoadLateClass(HcgPerson);
    
    // Dynamically create the subclass HcgStudent class of HcgPerson class
    CHRegisterClass(HcgStudent, HcgPerson);
    
    // Because the static chclassdeclaration of HcgStudent class has been in the macro interface CHRegisterClass_ The HcgStudent $structure is assigned a value
    // Therefore, after calling the macro interface CHRegisterClass, you do not need to call the macro interface CHLoadLateClass to load the HcgStudent class
    // CHLoadLateClass(HcgStudent);
}

The results after pretreatment are:

#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
    (
        // Dynamically create the subclass HcgStudent class of the HcgPerson class at run time
        // And static chclassdeclaration of HcgStudent class_ HcgStudent $struct
        int _tmp =
        ({
            HcgStudent$.class_ = objc_allocateClassPair(HcgPerson$.class_, "HcgStudent", 0);
            HcgStudent$.metaClass_ = object_getClass(HcgStudent$.class_);
            HcgStudent$.superClass_ = class_getSuperclass(HcgStudent$.class_);
            1;
        });
		
        _tmp;
		
        // Register the dynamically created HcgStudent class with the runtime
        _tmp =
        ({
            objc_registerClassPair(HcgStudent$.class_), 0;
        })
    );
}

Add a member variable (CHAddIvar) to a dynamically created class

In CaptainHook.h, the macro interface CHAddIvar is used to add member variables to dynamically created classes. It is defined as:

// @param.targetClass 	 Dynamically created class objects
// @param.name 			 The name of the member variable to be added (do not add "" or @ "")
// @param.type 			 The type of member variable to add
#define CHAddIvar(targetClass, name, type) \
	class_addIvar(targetClass, #name, sizeof(type), CHAlignmentForSize_(sizeof(type)), @encode(type))

The macro interface CHAddIvar organizes the macro parameters passed in and generates a class for the RunTime function_ Addivar call:

/*
Add a new member variable to the class

@param.cls 			Class to modify (must be a dynamically created class, not an existing class)
@param.name 		The name of the member variable
@param.size 		Size of member variable
@param.alignment	Memory alignment of member variables
@param.types 		Type code of member variable
@return 			Returns YES if the member variable was successfully added. Otherwise, NO is returned (for example, the class already contains a member variable with this name)
@note This function can only be used in objc_ After allocateclasspair() and objc_registerClassPair() before calling
	  Adding member variables to existing classes is not supported (this function can only add member variables to dynamically generated classes)
@note This class must not be a metaclass. Adding member variables to a metaclass is not supported
@note The minimum byte alignment value of the member variable is 1 < < align
	  The minimum byte alignment of member variables depends on the type of member variables and the architecture of the CPU
	  For member variables of any pointer type, pass log2(sizeof(pointer_type))
*/
BOOL class_addIvar(Class _Nullable cls, 
				   const char * _Nonnull name, 
				   size_t size, 
				   uint8_t alignment, 
				   const char * _Nullable types);

Where, the macro interface CHAlignmentForSize_ Used to calculate (memory alignment of member variables) based on (size of member variables), which is defined as:

// @The size of the param.size member variable
// @note here__ builtin_constant_p(s) is a built-in function of the compiler, which is used to judge whether a given expression s is a constant at compile time
//		 If the given expression s is a constant at compile time, it returns true(1); Otherwise, false(0) is returned
// @note the calculated value here is the number of bits shifted left by 1!!! for instance:
//		 If the size of the member variable is between 0 and 1 bytes, the calculated left shift bit is 0, and the minimum byte alignment value of the member variable is 1 < < 0 = = 1 byte
//		 If the size of the member variable is between 2 and 3 bytes, the calculated left shift bit is 1, and the minimum byte alignment value of the member variable is 1 < < 1 = = 2 bytes
//		 If the size of the member variable is between 4 and 7 bytes, the calculated left shift bit is 2, and the minimum byte alignment value of the member variable is 1 < < 2 = = 4 bytes
//		 If the size of the member variable is between 8 and 15 bytes, the calculated left shift bit is 3, and the minimum byte alignment value of the member variable is 1 < < 3 = = 8 bytes
//		 If the size of the member variable is between 16 and 31 bytes, the calculated left shift bit is 4, and the minimum byte alignment value of the member variable is 1 < < 4 = = 16 bytes
//		 If the size of the member variable is between 32 and 63 bytes, the calculated left shift bit is 5, and the minimum byte alignment value of the member variable is 1 < < 5 = = 32 bytes
//		 ... ...
#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); \
})

To add a member variable of NSString * type to the dynamically created HcgStudent class_ addr as an example, the following code:

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

CHDeclareClass(HcgPerson)
CHDeclareClass(HcgStudent)

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

The results after pretreatment are:

#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
    (
    	// Step 1: dynamically create the subclass HcgStudent class of HcgPerson class at runtime
        int _tmp =
        ({
            HcgStudent$.class_ = objc_allocateClassPair(HcgPerson$.class_, "HcgStudent", 0);
            HcgStudent$.metaClass_ = object_getClass(HcgStudent$.class_);
            HcgStudent$.superClass_ = class_getSuperclass(HcgStudent$.class_);
            1;
        });
        
        _tmp;
        
        // Step 3: register the dynamically created HcgStudent class with the runtime
        _tmp = ({ objc_registerClassPair(HcgStudent$.class_), 0; })
     )
	 {
	 	// Step 2: add member variables to the dynamically created HcgStudent class
        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*));
	 }
}

Gets and sets the value of the member variable in the instance object (CHIvar)

In CaptainHook.h, the macro interface CHIvar is used to obtain or set the value of the member variable in the instance object, which is defined as:

// @param.object instance object
// @param.name 	  The name of the member variable (do not add "" or @ "")
// @param.type 	  Type of member variable
#define CHIvar(object, name, type) \
	(*CHIvarRef(object, name, type))

The macro interface CHIvar organizes the incoming macro parameters and generates a call to the macro interface CHIvarRef

// @param.object instance object
// @param.name  	  The name of the member variable (do not add "" or @ "")
// @param.type 	  Type of member variable
#define CHIvarRef(object, name, type) \
	((type *)CHIvar_(object, #name))

The macro interface CHIvarRef organizes the macro parameters passed in and generates a pair of static functions CHIvar_ Call of

// @param.object instance object
// @param.name  	  The name of the member variable
__attribute__((unused)) CHInline
static void* CHIvar_(id object, const char * name)
{
	// Calling RunTime's class_getInstanceVariable function: gets the Ivar structure of the member variable with the given name in the given class
	Ivar ivar = class_getInstanceVariable(object_getClass(object), name);
	// If there is a member variable with the given name in the given class, the offset of the member variable is obtained, and the pointer to the value of the member variable in the current instance object is obtained according to the offset of the member variable
	// Returns NULL if a member variable with the given name does not exist in the given class
	if (ivar)
#ifdef CHHasARC
		return (void*)&((char*)(__bridge void*)object)[ivar_getOffset(ivar)];
#else
		return (void*)&((char*)object)[ivar_getOffset(ivar)];
#endif
	return NULL;
}

The following code:

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

CHDeclareClass(HcgPerson)

CHConstructor
{
    CHLoadLateClass(HcgPerson);
    
    HcgPerson* person = [CHAlloc(HcgPerson) initWithName:@"hcg" age:20];
    
    // Because in ARC environment, it is not allowed to implicitly convert non Objective-C pointer type (void *) to Objective-C pointer type (NSString *)
    // Therefore, the current source file needs to be compiled by MRC: Target - Build Phases - current source file - add compilation ID (- 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);
}

Equivalent to

#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);
}

Equivalent to

#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);
}

The results after pretreatment are:

#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);
}

Add properties dynamically (chpropertyretain nonatomic + chhookproperty)

In CaptainHook.h, the following five macro interfaces are used to dynamically add attributes to classes, which are defined as

// @param.class 	  The name of the class to which the attribute is to be added (do not add '' or @ '')
// @param.type 	  Type of property to add
// @param.getter the getter method name of the attribute to be added (write the getter method name directly without adding the keyword @ selector)
// @param.setter the setter method name of the property to be added (write the setter method name directly without adding the keyword @ selector or colon:)

// Dynamically add an (atomic, retain) attribute to a given class
#define CHPropertyRetain(class, type, getter, setter) CHProperty(class, type, getter, setter, OBJC_ASSOCIATION_RETAIN)
// Dynamically add a (nonatomic, retain) attribute to a given class
#define CHPropertyRetainNonatomic(class, type, getter, setter) CHProperty(class, type, getter, setter, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
// Dynamically add an attribute (atomic, copy) to a given class
#define CHPropertyCopy(class, type, getter, setter) CHProperty(class, type, getter, setter, OBJC_ASSOCIATION_COPY)
// Dynamically add a (nonatomic, copy) attribute to a given class
#define CHPropertyCopyNonatomic(class, type, getter, setter) CHProperty(class, type, getter, setter, OBJC_ASSOCIATION_COPY_NONATOMIC)
// Dynamically add a (nonatomic, assign) attribute to a given class
#define CHPropertyAssign(class, type, getter, setter) CHProperty(class, type, getter, setter, OBJC_ASSOCIATION_ASSIGN)

These five macro interfaces will organize the incoming macro parameters and generate calls to the macro interface CHProperty according to the storage policies of the associated objects required to add properties

// @param.class 	  The name of the class to which the attribute is to be added (do not add '' or @ '')
// @param.type 	  Type of property to add
// @param.getter the getter method name of the attribute to be added (write the getter method name directly without adding the keyword @ selector)
// @param.setter the setter method name of the property to be added (write the setter method name directly without adding the keyword @ selector or colon:)
// @param.policy storage policy of associated objects
#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); \
	}

The macro interfaces CHDeclareProperty, CHPropertyGetValue and CHPropertySetValue are used to define the keys required by the associated object, obtain the associated object and set the associated object

#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)

Macro interfaces CHPropertyGetter and CHPropertySetter are used to generate (add code for property getter and setter methods)

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

The macro interface CHHookProperty is used to trigger (add the code of property getter and setter methods)

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

To add the attribute @ property (nonatomic, retain) NSString* addr to the HcgPerson class; For example, the following code:

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

CHDeclareClass(HcgPerson)

CHPropertyRetainNonatomic(HcgPerson, NSString*, addr, setAddr)

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

Equivalent to

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

CHDeclareClass(HcgPerson)

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

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

Equivalent to

#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);
}

Equivalent to

#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);
}

Equivalent to

#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);
}

The results after pretreatment are:

#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 of associated object
static const char kHcgPerson_addr = '\0';

// Add getter method
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 );
}

// Add setter method
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);
}

Add properties dynamically (CHPrimitiveProperty + CHHookProperty)

In CaptainHook.h, the macro interface CHPrimitiveProperty is used to dynamically add properties to the class, which is defined as

// @param.class 	   The name of the class to which the attribute is to be added (do not add '' or @ '')
// @param.type 	   Type of property to add
// @param.getter the getter method name of the attribute to be added (write the getter method name directly without adding the keyword @ selector)
// @param.setter the setter method name of the property to be added (write the setter method name directly without adding the keyword @ selector or colon:)
// @The default value of the param.default property
#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 ); \
	}

Macro interfaces CHPrimitivePropertyGetValue and CHPrimitivePropertySetValue are used to generate: methods for obtaining and setting property values

#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)

The macro interfaces CHDeclareProperty, CHPropertyGetValue and CHPropertySetValue are used to define the keys required by the associated object, obtain the associated object and set the associated object

#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)

The macro interface CHHookProperty is used to trigger (add the code of property getter and setter methods)

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

To add the attribute @ property (nonatomic, retain) NSString* addr to the HcgPerson class; For example, the following code:

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

CHDeclareClass(HcgPerson)

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

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

Equivalent to

#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);
}

Equivalent to

#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);
}

Equivalent to

#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);
}

The results after pretreatment are:

#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 of associated object
static const char kHcgPerson_addr = '\0';

// Add getter method
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;
}

// Add setter method
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);
}

Differences and relations of various macro interfaces for dynamically adding attributes

CHPropertyRetain: used to dynamically add objc using Association policy to the class_ ASSOCIATION_ The property of retain. The underlying layer calls CHProperty

Chpropertyretain nonatomic: used to dynamically add objc using Association policy to a class_ ASSOCIATION_ RETAIN_ Nonatomic property. The underlying layer calls CHProperty

CHPropertyCopy: used to dynamically add objc using Association policy to a class_ ASSOCIATION_ The property of copy. The underlying layer calls CHProperty

CHPropertyCopyNonatomic: used to dynamically add objc using Association policy to a class_ ASSOCIATION_ COPY_ Nonatomic property. The underlying layer calls CHProperty

CHPropertyAssign: used to dynamically add objc using Association policy to the class_ ASSOCIATION_ Assign property, and the underlying layer calls CHProperty

CHPrimitiveProperty: used to dynamically add objc using Association policy to a class_ ASSOCIATION_ RETAIN_ The property of nonatomic is equivalent to chpropertyretain nonatomic

Access dynamically added properties

Mode 1:

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

// Declare the HcgPerson class
CHDeclareClass(HcgPerson)

// Dynamically add the attribute @ property (nonatomic, retain) NSString* addr to the HcgPerson class;
CHPropertyRetainNonatomic(HcgPerson, NSString*, addr, setAddr)

// Dynamically add object method - [HcgPerson propertyTest] to HcgPerson class
CHMethod(0, void, HcgPerson, propertyTest)
{
    CHPropertySetValue(HcgPerson, addr, @"XiaMen", OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    NSString* addr = CHPropertyGetValue(HcgPerson, addr);
    NSLog(@"addr = %@", addr);
}

// Constructor 1:
// 1.1 load HcgPerson class
// 1.2 dynamically add the attribute @ property (nonatomic, retain) NSString* addr to the HcgPerson class;
// 1.3 dynamically add object method - [HcgPerson propertyTest] to HcgPerson class
CHConstructor
{
    CHLoadLateClass(HcgPerson);
    CHHookProperty(HcgPerson, addr, setAddr);
    CHHook(0, HcgPerson, propertyTest);
}

// Constructor 2:
// Create an HcgPerson instance object and call the - [HcgPerson propertyTest] method
// -The [HcgPerson propertyTest] method will get and set the value of the person.addr property through the macro interfaces CHPropertyGetValue and CHPropertySetValue
CHConstructor
{
    HcgPerson* person = [CHAlloc(HcgPerson) initWithName:@"hcg" age:20];
    ((void(*)(id, SEL))objc_msgSend)(person, @selector(propertyTest));
}

// Constructor 3:
// Create an HcgPerson instance object, and get and set the value of the person.addr property through KVC
CHConstructor
{
    HcgPerson* person = [CHAlloc(HcgPerson) initWithName:@"hcg" age:20];
    [person setValue:@"XiaMen" forKey:@"addr"];
    NSString* addr = [person valueForKey:@"addr"];
    NSLog(@"addr = %@", addr);
}

Mode 2:

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

// Declare the HcgPerson class
CHDeclareClass(HcgPerson)

// Dynamically add the attribute @ property (nonatomic, retain) NSString* addr to the HcgPerson class;
CHPrimitiveProperty(HcgPerson, NSString*, addr, setAddr, @"ZhangZhou")

// Dynamically add object method - [HcgPerson propertyTest] to HcgPerson class
CHMethod(0, void, HcgPerson, propertyTest)
{
    NSString* addr0 = @"XiaMen";
    CHPrimitivePropertySetValue(HcgPerson, addr, NSString*, addr0);
    CHPrimitivePropertyGetValue(HcgPerson, addr, NSString*, addr1, @"ZhangZhou");
    NSLog(@"addr1 = %@", addr1);
}

// Constructor 1:
// 1.1 load HcgPerson class
// 1.2 dynamically add the attribute @ property (nonatomic, retain) NSString* addr to the HcgPerson class;
// 1.3 dynamically add object method - [HcgPerson propertyTest] to HcgPerson class
CHConstructor
{
    CHLoadLateClass(HcgPerson);
    CHHookProperty(HcgPerson, addr, setAddr);
    CHHook(0, HcgPerson, propertyTest);
}

// Constructor 2:
// Create an HcgPerson instance object and call the - [HcgPerson propertyTest] method
// -The [HcgPerson propertyTest] method obtains and sets the value of the person.addr property through the macro interfaces CHPrimitivePropertyGetValue and CHPrimitivePropertySetValue
CHConstructor
{
    HcgPerson* person = [CHAlloc(HcgPerson) initWithName:@"hcg" age:20];
    ((void(*)(id, SEL))objc_msgSend)(person, @selector(propertyTest));
}

// Constructor 3:
// Create an HcgPerson instance object, and get and set the value of the person.addr property through KVC
CHConstructor
{
    HcgPerson* person = [CHAlloc(HcgPerson) initWithName:@"hcg" age:20];
    [person setValue:@"XiaMen" forKey:@"addr"];
    NSString* addr = [person valueForKey:@"addr"];
    NSLog(@"addr = %@", addr);
}

Summary:

In the object method of the current class:
You can use the macro interfaces CHPropertyGetValue and CHPropertySetValue to get and set (the value of a dynamically added property)
You can also use valueForKey:, setValue:forKey: of KVC to obtain and set (the value of dynamically added attributes)

In other external methods:
Only valueForKey:, setValue:forKey: of KVC can be used to get and set (the value of dynamically added property)

Keywords: objective-c hook

Added by Jordi_E on Thu, 09 Dec 2021 06:16:30 +0200