好的,我终于把WoolDelegate 放到了 GitHub 上。现在我只需要再花一个月的时间来写一个合适的 README(尽管我想这是一个好的开始)。
委托类本身非常简单。它只是维护一个字典映射SEL
s 到 Block。当一个实例收到一条它没有响应的消息时,它最终会进入forwardInvocation:
并在字典中查找选择器:
- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL sel = [anInvocation selector];
GenericBlock handler = [self handlerForSelector:sel];
如果找到,则将 Block 的调用函数指针拉出并传递给 juicy bits:
IMP handlerIMP = BlockIMP(handler);
[anInvocation Wool_invokeUsingIMP:handlerIMP];
}
(该BlockIMP()
功能以及其他块探测代码感谢Mike Ash。实际上,这个项目的很多内容都是基于我从他的周五问答中学到的东西。如果你还没有读过这些文章,你就错过了.)
我应该注意到,每次发送特定消息时,这都会通过完整的方法解析机制;那里有一个速度。另一种方法是 Erik H. 和EMKPantry各自采用的路径,即为您需要的每个委托对象创建一个新类,并使用class_addMethod()
. 由于每个实例WoolDelegate
都有自己的处理程序字典,我们不需要这样做,但另一方面,没有办法“缓存”查找或调用。方法只能添加到类,不能添加到实例。
我这样做有两个原因:这是一个练习,看看我是否可以解决接下来的部分——从NSInvocation
到 Block 调用的切换——以及为每个需要的实例创建一个新类似乎简单对我不优雅。它是否不如我的解决方案优雅,我将留给每个读者的判断。
继续前进,这个过程的核心实际上是在项目中找到的NSInvocation
类别中。这利用libffi调用一个直到运行时才知道的函数——块的调用——使用在运行时之前也是未知的参数(可以通过 访问NSInvocation
)。通常,这是不可能的,原因与 ava_list
不能传递的原因相同:编译器必须知道有多少参数以及它们有多大。libffi 包含每个知道/基于这些平台的调用约定的平台的汇编程序。
这里有三个步骤: libffi 需要一个被调用函数的参数类型列表;它需要将参数值本身放入特定的格式;然后需要通过 libffi 调用函数(块的调用指针)并将返回值放回NSInvocation
.
第一部分的实际工作主要由 Mike Ash 编写的函数处理,该函数从Wool_buildFFIArgTypeList
. libffi 有 internal struct
s 用于描述函数参数的类型。在准备调用函数时,库需要指向这些结构的指针列表。NSMethodSignature
允许访问每个参数的NSInvocation
编码字符串;从那里翻译到正确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
:再次从 中获取每个参数的大小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];
(顺便说一句:代码中有几个关于跳过 的注释SEL
,这是任何方法调用的(隐藏的)第二个传递参数。块的调用指针没有一个插槽来保存SEL
;它只是将自己作为第一个参数,其余的是“正常”参数。由于用客户端代码编写的块,无论如何都无法访问该参数(它当时不存在),我决定忽略它。)
libffi 现在需要做一些“准备”;只要成功(并且可以为返回值分配空间),现在可以“调用”调用函数指针,并且可以设置返回值:
ffi_call(&inv_cif, (genericfunc)theIMP, ret_val, arg_vals);
if( ret_val ){
[self setReturnValue:ret_val];
free(ret_val);
}
在项目的 main.m 中有一些功能演示。
最后,关于你“应该这样做吗?”的问题,我认为答案是“是的,只要它能让你更有效率”。WoolDelegate
是完全通用的,实例可以像任何完全写出的类一样工作。不过,我的意图是制作简单的一次性委托——只需要一两个方法,并且不需要经过委托——比编写一个全新的类更少工作,而且更易读/maintainable 比将一些委托方法粘贴到视图控制器中,因为这是最容易放置它们的地方。像这样利用运行时和语言的活力有望提高代码的可读性,就像基于块的NSNotification
处理程序一样。