欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

iOS Runntime 动态添加类方法并调用-class_addMethod

程序员文章站 2023-12-17 14:07:22
上手开发 ios 一段时间后,我发现并不能只着眼于完成需求,利用闲暇之余多研究其他的开发技巧,才能在有限时间内提升自己水平。当然,“其他开发技巧”这个命题对于任何一个开发领...

上手开发 ios 一段时间后,我发现并不能只着眼于完成需求,利用闲暇之余多研究其他的开发技巧,才能在有限时间内提升自己水平。当然,“其他开发技巧”这个命题对于任何一个开发领域都感觉不找边际,而对于我来说,尝试接触 objc/runtime 不失为是开始深入探索 ios 开发的第一步。

刚了解 runtime 当然要从比较简单的 api 开始,今天就罗列整理一下 class_addmethod 的相关点:

首先从文档开始。

/** 
* adds a new method to a class with a given name and implementation.
* 
* @param cls the class to which to add a method.
* @param name a selector that specifies the name of the method being added.
* @param imp a function which is the implementation of the new method. the function must take at least two arguments—self and _cmd.
* @param types an array of characters that describe the types of the arguments to the method. 
* 
* @return yes if the method was added successfully, otherwise no 
* (for example, the class already contains a method implementation with that name).
*
* @note class_addmethod will add an override of a superclass's implementation, 
* but will not replace an existing implementation in this class. 
* to change an existing implementation, use method_setimplementation.
*/
objc_export bool class_addmethod(class cls, sel name, imp imp, 
const char *types) 
__osx_available_starting(__mac_10_5, __iphone_2_0);

大意翻译一下,这个方法的作用是,给类添加一个新的方法和该方法的具体实现。分析一下这个方法需要的参数:

class cls

cls 参数表示需要添加新方法的类。

sel name

name 参数表示 selector 的方法名称,可以根据喜好自己进行命名。

imp imp

imp 即 implementation ,表示由编译器生成的、指向实现方法的指针。也就是说,这个指针指向的方法就是我们要添加的方法。

const char *types

最后一个参数 *types 表示我们要添加的方法的返回值和参数。

简要介绍了 class_addmethod 中所需要的参数以及作用之后,我们就可以开始利用这个方法进行添加我们所需要的方法啦!在使用之前,我们首先要明确 objective-c 作为一种动态语言,它会将部分代码放置在运行时的过程中执行,而不是编译时,所以在执行代码时,不仅仅需要的是编译器,也同时需要一个运行时环境(runtime),为了满足一些需求,苹果开源了 runtime source 并提供了开放的 api 供开发者使用。

其次,我们需要知道在什么情况下需要调用 class_addmethod 这个方法。当项目中,需要继承某一个类(subclass),但是父类中并没有提供我需要的调用方法,而我又不清楚父类中某些方法的具体实现;或者,我需要为这个类写一个分类(category),在这个分类中,我可能需要替换/新增某个方法(注意:不推荐在分类中重写方法,而且也无法通过 super 来获取所谓父类的方法)。大致在这两种情况下,我们可以通过 class_addmethod 来实现我们想要的效果。

好了,说了这么多那么到底应该如何调用呢?如果不清楚使用方法,那么看说明书就是最好的方法。在 apple 提供的文档中就有详细的使用方法(objective-c runtime programming guide - dynamic method resolution),以下内容就以 mycar 这个类来详细说明一下具体的使用规则:

首先,既然要给某个类添加我们的方法,就应该继承或者给这个类写一个分类,这里我新建一个名为「mycar」的类,作为「car」类的分类。

#import "car+mycar.h"
@implementation car (mycar)
@end

我们知道,在 objective-c 中,正常的调用方法是通过消息机制(message)来实现的,那么如果类中没有找到发送的消息方法,系统就会进入找不到该方法的处理流程中,如果在这个流程中,我们加入我们所需要的新方法,就能实现运行过程中的动态添加了。这个流程或者说机制,就是 objective-c 的 message forwarding

这个机制中所涉及的方法主要有两个:

+ (bool)resolveinstancemethod:(sel)sel
+ (bool)resolveclassmethod:(sel)sel

两个方法的唯一区别在于需要添加的是静态方法还是实例方法。我们就拿前者来说,既然要添加方法,我们就在「mycar」类中实现它,代码如下:

#import "car+mycar.h"
void startengine(id self, sel _cmd) {
nslog(@"my car starts the engine");
}
@implementation car (mycar)
@end

至此,我们实现了我们要添加的 startengine 这个方法。这是一个 c 语言的函数,它至少包含了 self 和 _cmd 两个参数(self 代表着函数本身,而 _cmd 则是一个 sel 数据体,包含了具体的方法地址)。如果要在这个方法中新增参数呢?见如下代码:

#import "car+mycar.h"
void startengine(id self, sel _cmd, nsstring *brand) {
nslog(@"my %@ car starts the engine", brand);
}
@implementation car (mycar)
@end

只要在那两个必须的参数之后添加所需要的参数和类型就可以了,返回值同样道理,只要把方法名之前的 void 修改成我们想要的返回类型就可以,这里我们不需要返回值。

接着,我们重载 resolveinstancemethod: 这个函数:

#import "car+mycar.h"
#import <objc/runtime.h>
void startengine(id self, sel _cmd, nsstring *brand) {
nslog(@"my %@ car starts the engine", brand);
}
@implementation car (mycar)
+ (bool)resolveinstancemethod:(sel)sel {
if (sel == @selector(drive)) {
class_addmethod([self class], sel, (imp)startengine, "v@:@");
return yes;
}
return [super resolveinstancemethod:sel];
}
@end

解释一下,这个函数在 runtime 环境下,如果没有找到该方法的实现的话就会执行。第一行判断的是传入的 sel 名称是否匹配,接着调用 class_addmethod 方法,传入相应的参数。其中第三个参数传入的是我们添加的 c 语言函数的实现,也就是说,第三个参数的名称要和添加的具体函数名称一致。第四个参数指的是函数的返回值以及参数内容。

至于该类方法的返回值,在我测试的时候,无论这个 bool 值是多少,并不会影响我们的执行目标,一般返回 yes 即可。

如果觉得用 c 语言风格写新函数比较不适应,那么可以改写成以下的代码:

@implementation car (mycar)
+ (bool)resolveinstancemethod:(sel)sel {
if (sel == @selector(drive)) {
class_addmethod([self class], sel, class_getmethodimplementation(self, @selector(startengine:)), "s@:@");
return yes;
}
return [super resolveinstancemethod:sel];
}
- (void)startengine:(nsstring *)brand {
nslog(@"my %@ car starts the engine", brand);
}
@end

其中 class_getmethodimplementation 意思就是获取 sel 的具体实现的指针。

然后创建一个新的类「dynamicselector」,在这个新类中我们实现对「car」的动态添加方法。

#import "dynamicselector.h"
#import "car+mycar.h"
@implementation dynamicselector
- (void)dynamicaddmethod {
car *c = [[car alloc] init];
[c performselector:@selector(drive) withobject:@"bmw"];
}
@end

注意,在这里就不能使用 [self method:] 进行调用了,因为我们添加的方法是在运行时才执行,而编译器只负责编译时的方法检索,一旦对一个对象没有检索到它的 drive 方法,就会报错,所以这里我们使用 performselector:withobject: 来进行调用,保存,运行。

2016-08-26 10:50:17.207 objc-runtime[76618:3031897] my bmw car starts the engine
program ended with exit code: 0

打印结果符合我们期望实现的目标。如果需要返回值,方法类似。

以上所述是小编给大家介绍的ios runntime 动态添加类方法并调用-class_addmethod,希望对大家有所帮助

上一篇:

下一篇: