在 ios开发 Runtime 详解part1中我已经解释了Introspection,接下来介绍Runtime的其它特性。
Runtime能做什么?
1、Introspection, 获得对象中的信息,如Class, Selector(SEL), Method:
2、Dynamic Method Resolution (动态方法解析)
在苹果的文档里给出了一个动态添加c方法的例子: 例子, 我在这里做一些扩展:
首先我在RuntimeObject.h类中添加三个方法:
- (void)addInstanceMethod: (NSString *)str; // 添加实例方法
+ (void)addClassMethod: (NSString *)str; // 添加类方法
- (void)addCMethod; // 添加c方法
在RuntimeObject.m中我们有三个方法:
/**
动态绑定的实例方法
@param str 传递的参数
*/
- (void)instanceMethod: (NSString *)str {
NSLog(@"execute instance Method, pass value: %@", str);
}
/**
动态绑定的类方法
@param str 传递的参数
*/
+ (void)classMethod: (NSString *)str {
NSLog(@"execute class Method, pass value: %@", str);
}
/**
动态绑定的c方法, 至少包含self和_cmd两个参数
@param self c方法必须传递
@param _cmd c方法必须传递
*/
void dynamicMethodIMP(id self, SEL _cmd) {
printf("execute C Method");
}
现在我们需要通过调用.h文件里的方法来执行.m里对应绑定的方法:
[ro addInstanceMethod: @"instance method"];
[RuntimeObject addClassMethod: @"class method"];
[ro addCMethod];
此时调用一定是会出错的,因为我们还没有进行动态绑定,要绑定RuntimeObject.m中的三个方法,我们需要借助实现resolveInstanceMethod和resolveClassMethod这两个方法来动态添加我们需要绑定的方法:
+ (BOOL)resolveInstanceMethod:(SEL)aSEL{
if (aSEL == @selector(addInstanceMethod:)) {
class_addMethod([self class], aSEL, class_getMethodImplementation([self class], @selector(instanceMethod:)), "v@:");
NSLog(@"%@", @"dynamicMethodIMP added");
return YES;
} else if (aSEL == @selector(addCMethod)) {
class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveClassMethod:aSEL];
}
+ (BOOL)resolveClassMethod:(SEL)aSEL {
if (aSEL == @selector(addClassMethod:)) {
class_addMethod(object_getClass(self), aSEL, class_getMethodImplementation(object_getClass(self), @selector(classMethod:)), "v@:");
return YES;
}
return [super resolveClassMethod:aSEL];
}
要注意,添加实例方法和类方法均是调用了runtime里class_addMeghod方法,这个方法结构如下:
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types);
cls: 是要将方法添加到的目标类
name: 是一个selector,指定添加的方法的名字
imp: 实现的新方法,必须包含self和_cmd两个参数,由于oc的方法中默认有这两个参数,在代码中不必特别添加,但是c的方法需要加上这两个参数。
types: 描述参数的类型,由于方法都含有self和_cmd两个参数,第二个字符一定是@"", 而第一个字符表示返回值的类型, 我们这里因为返回值是void就用v表示,第三个字符表示一个方法的selector,我们放':'来表示,所以types传入"v@:",此处类型的编码可以在这里查看: 类型编码
要注意的是当self是类对象的时候,调用self class返回的是self,要获得class,需要通过object_getClass(self)来获得,否则会出错。
动态方法解析可以让我们像对属性修饰@dynamic一样,不让系统提供实现的方法,自定义方法的实现,也可以用来绑定c的方法来进行实现,一旦绑定,我们也可以用performSelector来直接调用.m里绑定的方法:
[ro addInstanceMethod: @"instance method"]; // 动态绑定了instanceMethod:方法
if ([ro respondsToSelector: @selector(instanceMethod:)]) {
[ro performSelector:@selector(instanceMethod:) withObject:@"call instance method"];
// 输出: execute instance Method, pass value: call instance method
}
此文介绍了Dynamic Method Resolution (动态方法解析)的用法,希望能对你有所帮助。