我喜欢积木,当我不能使用它时会让我感到难过。特别是,每次我使用委托时都会发生这种情况(例如:使用UIKit类,主要是预阻塞功能)。
所以我想知道......使用ObjC的疯狂力量,做这样的事情是否可能?
// id _delegate; // Most likely declared as class variable or it will be released
_delegate = [DelegateFactory delegateOfProtocol:@protocol(SomeProtocol)];
_delegate performBlock:^{
// Do something
} onSelector:@selector(someProtocolMethod)]; // would execute the given block when the given selector is called on the dynamic delegate object.
theObject.delegate = (id<SomeProtocol>)_delegate;
// Profit!
performBlock:onSelector:
如果YES
,怎么样?我们不应该尽可能多地这样做吗?
修改
看起来有可能。目前的答案集中在问题的第一部分,即如何。但是对“我们应该这样做”部分进行一些讨论会很好。
答案 0 :(得分:9)
好的,我终于把WoolDelegate放在了GitHub上。现在我应该再花一个月时间写一个合适的自述文件(虽然我觉得这是一个好的开始)。
委托类本身非常简单。它只是维护一个字典映射SEL
s到Block。当一个实例接收到它没有响应的消息时,它会在forwardInvocation:
中结束,并在字典中查找选择器:
- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL sel = [anInvocation selector];
GenericBlock handler = [self handlerForSelector:sel];
如果找到了,Block的调用函数指针将被拉出并传递给多汁的位:
IMP handlerIMP = BlockIMP(handler);
[anInvocation Wool_invokeUsingIMP:handlerIMP];
}
(BlockIMP()
函数以及其他Block-probing代码,归功于Mike Ash。实际上,很多这个项目都建立在我从他周五的Q&amp; A&#39中学到的东西上。 ; s。如果你还没读过那些文章,那你就错过了。)
我应该注意,每次发送特定消息时,都会通过完整的方法解析机制;那里的速度很快。另一种选择是Erik H.和EMKPantry各自采用的路径,它为您需要的每个委托对象创建一个新的clas,并使用class_addMethod()
。由于WoolDelegate
的每个实例都有自己的处理程序字典,因此我们不需要这样做,但另一方面,我们无法“缓存”#34;查找或调用。方法只能添加到类,而不能添加到实例。
我之所以这样做是出于两个原因:这是一个练习,看看我是否可以解决接下来要做的部分 - 从NSInvocation
切换到Block调用 - 以及为每个所需的实例创建一个新的类对我来说似乎不太优雅。它是否不如我的解决方案优雅,我将留给每个读者的判断。
接下来,这个程序的内容实际上在项目中找到的NSInvocation
category中。这利用libffi来调用一个未知的函数,直到运行时 - 块的调用 - 参数在运行时也是未知的(可以通过NSInvocation
访问) 。通常,这是不可能的,原因与va_list
无法传递的原因相同:编译器必须知道有多少参数以及它们有多大。 libffi包含了解/基于这些平台的每个平台的汇编程序。 calling conventions
这里有三个步骤:libffi需要一个被调用函数参数类型的列表;它需要将参数值本身放入特定格式;然后需要通过libffi调用函数(Block的调用指针),并将返回值放回NSInvocation
。
第一部分的实际工作主要由一个函数处理,该函数由Mike Ash编写,来自Wool_buildFFIArgTypeList
。 libffi具有内部struct
,用于描述函数参数的类型。在准备对函数的调用时,库需要一个指向这些结构的指针列表。 NSMethodSignature
的{{1}}允许访问每个参数的编码字符串;从那里翻译到正确的NSInvocation
由一组ffi_type
/ if
次查询处理:
else
接下来,libffi想要指向参数值本身的指针。这是在Wool_buildArgValList
中完成的:再次从arg_types[i] = libffi_type_for_objc_encoding([sig getArgumentTypeAtIndex:actual_arg_idx]);
...
if(str[0] == @encode(type)[0]) \
{ \
if(sizeof(type) == 1) \
return &ffi_type_sint8; \
else if(sizeof(type) == 2) \
return &ffi_type_sint16; \
获取每个参数的大小,并分配一大块内存,然后返回列表:
NSMethodSignature
(另外:代码中有几个关于跳过NSUInteger arg_size;
NSGetSizeAndAlignment([sig getArgumentTypeAtIndex:actual_arg_idx],
&arg_size,
NULL);
/* Get a piece of memory that size and put its address in the list. */
arg_list[i] = [self Wool_allocate:arg_size];
/* Put the value into the allocated spot. */
[self getArgument:arg_list[i] atIndex:actual_arg_idx];
的注释,这是(隐藏的)第二个传递给任何方法调用的参数.Block的调用指针没有& #39; t有一个插槽来保存SEL
;它只有自己作为第一个参数,其余的是#34;普通&#34;参数。由于Block,如客户端代码中所写,永远不会访问那个论点(当时它不存在),我决定忽略它。)
libffi现在需要做一些&#34; prep&#34 ;;只要成功(并且可以分配返回值的空间),调用函数指针现在可以被称为&#34;并且可以设置返回值:
SEL
在项目的main.m中有一些功能演示。
最后,至于你的问题&#34;是否应该这样做?&#34;,我认为答案是&#34;是的,只要它能让你提高效率&#34;。 ffi_call(&inv_cif, (genericfunc)theIMP, ret_val, arg_vals);
if( ret_val ){
[self setReturnValue:ret_val];
free(ret_val);
}
是完全通用的,实例可以像任何完全写出的类一样。不过,我的目的是制作一个简单的,一次性的代表 - 只需要一两种方法,而不需要经过他们的委托人 - 比写一个全新的课程更少的工作,比将一些委托方法粘贴到视图控制器中更易读/可维护,因为它是放置它们的最简单的地方。充分利用运行时和语言这样的动态可以提高代码的可读性,例如,Block-based NSNotification
handlers可以提高代码的可读性。
答案 1 :(得分:5)
我刚刚整理了一个让你做到这一点的小项目......
@interface EJHDelegateObject : NSObject
+ (id)delegateObjectForProtocol:(Protocol*) protocol;
@property (nonatomic, strong) Protocol *protocol;
- (void)addImplementation:(id)blockImplementation forSelector:(SEL)selector;
@end
@implementation EJHDelegateObject
static NSInteger counter;
+ (id)delegateObjectForProtocol:(Protocol *)protocol
{
NSString *className = [NSString stringWithFormat:@"%s%@%i",protocol_getName(protocol),@"_EJH_implementation_", counter++];
Class protocolClass = objc_allocateClassPair([EJHDelegateObject class], [className cStringUsingEncoding:NSUTF8StringEncoding], 0);
class_addProtocol(protocolClass, protocol);
objc_registerClassPair(protocolClass);
EJHDelegateObject *object = [[protocolClass alloc] init];
object.protocol = protocol;
return object;
}
- (void)addImplementation:(id)blockImplementation forSelector:(SEL)selector
{
unsigned int outCount;
struct objc_method_description *methodDescriptions = protocol_copyMethodDescriptionList(self.protocol, NO, YES, &outCount);
struct objc_method_description description;
BOOL descriptionFound = NO;
for (int i = 0; i < outCount; i++){
description = methodDescriptions[i];
if (description.name == selector){
descriptionFound = YES;
break;
}
}
if (descriptionFound){
class_addMethod([self class], selector, imp_implementationWithBlock(blockImplementation), description.types);
}
}
@end
使用EJHDelegateObject:
self.alertViewDelegate = [EJHDelegateObject delegateObjectForProtocol:@protocol(UIAlertViewDelegate)];
[self.alertViewDelegate addImplementation:^(id _self, UIAlertView* alertView, NSInteger buttonIndex){
NSLog(@"%@ dismissed with index %i", alertView, buttonIndex);
} forSelector:@selector(alertView:didDismissWithButtonIndex:)];
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Example" message:@"My delegate is an EJHDelegateObject" delegate:self.alertViewDelegate cancelButtonTitle:@"Cancel" otherButtonTitles:@"OK", nil];
[alertView show];
答案 2 :(得分:2)
编辑:这是我在了解您的要求后提出的。这只是一个快速入侵,一个让你开始,没有正确实现,也没有经过测试的想法。它应该适用于将发送者作为唯一参数的委托方法。 它的工作原理它应该适用于普通和结构返回的委托方法。
typedef void *(^UBDCallback)(id);
typedef void(^UBDCallbackStret)(void *, id);
void *UBDDelegateMethod(UniversalBlockDelegate *self, SEL _cmd, id sender)
{
UBDCallback cb = [self blockForSelector:_cmd];
return cb(sender);
}
void UBDelegateMethodStret(void *retadrr, UniversalBlockDelegate *self, SEL _cmd, id sender)
{
UBDCallbackStret cb = [self blockForSelector:_cmd];
cb(retaddr, sender);
}
@interface UniversalBlockDelegate: NSObject
- (BOOL)addDelegateSelector:(SEL)sel isStret:(BOOL)stret methodSignature:(const char *)mSig block:(id)block;
@end
@implementation UniversalBlockDelegate {
SEL selectors[128];
id blocks[128];
int count;
}
- (id)blockForSelector:(SEL)sel
{
int idx = -1;
for (int i = 0; i < count; i++) {
if (selectors[i] == sel) {
return blocks[i];
}
}
return nil;
}
- (void)dealloc
{
for (int i = 0; i < count; i++) {
[blocks[i] release];
}
[super dealloc];
}
- (BOOL)addDelegateSelector:(SEL)sel isStret:(BOOL)stret methodSignature:(const char *)mSig block:(id)block
{
if (count >= 128) return NO;
selectors[count] = sel;
blocks[count++] = [block copy];
class_addMethod(self.class, sel, (IMP)(stret ? UBDDelegateMethodStret : UBDDelegateMethod), mSig);
return YES;
}
@end
用法:
UIWebView *webView = [[UIWebView alloc] initWithFrame:CGRectZero];
UniversalBlockDelegate *d = [[UniversalBlockDelegate alloc] init];
webView.delegate = d;
[d addDelegateSelector:@selector(webViewDidFinishLoading:) isStret:NO methodSignature:"v@:@" block:^(id webView) {
NSLog(@"Web View '%@' finished loading!", webView);
}];
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://google.com"]]];