有关类的 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];
打印结果为:
<Valenti: 0x102032170> run
替换方法实现
class_replaceMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types)
上面的 People 类增加了 funcA
、funB
方法,在外部增加了 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,可变。