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

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

问题描述

我喜欢积木,当我不能使用积木时,我很难过.特别是,这主要发生在我每次使用委托时(例如:使用 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:

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

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.

推荐答案

好吧,我终于有时间把 WoolDelegate 放在上面GitHub.现在我应该只需要一个月的时间来编写一个合适的 README(虽然我认为这是一个好的开始).

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).

委托类本身非常简单.它只是维护一个字典映射 SELs 到 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() 函数以及其他块探测代码都归功于 Mike Ash.实际上,这个项目的很多内容都是建立在我从他的周五问答中学到的东西的基础上的.如果你还没有读过这些文章,你错过了.)

(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 各自采用的路径,它正在创建一个为您需要的每个委托对象创建新类,并使用 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<在项目中找到的/code> 类别.这利用 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 调用函数(Block 的调用指针)并将返回值放回 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 用于描述函数参数的类型.在准备调用函数时,库需要一个指向这些结构的指针列表.NSInvocationNSMethodSignature 允许访问每个参数的编码字符串;从那里转换为正确的 ffi_type 由一组 if/else 查找处理:

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 的注释,它是任何方法调用的(隐藏的)第二个传递参数.块的调用指针没有插槽保存 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 是完全通用的,一个实例可以像任何完全写出的类一样工作.不过,我的目的是制作简单的一次性委托——只需要一两种方法,并且不需要超越委托人——比编写一个全新的类更少的工作,而且更清晰/maintainable 而不是将一些委托方法粘贴到视图控制器中,因为这是放置它们的最简单的地方.像这样利用运行时和语言的动态性可以提高代码的可读性,例如 基于块的 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天全站免登陆