9

我喜欢积木,它们非常酷。

但是,我发现如果不将所有块都折叠在 Xcode 中(我不喜欢这样做),这些块会弄乱我的代码并使其更难阅读。

我喜欢将我的代码拆分为逻辑方法(选择器)以使其更易于阅读,但(表面上)这对于诸如调度、AFNetworking 和其他几个框架来说是不容易实现的。

我也不关心委托方法,因为这意味着我不能随意命名我的方法,而是依赖其他人认为我需要的方法。

所以,不用像这样写一堆胶水代码:

-(void) reloadData { 
    ...
    [[EventsManager instance] data:YES async:^(NSArray *events) { [self processEvents:events] }];
    ...
}

我可以做这样的事情:

-(void) reloadData {
    ...
    [[EventsManager instance] data:YES async:createBlock(self, @selector(processEvents:))];
    ...
}

哪个更容易阅读(对我来说)。

凭借我们对 Objective-c 的强大功能,以及它的运行时,这应该是可能的,不是吗?不过,我还没有在外面看到过这样的事情。

4

2 回答 2

14

从学术的角度来看,我喜欢你的回答;+1,很明显,你学到了一些东西。

从实际的角度来看,这似乎增加了很多额外的脆弱性,几乎没有减少打字,同时也导致呼叫站点的一些信息丢失。

这样做的好处是它非常明确:

-(void) reloadData { 
    ...
    [[EventsManager instance] data:YES async:^(NSArray *events) { [self processEvents:events] }];
    ...
}

读到这里,你会看到处理参数需要异步回调块,并且processEvents:方法 onself将用于完成实际工作。

该表达式createBlock(self, @selector(processEvents:))是相同的有损表示;它丢失了回调的显式论证以及该论证与被调用方法之间的映射(我经常看到像上面这样带有多个参数的回调块,其中在调用方法之前有一些轻量级逻辑和/或参数处理)。

另请注意,在调用时将非可变参数调用站点作为可变参数处理是违反 C 标准的,并且不适用于具有某些参数列表的某些 ABI。

于 2013-04-27T21:34:49.590 回答
6

是的,这确实是可能的,但是这个解决方案是特定于 ABI 的(不保证在所有平台上都可以工作),并且广泛使用了运行时可用的有关方法的信息。

我们首先必须做的是获取有关我们用块包装的方法的信息。这是通过 完成的NSMethodSignature,其中包含以下信息:

  • 参数数量
  • 每个参数的大小(以字节为单位)
  • 返回类型的大小

这允许我们包装(几乎)任何没有该方法的特定代码的方法,从而创建一个可重用的函数。

其次,我们需要一种在运行时安全地调度方法调用的方法。我们通过 执行此操作NSInvocation,这使我们能够在运行时创建动态且安全的方法调用。

第三,我们需要有一个可以传入任意数量的参数的块,以及它的调度。这是通过 C 的va_listAPI 完成的,并且应该适用于 99% 的方法。

最后,我们需要获取返回值,并能够从我们的块中返回它。这是整个操作中可能无法工作的部分,因为返回结构和 Objective-C 运行时的怪异。

但是,只要您保留原始类型和 Objective-C 对象,这段代码就应该非常适合您。

关于这个实现有几点需要注意:

  • 它依赖于块和函数类型转换的未定义行为,但是,由于 iOS 和 Mac 的调用约定,这不应该造成任何问题(除非您的方法具有与块期望不同的返回类型)。

  • 它还依赖于未定义的行为,其结果是va_arg使用可能不是传递的类型进行调用 - 但是,由于类型的大小相同,因此这永远不会成为问题。

事不宜迟,下面是代码示例,然后是实现:


@interface MyObj : NSObject

-(void) doSomething;

@end

@implementation MyObj

-(void) doSomething
{
    NSLog(@"This is me, doing something! %p", self);
}

-(id) doSomethingWithArgs:(long) arg :(short) arg2{
    return [NSString stringWithFormat:@"%ld %d", arg, arg2];
}

@end

int main() {
    // try out our selector wrapping
    MyObj *obj = [MyObj new];

    id (^asBlock)(long, short) = createBlock(obj, @selector(doSomethingWithArgs::));
    NSLog(@"%@", asBlock(123456789, 456));
}

/* WARNING, ABI SPECIFIC, BLAH BLAH BLAH NOT PORTABLE! */
static inline void getArgFromListOfSize(va_list *args, void *first, size_t size, size_t align, void *dst, BOOL isFirst) {
    // create a map of sizes to types
    switch (size) {
            // varargs are weird, and are aligned to 32 bit boundaries. We still only copy the size needed, though.
            // these cases should cover all 32 bit pointers (iOS), boolean values, and floats too.
        case sizeof(uint8_t): {
            uint8_t tmp = isFirst ? (uint32_t) first : va_arg(*args, uint32_t);
            memcpy(dst, &tmp, size);
            break;
        }

        case sizeof(uint16_t): {
            uint16_t tmp = isFirst ? (uint32_t) first : va_arg(*args, uint32_t);
            memcpy(dst, &tmp, size);
            break;
        }

        case sizeof(uint32_t): {
            uint32_t tmp = isFirst ? (uint32_t) first : va_arg(*args, uint32_t);
            memcpy(dst, &tmp, size);
            break;
        }

            // this should cover 64 bit pointers (Mac), and longs, and doubles
        case sizeof(uint64_t): {
            uint64_t tmp = isFirst ? (uint64_t) first : va_arg(*args, uint64_t);
            memcpy(dst, &tmp, size);
            break;
        }
            /* This has to be commented out to work on iOS (as CGSizes are 64 bits)
            // common 'other' types (covers CGSize, CGPoint)
        case sizeof(CGPoint): {
            CGPoint tmp = isFirst ? *(CGPoint *) &first : va_arg(*args, CGPoint);
            memcpy(dst, &tmp, size);
            break;
        }
             */

            // CGRects are fairly common on iOS, so we'll include those as well
        case sizeof(CGRect): {
            CGRect tmp = isFirst ? *(CGRect *) &first : va_arg(*args, CGRect);
            memcpy(dst, &tmp, size);
            break;
        }

        default: {
            fprintf(stderr, "WARNING! Could not bind parameter of size %zu, unkown type! Going to have problems down the road!", size);
            break;
        }
    }
}

id createBlock(id self, SEL _cmd) {
    NSMethodSignature *methodSig = [self methodSignatureForSelector:_cmd];

    if (methodSig == nil)
        return nil;

    return ^(void *arg, ...) {
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];

        [invocation setTarget:self];
        [invocation setSelector:_cmd];

        NSUInteger argc = [methodSig numberOfArguments];
        va_list args;
        va_start(args, arg);

        for (int argi = 2; argi < argc; argi++) {
            const char *type = [methodSig getArgumentTypeAtIndex:argi];

            NSUInteger size;
            NSUInteger align;

            // get the size
            NSGetSizeAndAlignment(type, &size, &align);

            // find the right type
            void *argument = alloca(size);

            getArgFromListOfSize(&args, arg, size, align, argument, argi == 2);

            [invocation setArgument:argument atIndex:argi];
        }

        va_end(args);

        [invocation invoke];

        // get the return value
        if (methodSig.methodReturnLength != 0) {
            void *retVal = alloca(methodSig.methodReturnLength);
            [invocation getReturnValue:retVal];

            return *((void **) retVal);
        }

        return nil;
    };
}

如果您对此实施有任何问题,请告诉我!

于 2013-04-27T20:04:01.013 回答