用块创建代表

时间:2013-03-15 17:17:31

标签: ios objective-c macos objective-c-blocks

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

所以我想知道......使用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,怎么样?我们不应该尽可能多地这样做吗?

修改

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

3 个答案:

答案 0 :(得分:9)

好的,我终于把WoolDelegate放在了GitHub上。现在我应该再花一个月时间写一个合适的自述文件(虽然我觉得这是一个好的开始)。

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

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

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

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

    IMP handlerIMP = BlockIMP(handler);

    [anInvocation Wool_invokeUsingIMP:handlerIMP];
}

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

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

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

接下来,这个程序的内容实际上在项目中找到的NSInvocation category中。这利用libffi来调用一个未知的函数,直到运行时 - 块的调用 - 参数在运行时也是未知的(可以通过NSInvocation访问) 。通常,这是不可能的,原因与va_list无法传递的原因相同:编译器必须知道有多少参数以及它们有多大。 libffi包含了解/基于这些平台的每个平台的汇编程序。 calling conventions

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

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

else

接下来,libffi想要指向参数值本身的指针。这是在Wool_buildArgValList中完成的:再次从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; \ 获取每个参数的大小,并分配一大块内存,然后返回列表:

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]; 的注释,这是(隐藏的)第二个传递给任何方法调用的参数.Block的调用指针没有& #39; t有一个插槽来保存SEL;它只有自己作为第一个参数,其余的是#34;普通&#34;参数。由于Block,如客户端代码中所写,永远不会访问那个论点(当时它不存在),我决定忽略它。)

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

SEL

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

最后,至于你的问题&#34;是否应该这样做?&#34;,我认为答案是&#34;是的,只要它能让你提高效率&#34;。 ffi_call(&inv_cif, (genericfunc)theIMP, ret_val, arg_vals); if( ret_val ){ [self setReturnValue:ret_val]; free(ret_val); } 是完全通用的,实例可以像任何完全写出的类一样。不过,我的目的是制作一个简单的,一次性的代表 - 只需要一两种方法,而不需要经过他们的委托人 - 比写一个全新的课程更少的工作,比将一些委托方法粘贴到视图控制器中更易读/可维护,因为它是放置它们的最简单的地方。充分利用运行时和语言这样的动态可以提高代码的可读性,例如,Block-based NSNotification handlers可以提高代码的可读性。

答案 1 :(得分:5)

我刚刚整理了一个让你做到这一点的小项目......

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

答案 2 :(得分:2)

编辑:这是我在了解您的要求后提出的。这只是一个快速入侵,一个让你开始,没有正确实现,也没有经过测试的想法。它应该适用于将发送者作为唯一参数的委托方法。 它的工作原理它应该适用于普通和结构返回的委托方法。

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