使用块在现场创建代表 [英] Creating delegates on the spot with blocks
问题描述
我喜欢积木,当我不能使用积木时,我很难过.特别是,这主要发生在我每次使用委托时(例如:使用 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).
委托类本身非常简单.它只是维护一个字典映射 SEL
s 到 Block.当一个实例收到一条没有响应的消息时,它会在 forwardInvocation:
中结束并在字典中查找选择器:
The delegate class itself is pretty straightforward. It simply maintains a dictionary mapping SEL
s 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
用于描述函数参数的类型.在准备调用函数时,库需要一个指向这些结构的指针列表.NSInvocation
的 NSMethodSignature
允许访问每个参数的编码字符串;从那里转换为正确的 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 struct
s 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屋!