用块创建代表 [英] Creating delegates on the spot with blocks

查看:94
本文介绍了用块创建代表的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我喜欢积木,当我不能使用它时会让我感到难过。特别是,每次我使用委托时都会发生这种情况(例如:使用UIKit类,主要是预阻塞功能)。

I love blocks and it makes me sad when I can't use them. In particular, this happens mostly every time I use delegates (e.g.: with UIKit classes, mostly pre-block functionality).

所以我想知道......是否可能 - 使用ObjC-的疯狂力量来做这样的事情?

So I wonder... Is it possible -using the crazy power of ObjC-, to do something like this?

   // 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:

如果,怎么样?有没有理由我们不应该尽可能多地这样做?

If YES, how? And is there a reason why we shouldn't be doing this as much as possible?

编辑

看起来有可能。目前的答案集中在问题的第一部分,即如何。但是对我们应该这样做部分进行一些讨论会很好。

Looks like it IS possible. Current answers focus on the first part of the question, which is how. But it'd be nice to have some discussion on the "should we do it" part.

推荐答案

<好的,我终于开始在GitHub上放置 WoolDelegate 了。现在它只需要我一个月来编写一个正确的自述文件(虽然我想这是一个好的开始)。

Okay, I finally got around to putting WoolDelegate up on GitHub. Now it should only take me another month to write a proper README (although I guess this is a good start).

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

The delegate class itself is pretty straightforward. It simply maintains a dictionary mapping SELs to Block. When an instance recieves a message to which it doesn't respond, it ends up in forwardInvocation: and looks in the dictionary for the selector:

- (void)forwardInvocation:(NSInvocation *)anInvocation {

    SEL sel = [anInvocation selector];
    GenericBlock handler = [self handlerForSelector:sel];

如果找到,则将块的调用函数指针拉出并传递给多汁位:

If it's found, the Block's invocation function pointer is pulled out and passed along to the juicy bits:

    IMP handlerIMP = BlockIMP(handler);

    [anInvocation Wool_invokeUsingIMP:handlerIMP];
}

BlockIMP()功能,以及其他Block-probing代码,归功于 Mike Ash 。实际上,很多这个项目都建立在我从他周五的Q& A's中学到的东西的基础上。如果你还没读过那些文章,你就错过了。)

(The BlockIMP() function, along with other Block-probing code, is thanks to Mike Ash. Actually, a lot of this project is built on stuff I learned from his Friday Q&A's. If you haven't read those essays, you're missing out.)

我应该注意,每次发送特定消息时,都会通过完整的方法解析机制;那里的速度很快。替代方案是Erik H.和 EMKPantry 各自采取的路径,这是创建一个您需要的每个委托对象的新clas,并使用 class_addMethod()。由于 WoolDelegate 的每个实例都有自己的处理程序字典,我们不需要这样做,但另一方面,没有办法缓存查找或者调用。一个方法只能添加到,而不能添加到实例。

I should note that this goes through the full method resolution machinery every time a particular message is sent; there's a speed hit there. The alternative is the path that Erik H. and EMKPantry each took, which is creating a new clas for each delegate object that you need, and using class_addMethod(). Since every instance of WoolDelegate has its own dictionary of handlers, we don't need to do that, but on the other hand there's no way to "cache" the lookup or the invocation. A method can only be added to a class, not to an instance.

我这样做有两个原因:这是一个练习看看我是否可以解决下一步的部分 - 从 NSInvocation 切换到Block调用 - 以及创建一个新的对于每个需要的实例对我来说似乎不太优雅。不管它是不是比我的解决方案更优雅,我将留给每个读者的判断。

I did it this way for two reasons: this was an excercise to see if I could work out the part that's coming next -- the hand-off from NSInvocation to Block invocation -- and the creation of a new class for every needed instance simply seemed inelegant to me. Whether it's less elegant than my solution, I will leave to each reader's judgement.

继续,这个程序的内容实际上在 NSInvocation 类别。这利用 libffi 来调用一个未知的函数,直到运行时 - 块的调用 - 使用的参数在运行时也是未知的(这是可通过 NSInvocation 访问。通常情况下,这是不可能的,因为无法传递 va_list 的原因:编译器必须知道有多少参数以及它们有多大。 libffi包含了解/基于这些平台的调用约定的每个平台的汇编程序。

Moving on, the meat of this procedure is actually in the NSInvocation category that's found in the project. This utilizes libffi to call a function that's unknown until runtime -- the Block's invocation -- with arguments that are also unknown until runtime (which are accessible via the NSInvocation). Normally, this is not possible, for the same reason that a va_list cannot be passed on: the compiler has to know how many arguments there are and how big they are. libffi contains assembler for each platform that knows/is based on those platforms' calling conventions.

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

There's three steps here: libffi needs a list of the types of the arguments to the function that's being called; it needs the argument values themselves put into a particular format; then the function (the Block's invocation pointer) needs to be invoked via libffi and the return value put back into the NSInvocation.

第一部分的实际工作主要由一个函数处理,该函数由Mike Ash再次编写,来自 Wool_buildFFIArgTypeList 。 libffi有内部 struct ,用于描述函数参数的类型。在准备对函数的调用时,库需要一个指向这些结构的指针列表。 NSInvocation NSMethodSignature 允许访问每个参数的编码字符串;从那里翻译到正确的 ffi_type 由一组处理,如果 / else lookups:

The real work for the first part is handled largely by a function which is again written by Mike Ash, called from Wool_buildFFIArgTypeList. libffi has internal structs that it uses to describe the types of function arguments. When preparing a call to a function, the library needs a list of pointers to these structures. The NSMethodSignature for the NSInvocation allows access of each argument's encoding string; translating from there to the correct ffi_type is handled by a set of if/else lookups:

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 中完成的。 :再次从 NSMethodSignature 获取每个参数的大小,并分配一大块内存,然后返回列表:

Next, libffi wants pointers to the argument values themselves. This is done in Wool_buildArgValList: get the size of each argument, again from the NSMethodSignature, and allocate a chunk of memory that size, then return the list:

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的注释,这是任何方法调用的(隐藏的)第二个传递参数.Block的调用指针没有用于保存 SEL 的插槽;它只是将自己作为第一个参数,其余的是正常参数。由于Block,就像在客户端代码中编写的那样,无论如何都无法访问该参数(当时它不存在),我决定忽略它。)

(An aside: there's several notes in the code about skipping over the SEL, which is the (hidden) second passed argument to any method invocation. The Block's invocation pointer doesn't have a slot to hold the SEL; it just has itself as the first argument, and the rest are the "normal" arguments. Since the Block, as written in client code, could never access that argument anyways (it doesn't exist at the time), I decided to ignore it.)

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

libffi now needs to do some "prep"; as long as that succeeds (and space for the return value can be allocated), the invocation function pointer can now be "called", and the return value can be set:

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

在项目的main.m中有一些功能演示。

There's some demonstrations of the functionality in main.m in the project.

最后,关于应该这样做吗?的问题,我认为答案是是的,只要它能让你更有效率。 WoolDelegate 是完全通用的,并且实例可以像任何完全写出的类一样工作。不过,我的意图是制作一个简单的,一次性的代表 - 只需要一两种方法,而不需要经过他们的委托人 - 比写一个全新的课程更少的工作,更清晰/可维护而不是将一些委托方法粘贴到视图控制器中,因为它是放置它们的最简单的地方。充分利用运行时和语言这样的动态可以提高代码的可读性,例如,基于块的 NSNotification 处理程序

Finally, as for your question of "should this be done?", I think the answer is "yes, as long as it makes you more productive". WoolDelegate is completely generic, and an instance can act like any fully written-out class. My intention for it, though, was to make simple, one-off delegates -- that only need one or two methods, and don't need to live past their delegators -- less work than writing a whole new class, and more legible/maintainable than sticking some delegate methods into a view controller because it's the easiest place to put them. Taking advantage of the runtime and the language's dynamism like this hopefully can increase your code's readability, in the same way, e.g., Block-based NSNotification handlers do.

这篇关于用块创建代表的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆