在我的课程中,我有一个关于UIViewController的参考,并希望在运行时在这个ViewController上实现一个委托。委托只有一个方法(有两个参数),当调用ViewController上的delegate-method时,我的类应该处理这个调用。
我很确定这种方法可以通过某种方式调整等等,但我不知道如何实现这一目标。
答案 0 :(得分:3)
你想要什么是可能的,但这不是方法调整,因为你不想切换到方法,而是添加一个新方法。由于Objective-C的动态特性,它可以完成,但它仍然是一个肮脏的黑客,所以也向库供应商提交功能请求。
你想要的是class_addMethod()
和一个C函数以及它的实际实现。还有一件事,Objective-C方法是 C方法,但有两个隐含参数,self
和_cmd
,必须记住(在创建C方法时)当告诉class_addMethod
你的方法签名时。这里有一个如何拉出这样的东西的SSCE:
#import <Foundation/Foundation.h>
#import <objc/runtime.h> // Required for class_addMethod()
@interface MyClass : NSObject
@end
@implementation MyClass
@end
@protocol MyProtocol <NSObject>
- (void)printString:(NSString *)string;
@end
// Note the method signature containing the
// two implicit parameters self and _cmd!
void MyClassPrinStringIMP(id self, SEL _cmd, NSString *string)
{
NSLog(@"Hi I'm %@:%s and this is the string: %@", self, sel_getName(_cmd), string);
}
void PimpMyClass()
{
// The last argument is the signature. First character is the return type, in our case void
// Then comes self and _cmd, followed by the NSString. You can use @encode() to find out how your
// type is encoded. Best is to build this string at runtime, since the encoding can change with architectures
class_addMethod([MyClass class], @selector(printString:), (IMP)MyClassPrinStringIMP, "v@:@");
}
int main(int argc, const char * argv[])
{
@autoreleasepool
{
PimpMyClass();
id foo = [[MyClass alloc] init]; // id, to silence the compiler!
[foo printString:@"Hello World"];
}
return 0;
}
示例输出:
Hi I'm <MyClass: 0x100101810>:printString: and this is the string: Hello World
编辑:您可能会发现,在运行时检查传递的对象是否符合协议,或者使用conformsToProtocol:
。由于此代码只是添加了方法实现,它仍然会失败,但您可以告诉运行时您完全使用这一个函数调用实现该协议:
class_addProtocol([MyClass class], @protocol(MyProtocol));
Objective-Cs的动态性和消息转发已经被@JasperBlues称赞,但是,Objective-C中有一个特定的类可以实现这一目的:NSProxy
。它旨在拦截已发送的消息并将其动态分派给相关目标,并使用高级NSInvocation
方法。如果您可以以某种方式传递代理对象作为委托(取决于您的代码允许和不允许的内容),创建NSProxy
子类可能是最干净的方法。
但是,请注意,然后你最终得到了一个包裹你的另一个对象的填充对象,它有自己的痛苦,当你尝试通过->
语法直接访问变量时会破坏。它不是一个完全不可见的代理,但对大多数情况来说都足够好了。
答案 1 :(得分:2)
首先,一些评论表明你所要求的是“做坏事”或“肮脏的黑客”。我在这里不同意。大多数现代面向对象语言都支持这些功能,并且它们被许多系统级框架用于良好的效果。当然,或许使用这些动态特性而不是真正需要(为了娱乐或实践),即使更简单的方法可以正常工作也是人性的。要小心这一点。
Objective-C令人钦佩,因为它有点遗留的语言并且接近“裸机”,但却具有令人惊讶的动态水平,使得在没有任何外部库或框架的情况下支持这些要求相对容易。
除了使用另一个答案正确指出的 class_addMethod 指南外,还有其他一些方法:
消息转发:(推荐)
所有NSObject子类都能够将他们无法响应的方法转发给另一个目标对象。这类似于蹦床的低级概念。 Apple publishes a guide on using this approach 。
使用正向调用的优点是它使用 NSInvocation 抽象级别,而不是直接调用C ObjC运行时API。这抽象出以下细节:
解析实例方法:
通过将C函数注册为响应给定消息的实现来创建实例方法。使用IMP_ImplementationWithBlock:
可以使用块整齐地完成此操作+ (BOOL)resolveInstanceMethod:(SEL)sel
{
IMP imp = imp_implementationWithBlock((__bridge id) objc_unretainedPointer(
^(id me, BOOL firstParam, NSString* secondParam)
{
//Implementation goes in here
return something; //something of type 'id'
}));
class_addMethod(self, sel, imp, "@@:");
return YES;
}
return NO;
}
使用libffi:
Libffi也可以做这种事情,但如果你使用的是纯Objective-C则没有必要,因为运行时已经有了这些功能。