iOS - Runtime 中有关类、成员和方法的 API

iOS - Runtime 中有关类、成员和方法的 API_第1张图片
image.png

有关类的 API

获取 isa 指向的 Class

Class object_getClass(id obj);

设置 isa 指向的 Class

object_setClass(id _Nullable obj, Class _Nonnull cls) 

该方法能修改 isa 的指向,假如现在有两个类 People 和 Car 类,都有 run() 方法,两个 run 方法的实现为:

- (void)run {
    NSLog(@"%s", __func__);
}

执行:

People* p = [[People alloc] init];
[p run];
        
object_setClass(p, [Car class]);
[p run];

结果为:

-[People run]
-[Car run]

判断一个对象是否为类对象

object_isClass(id _Nullable obj)

运行:

NSLog(@"%d, %d, %d",
              object_isClass([[People alloc] init]),
              object_isClass([People class]),
              object_isClass(object_getClass([People class])));

结果为:

0, 1, 1

最后一个,元类对象是特殊的类对象。

动态创建一个类

objc_allocateClassPair(Class _Nullable superclass, const char * _Nonnull name, size_t extraBytes) 

第一个参数为父类,第二个参数为目标类的类名,第三个参数为给目标类新增的额外空间,一般传 0。

假如要创建一个 Valenti 类,则:

Class Valenti = objc_allocateClassPair([NSObject class], "Valenti", 0); // Valenti 继承自 NSObject
id v = [[Valenti alloc] init];
        
objc_registerClassPair(Valenti); // 注册该类,提示系统未来会用到该类
NSLog(@"%@", [v class]);

运行结果:

Valenti

动态注册类

objc_registerClassPair(Class _Nonnull cls) 

该方法为动态注册一个类,注册过后的类可以正常使用,需要注意的是添加成员操作都要在注册类前完成。类注册完后,基本信息会在只读的 class_ro_t 中存储,所以注册过后不能向只读的成员中覆盖新数据。

也就是说,添加成员的动态操作是不能对已存在的 People 和 Car 类使用的。

但是动态添加方法的操作是可以在注册之后进行。因为方法列表是存在 class_rw_t 中的,该结构是可读可写的。

动态销毁类

objc_disposeClassPair(Class _Nonnull cls)

有关成员的 API

动态添加成员

class_addIvar(Class _Nullable cls, const char * _Nonnull name, size_t size, uint8_t alignment, const char * _Nullable types) 

若要给上节动态新建的 Valenti 类中动态添加成员 int 型 age,则:

class_addIvar(Valenti, "age", 4, 1, @encode(int)); // 第三个参数为添加的成员类型字节数, 第四个参数为内存对齐相关,传 1 即可

首先在添加成员之前我们打印:

NSLog(@"%zd", class_getInstanceSize(Valenti));

结果为 8,很简单,因为目前该类只有一个 isa 指针,所以整个结构体占 8 个字节。
在添加成员后打印,结果为 16,说明 _age 添加成功。

我们可通过 KVC 对其进行赋值:

[v setValue: @26 forKey:@"age"];

打印:

NSLog(@"%@", [v valueForKey:@"age"]);

结果为: 26

获取实例变量信息

class_getInstanceVariable(Class _Nullable cls, const char * _Nonnull name)

现对 People 增加如下属性:

@property(assign, nonatomic) int age;
@property(assign, nonatomic) NSInteger weight;
@property(copy, nonatomic) NSString* name;

若要在外部获取实例变量 _age 的信息,则:

Ivar ageIvar = class_getInstanceVariable([People class], "_age");
NSLog(@"%s %s", ivar_getName(ageIvar), ivar_getTypeEncoding(ageIvar));

结果为:

_age i

获取成员变量信息

ivar_getName(Ivar _Nonnull v) // 获得成员变量的名字
ivar_getTypeEncoding(Ivar _Nonnull v) // 获得成员变量的编码类型

使用见上。

设置、获取成员变量的值

object_setIvar(id _Nullable obj, Ivar _Nonnull ivar, id _Nullable value) 
object_getIvar(id _Nullable obj, Ivar _Nonnull ivar) 

运行:

People* p = [[People alloc] init];
Ivar nameIvar = class_getInstanceVariable([People class], "_name");
object_setIvar(p, nameIvar, @"valenti"); 
NSLog(@"%@", object_getIvar(p, nameIvar));

结果为:

valenti

但这种方法对非对象类型的数据行不通,因为 API 的第三个接口需要我们传 id 类型的值,想要调用这个接口对非对象类型设值,则:

Ivar ageIvar = class_getInstanceVariable([People class], "_age");
object_setIvar(p, ageIvar, (__bridge id)(void*)10);
NSLog(@"%d", p.age);

结果:

10

首先将 10 转成指针变量,即 void*,因为指针变量就是存地址值的,然后转成 id 类型,即 __bridge id

获取成员变量数组

class_copyIvarList(Class _Nullable cls, unsigned int * _Nullable outCount) 

运行:

unsigned int count;
Ivar* ivars = class_copyIvarList([People class], &count);
        
for (int i = 0; i < count; i ++) {
    NSLog(@"%s %s", ivar_getName(ivars[i]), ivar_getTypeEncoding(ivars[i]));
}
free(ivars)

结果为:

_age i
_weight q
_name @"NSString"

调用 runtime 的 API 中有 copy 操作都需要 free 释放掉。

有关方法的 API

动态添加方法

class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types) 

若要给上节动态新建的 Valenti 类中动态添加方法,则:

class_addMethod(Valenti, @selector(run), (IMP)run, "v@:");

run 方法实现为:

void run(id self, SEL _cmd) {
    NSLog(@"%@ %@", self, NSStringFromSelector(_cmd));
}

调用 [v run];
打印结果为:

 run

替换方法实现

class_replaceMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types) 

上面的 People 类增加了 funcAfunB 方法,在外部增加了 funcC 函数:

- (void)funcA {
    NSLog(@"This is function A");
}

- (void)funcB {
    NSLog(@"This is function B");
}

funcC 函数的实现为:

void funcC() {
    NSLog(@"This is function C");
}

那么,要替换 funcA 的实现为 funcC,则:

People* p = [[People alloc] init];
        
class_replaceMethod([People class], @selector(funcA), (IMP)funcC, @encode(void));
[p funcA];

结果为:

This is function C

获得方法列表

class_copyMethodList(Class _Nullable cls, unsigned int * _Nullable outCount)

和获取成员列表数组一样,最后也需要 free
运行:

unsigned int count;
        Method* methods = class_copyMethodList([People class], &count);
        for (int i = 0; i < count; i ++) {
            Method method = methods[i];
            
            NSLog(@"%@", NSStringFromSelector(method_getName(method)));
            NSLog(@"%p", method_getImplementation(method));
            NSLog(@"%s", method_getTypeEncoding(method));
            NSLog(@"%d", method_getNumberOfArguments(method));
            NSLog(@"%s", method_copyReturnType(method));
            NSLog(@"%s", method_copyArgumentType(method, 0));
}
free(methods);

结果为:

funcA
0x100001b30
v16@0:8
2
v
@
funcB
0x100001920
v16@0:8
2
v
@
.cxx_destruct // dealloc 
0x100001a60
v16@0:8
2
v
@
name
0x1000019f0
@16@0:8
2
@
@
setName:
0x100001a20
v24@0:8@16
3
v
@
run
0x1000018c0
v16@0:8
... // 下面是其他属性的 setter/getter 方法

获取方法相关信息

method_getName(Method _Nonnull m) // 返回方法名
method_getImplementation(Method _Nonnull m) // 返回方法实现
method_getTypeEncoding(Method _Nonnull m) // 返回方法编码类型
method_getNumberOfArguments(Method _Nonnull m) // 返回参数数量 
method_copyReturnType(Method _Nonnull m) // 返回方法返回值的编码 void -> v
method_copyArgumentType(Method _Nonnull m, unsigned int index) // 返回目标 index 下参数的编码类型

用法见上。

获取一个实例/类方法

class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name)
class_getClassMethod(Class _Nullable cls, SEL _Nonnull name)

Objective-C 在转成机器码之前会通过 LLVM 先转成中间码,生成中间代码命令

clang -emit-llvm -S 目标文件

生成的代码为全新的语法,具体语法可参考官方文档。

用 block 作为方法实现

imp_implementationWithBlock(id _Nonnull block)

运行:

People* p = [[People alloc] init];
        
class_replaceMethod([People class], @selector(funcA), imp_implementationWithBlock(^{
    NSLog(@"This is a block");
}), @encode(void));
[p funcA];

结果为:

This is a block

交换两个方法的实现

method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2) 

倘若要交换 funcA 和 funcB 的实现,则:

People* p = [[People alloc] init];
Method aMethod = class_getInstanceMethod([People class], @selector(funcA));
Method bMethod = class_getInstanceMethod([People class], @selector(funcB));
        
method_exchangeImplementations(aMethod, bMethod);
[p funcA];
[p funcB];

结果为:

This is function B
This is function A

那么它真正交换的实现是什么?
首先,方法列表在 class_rw_t 结构体的 method_array_t 类型的二位数组 methods 中,methods 的每个子元素都是 method_list_t 类型的一维数组,method_list_t 每个子元素为 method_t 类型,那么 method_t 的结构为:

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

那么,交换 API 中交换的就是这个结构体的 imp。

并且,一旦调用交换 API,就会清空缓存,所有的方法缓存都需要重新走一遍。源码中有体现:

void method_exchangeImplementations(Method m1, Method m2)
{
    if (!m1  ||  !m2) return;

    mutex_locker_t lock(runtimeLock);

    IMP m1_imp = m1->imp;
    m1->imp = m2->imp;
    m2->imp = m1_imp;

    flushCaches(nil); // 清除方法缓存

    updateCustomRR_AWZ(nil, m1);
    updateCustomRR_AWZ(nil, m2);
}

交换方法的 API 在开发中应用极其广泛,Hook 技术中经常能用到该 API,但是对于 NSString、NSArray 和 NSDictionary 进行 hook 的时候需要注意,这三个的真实类型是 __NSArrayI、__NSStringI 和 __NSDictionaryI。对应可变类型的真实类型为:__NSArrayM、__NSStringM 和 __NSDictionaryM,I 为 immutable,不可变,M 为 mutable,可变。

你可能感兴趣的:(iOS - Runtime 中有关类、成员和方法的 API)