美文网首页iOS Developer
Block 如何使用可变参数

Block 如何使用可变参数

作者: SmallflyBlog | 来源:发表于2016-11-25 16:16 被阅读981次

在 Objective-C 中有些函数可以接受可变的参数,比如常用的日志输出 NSLog,它的原型如下:

    FOUNDATION_EXPORT void NSLog(NSString *format, ...);

苹果官方有一个类似的例子来解析其内部实现。

既然函数可以接受可变的参数,那 Block 是否也能实现这一点呢?

其实只要在 Block 声明的时候参数列表填空,该类型的 Block 就可以接收任意个数和任意类型的参数了。

    typedef void (^SFCommonBlock)();

使用方法:

    void (^block1)(void) = ^{
        NSLog(@"Empty Block");
    };
    
    void (^block2)(NSInteger) = ^(NSInteger code){
        NSLog(@"code = %ld", code);
    };
    
    void (^block3)(id, NSInteger) = ^(id response, NSInteger code) {
        NSLog(@"response = %@, code = %ld", response, code);
    };
    
    SFCommonBlock commonBlock1 = block1;
    SFCommonBlock commonBlock2 = block2;
    SFCommonBlock commonBlock3 = block3;

    commonBlock1(); // Empty Block
    commonBlock2(999);// Code = 999
    commonBlock3(@"Apple", 1984);// Response = Apple, Code = 1984

我们可以看到 commonBlock1,commonBlock2,commonBlock3,都是 SFCommonBlock 类型,可以传入不同类型和个数的参数。然而这样用看起来并没什么意义,还不如直接调用原来的 Block。

不过如果将 Block 作为参数传递的时候就有意义了,例如:

- (void)requestWithNumberOfArguments:(NSUInteger)count responseBlock:(SFCommonBlock)block {
    switch (count) {
        case 0: {
            block();
        } break;
        
        case 1: {
            block(999);
        } break;
        
        case 2: {
            block(@"Response", 1000);
        } break;
        
        default:
            break;
    }
}

调用方只要传入 Block 参数的个数,就可以通过判断来执行相应的 Block。然而有一个不足之处是,调用时就没有代码提示了,需要手动挨个填写参数。

还有一个不是很优雅的地方是,每次调用的时候必须传递参数个数。能否有什么方法直接从传入的 Block 里面获取到参数的个数信息呢?

Objective-C 中方法调用实则是在 Runtime 期间消息的发送,每个对象能够接受的消息都会对应一个方法签名。方法签名中包含:参数的个数,返回值的类型,以及执行是异步的还是同步的。

如果我们能够拿到 Block 执行的方法签名,那么就可以知道 Block 参数的个数了。然而 Block 虽然是一个 Objective-C 的对象,但是并没有与之相关的公开 API,所幸 Runtime 的是开源的,可以在这里看到 Block 被编译后的结构:Block Implementation Specification

Block 的原型对应的是结构体,虽然我们无法直接进行访问,但我们可以自己撸一个跟原型一模一样的结构体,并将 block 实例强指过去,再通过这个结构体实例访问到 Block 的签名。结构体的原型是这个样子:

struct Block_literal_1 {
    void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock
    int flags;
    int reserved;
    void (*invoke)(void *, ...);
    struct block_descriptor {
        unsigned long int reserved; // NULL
           unsigned long int size;         // sizeof(struct Block_literal_1)
        // optional helper functions
           void (*copy_helper)(void *dst, void *src);     // IFF (1<<25)
           void (*dispose_helper)(void *src);             // IFF (1<<25)
        // required ABI.2010.3.16
        const char *signature;                         // IFF (1<<30)
    } *descriptor;
    // imported variables
};

enum {
    BLOCK_HAS_COPY_DISPOSE =  (1 << 25),
    BLOCK_HAS_CTOR =          (1 << 26), // helpers have C++ code
    BLOCK_IS_GLOBAL =         (1 << 28),
    BLOCK_HAS_STRET =         (1 << 29), // IFF BLOCK_HAS_SIGNATURE
    BLOCK_HAS_SIGNATURE =     (1 << 30),
};

在 block_descriptor 结构体中可以看到 signature 结构体。想必这就是 Block 的方法签名了,可以用这个 const char* 指针来初始化 NSMethodSignature 对象。

至于如何将 Block 映射到结构体上,有前辈已经实现过了,传送门。将指向 Block 的结构体的指针偏移指向 signature 字段,就可以取出签名字符串,进而初始化 NSMethodSignature 实例,使用代码如下:

- (void)responseBlock:(SFCommonBlock)block {
   CTBlockDescription *blockDescription = [[CTBlockDescription alloc] initWithBlock: block]; 
   NSMethodSignature * methodSignature = blockDescription.blockSignature;
   
   NSUInteger numberOfArguments = methodSignature.numberOfArguments;
   switch (numberOfArguments - 1) { // 减去 block 对象自身
       case 0: {
           block();
       } break;
       
       case 1: {
           block(999);
       } break;
       
       case 2: {
           block(@"Response", 1000);
       } break;
       
       default:
           break;
   }    
}

CTBlockDescription 是通过传入的 block 初始化出来的一个 Block 描述对象。其中有一个属性 blockSignature 就是 Block 的签名。

参考

非主流代码技巧
NSInvocation动态调用任意block
Block Implementation Specification

相关文章

  • Block 如何使用可变参数

    在 Objective-C 中有些函数可以接受可变的参数,比如常用的日志输出 NSLog,它的原型如下: 苹果官方...

  • ★10.关于可变参数模板

    可变参数函数模板 可变参数类模板 可变参数函数模板的使用 转发参数包

  • iOS可变参数和Format Function

    函数中使用可变参数使用NS_REQUIRES_NIL_TERMINATION 检查参数是否以nil 结尾,可变参数...

  • block使用方法

    block基本使用 没有参数,=左边的()里面要写上void。 作为方法的参数传递使用 怎么区分参数是block?...

  • 可变参数

    可变参数 格式: 可变参数注意事项: 如果一个函数的参数使用了可变参数,那么调用该方法时可以传递参数也可以不传递。...

  • Lua可变参数

    Lua 函数可以接受可变数目的参数,和 C 语言类似,在函数参数列表中使用...表示函数有可变的参数。 把可变参数...

  • Swift Founction 和 Closures

    可变参数数量和参数泛型约束 可以使用 ..., 来设置可变参数数量 使用 where 约束泛型实现特定的协议 方法...

  • 简述Python3中三种函数参数传递类型

    在这篇博文中,我们会使用sum()和information()作为例子来如何正确地向函数传入参数 可变参数(*ar...

  • 提示五十三、五十四

    提示五十三:慎用可变参数。 如果可变参数要求至少要求一个参数的话,可以使用这种样式。 在重视性能的情况下,因为可变...

  • Block相关

    Block基本使用 1.1、block声明:返回值(^block变量名)(参数)void(^block)(); 1...

网友评论

    本文标题:Block 如何使用可变参数

    本文链接:https://www.haomeiwen.com/subject/emqwpttx.html