用块创build代表

我喜欢积木,当我不能使用它时,这让我很难过。 尤其是,每当我使用委托时,都会发生这种情况(例如:使用UIKit类,主要是预块function)。

所以我想知道…有没有可能 – 使用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 ,如何? 我们有什么理由不应该这样做?

编辑

看起来像是可能的。 目前的答案集中在问题的第一部分,这是如何。 但是,关于“ 我们应该这样做 ”的一部分讨论会很好。

好吧,我终于把WoolDelegate放在GitHub上了。 现在只需要花费我一个月的时间来写一个正确的自述文件(尽pipe我认为这是一个好的开始)。

委托类本身非常简单。 它只是维护一个字典映射SEL到块。 当一个实例收到一个没有响应的消息时,它会在forwardInvocation:结束forwardInvocation:并在select器的字典中查找:

 - (void)forwardInvocation:(NSInvocation *)anInvocation { SEL sel = [anInvocation selector]; GenericBlock handler = [self handlerForSelector:sel]; 

如果find了,则块的调用函数指针被拉出并传递给多汁的位:

  IMP handlerIMP = BlockIMP(handler); [anInvocation Wool_invokeUsingIMP:handlerIMP]; } 

BlockIMP()函数,以及其他的Block-probing代码,都要感谢Mike Ash ,其实这个项目很多都是build立在我周五Q&A学到的东西上的,如果你没有阅读那些文章,重新错过了。)

我应该注意到,每次发送一个特定的消息时,都要通过完整的方法parsing机制。 那里有一个速度。 另一种方法是Erik H.和EMKPantry各自采用的path, 即为每个需要的委托对象创build一个新的类,并使用class_addMethod() 。 由于WoolDelegate每个实例都有自己的处理程序字典,所以我们不需要这样做,但另一方面却无法“caching”查找或调用。 一个方法只能被添加到一个 ,而不是一个实例。

我这样做是因为两个原因:这是一个练习,看看是否可以制定接下来的部分 – 从NSInvocation到Block调用的交接 – 以及为每个需要的实例创build一个新对我来说似乎不雅。 无论是不是比我的解决scheme优雅,我会留给每个读者的判断。

继续,这个过程的肉实际上是在项目中find的NSInvocation类别 。 这使用libffi来调用一个函数,直到运行时才知道 – Block的调用 – 参数也是未知的,直到运行时(可通过NSInvocation访问)。 通常情况下,这是不可能的,因为va_list不能被传递:编译器必须知道有多less个参数以及它们有多大。 libffi包含每个平台的汇编程序,该平台知道/基于这些平台的调用约定 。

这里有三个步骤:libffi需要一个被调用函数的参数types列表; 它需要将参数值本身放入一个特定的格式; 那么函数(块的调用指针)需要通过libffi调用,返回值放回NSInvocation

第一部分的实际工作很大程度上由一个由Mike Ash编写的函数来处理, Wool_buildFFIArgTypeList调用该Wool_buildFFIArgTypeList 。 libffi具有内部struct ,它用来描述函数参数的types。 准备调用函数时,库需要一个指向这些结构的指针列表。 NSInvocationNSMethodSignature允许访问每个参数的编码string; 从那里转换到正确的ffi_type由一组if / else查找处理:

 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; \ 

接下来,libffi需要自己指向参数值的指针。 这是在Wool_buildArgValList中完成的:再次从Wool_buildArgValList获取每个参数的大小,并分配一个大小的内存块,然后返回列表:

 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]; 

(另外:在代码中有几个关于跳过SEL注释,它是任何方法调用的(隐藏的)第二个参数,块的调用指针没有一个槽来保存SEL ,它只是自己第一个参数,其余的是“正常”的参数,因为用客户代码写的Block永远不能访问这个参数(当时不存在),所以我决定忽略它。

libffi现在需要做一些“准备”; 只要成功(并且可以分配返回值的空间),调用函数指针现在可以被“调用”,并且可以设置返回值:

 ffi_call(&inv_cif, (genericfunc)theIMP, ret_val, arg_vals); if( ret_val ){ [self setReturnValue:ret_val]; free(ret_val); } 

项目中的main.m中有一些function演示。

最后,关于“这个应该怎么办?”的问题,我想答案是“是的,只要能让你更有效率”。 WoolDelegate是完全通用的,一个实例可以像任何完全写出来的类一样。 然而,我的意图是要做一个简单的,一次性的代表 – 只需要一两种方法,不需要经过他们的代理人 – 比写一个全新的课程更less的工作,更清晰/维护比坚持一些委托方法到一个视图控制器,因为这是最简单的地方把它们。 利用运行时和像这样的语言的dynamic,希望可以增加你的代码的可读性,例如, 基于块的NSNotification处理程序 。

我只是把一个小项目,让你做这个…

 @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]; 

编辑:这是我了解你的要求之后所出现的。 这只是一个快速入门,让你开始的一个想法,它没有正确实施,也没有经过testing。 它应该适用于将发件人作为唯一参数的委托方法。 它的工作原理应该与普通的和结构返回的委托方法一起工作。

 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"]]];