【问题标题】:Checking Objective-C block type?检查Objective-C块类型?
【发布时间】:2012-02-21 07:52:43
【问题描述】:

这主要是一种好奇心,我不太确定它的实际用途是什么,但是就这样吧。

既然块也是Objective-C对象,是否可以检查它们的类型?也就是说,它是否响应isKindOfClass: 消息以及如何针对块使用该消息?

我天真的以为大概是这样的:

-(void) aMethod {
    typedef int (^BlockA)(int x, int y);
    id blockVar = ...; // get a block from somewhere
    if([blockVar isKindOfClass:BlockA]) {
        BlockA blockVarA = blockVar;
        int result = blockVarA(1,2);
    }
}

上面的代码可能行不通。但是如果可以检查一个块的类型,那么正确的方法是什么?

【问题讨论】:

    标签: objective-c oop closures objective-c-blocks introspection


    【解决方案1】:

    一个老问题,但无论如何:

    如果你想要一个简单的方法:(使用 -fno-objc-arc 编译)

    Class __NSGlobalBlock__CLASS () {
        static Class result = nil;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            dispatch_block_t thisIsAGlobalBlock = ^{// a block with no variables will be a __NSGlobalBlock__
            };
            result = [[thisIsAGlobalBlock class] retain];
        });
        return result;
    };
    
    Class __NSStackBlock__CLASS () {
        static Class result = nil;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            __block dispatch_block_t thisIsAStackBlock = ^{
                return ;// we really DON'T want infinate recursion
                thisIsAStackBlock();// including a reference to __block var makes this a __NSStackBlock__
            };
            result = [[thisIsAStackBlock class] retain];
        });
        return result;
    };
    
    Class __NSMallocBlock__CLASS () {
        static Class result = nil;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            __block dispatch_block_t thisIsAMallocBlock = Block_copy(// << turns the __NSStackBlock__ Block into a __NSMallocBlock__
                                                                     ^{
                return ;// we really DON'T want infinate recursion
                thisIsAMallocBlock();// including a reference to __block var makes this a __NSStackBlock__
            });
    
            result = [[thisIsAMallocBlock class] retain];
            Block_release(thisIsAMallocBlock);
        });
        return result;
    };
    

    测试代码:

    @autoreleasepool {
    
        __block dispatch_block_t iAmAGlobalBlock = ^{
    
    
        };
    
    
        __block dispatch_block_t iAmAStackBlock = ^{
            return;
            iAmAStackBlock();
        };
    
    
        dispatch_block_t iAmHeapBlock = Block_copy(iAmAStackBlock);
        dispatch_block_t iAmNotAHeapBlock = Block_copy(iAmAGlobalBlock);
    
    
        if ([iAmAGlobalBlock isKindOfClass:__NSGlobalBlock__CLASS()]) {
    
            NSLog(@"very great success!");
        }
    
        if ([iAmAStackBlock isKindOfClass:__NSStackBlock__CLASS()]) {
    
            NSLog(@"another great success!");
        }
    
    
        if ([iAmHeapBlock isKindOfClass:__NSMallocBlock__CLASS()]) {
    
            NSLog(@"also great success!");
        }
    
    
        if ([iAmNotAHeapBlock isKindOfClass:__NSGlobalBlock__CLASS()]) {
    
            NSLog(@"yet another great success!");
        }
    
    
    
    
        NSLog (@"Block classes, as reported by NSStringFromClass():\n__NSGlobalBlock__CLASS() = %@\n__NSStackBlock__CLASS()  = %@\n__NSMallocBlock__CLASS() = %@\n[iAmAGlobalBlock class]  = %@\n[iAmAStackBlock class]   = %@\n[iAmHeapBlock class]     = %@\n[iAmNotAHeapBlock class] = %@\n",
    
               NSStringFromClass(__NSGlobalBlock__CLASS()),
               NSStringFromClass(__NSStackBlock__CLASS()),
               NSStringFromClass(__NSMallocBlock__CLASS()),
    
               NSStringFromClass([iAmAGlobalBlock class]),
               NSStringFromClass([iAmAStackBlock  class]),
               NSStringFromClass([iAmHeapBlock    class]),
               NSStringFromClass([iAmNotAHeapBlock    class])
    
    
               );
    
    
    
        Block_release(iAmHeapBlock);
        Block_release(iAmNotAHeapBlock);// not really needed, but since we did "Block_copy" it...
    
    }
    

    【讨论】:

      【解决方案2】:

      可以,有点。

      但首先,让我们消除歧义。 -[NSObject isKindOfClass:] 可以告诉你这是一个障碍,仅此而已。例如。我相信行代码——表面上很不幸地是A BAD IDEA——对于当前的 Lion 和 iOS 5.x 上的块将返回 YES:

      [myBlock isKindOfClass:NSClassFromString(@"NSBlock")]
      

      这不会帮助您区分块的功能签名。

      但是可以通过从块的记录内部结构中获取签名来完成。下面是一个示例 OS X 命令行应用程序的代码,其中大部分来自 Mike Ash 的MABlockClosure(很棒的detailed explanation)。 (更新:Github 项目 CTObjectiveCRuntimeAdditions 显然也为此目的提供了库代码。)

      #import <Foundation/Foundation.h>
      
      struct BlockDescriptor {
          unsigned long reserved;
          unsigned long size;
          void *rest[1];
      };
      
      struct Block {
          void *isa;
          int flags;
          int reserved;
          void *invoke;
          struct BlockDescriptor *descriptor;
      };
      
      static const char *BlockSig(id blockObj)
      {
          struct Block *block = (void *)blockObj;
          struct BlockDescriptor *descriptor = block->descriptor;
      
          int copyDisposeFlag = 1 << 25;
          int signatureFlag = 1 << 30;
      
          assert(block->flags & signatureFlag);
      
          int index = 0;
          if(block->flags & copyDisposeFlag)
              index += 2;
      
          return descriptor->rest[index];
      }
      
      int main(int argc, const char * argv[])
      {
          @autoreleasepool {
      
              int (^block)(NSNumber *) = ^(NSNumber *num) { 
                  NSLog(@"%@ %@", NSStringFromClass([num class]), num); 
                  return [num intValue]; 
              };
              NSLog(@"signature %s", BlockSig(block));
              NSLog(@"retval %d", (int)block([NSNumber numberWithInt:42]));
          }
          return 0;
      }
      

      运行这个,你应该得到类似的东西:

      [58003:403] signature i16@?0@8
      [58003:403] __NSCFNumber 42
      [58003:403] retval 42
      

      签名中的数字(我被告知它们是偏移量)可以被剥离为更简单的i@?@

      签名采用@encode 格式,这并不完美(例如,大多数对象映射到相同的@),但应该让您一些能够区分具有不同签名的块运行时。

      虽然 Apple 链接中没有记录,但我的测试指向 @? 是块类型的代码,这对上面的签名很有意义。我在这个问题上找到了一个clang-developers discussion,这似乎支持了这一点。

      【讨论】:

      【解决方案3】:

      (^BlockA) 中的“BlockA”是变量名(在本例中为 typedef),而不是其类。
      块是对象,但不是NSObject 的常规子类。它们只实现方法的一个子集。 -isKindOfClass: 可能会崩溃。
      块的类型为NSMallocBlockNSConcreteGlobalBlock,...取决于它们的创建位置(堆、堆栈...)。

      【讨论】:

      • 在我的系统上,块确实是NSObject 的实例。
      • 是的,它是一个 typedef,这就是为什么我说代码不会按原样工作。问题是,如何检查块是否“符合”某种类型,如参数列表和/或返回值?
      【解决方案4】:

      似乎块属于__NSGlobalBlock____NSStackBlock____NSMallocBlock__等类,其继承链最终到达NSBlock,然后是NSObject。所以你可以通过[... isKindOfClass:NSClassFromString(@"NSBlock")] 来测试某物是否是一个块。但是,似乎没有任何方法可以在运行时查询块的签名(返回类型和参数类型),因此您无法区分不同签名的块。

      【讨论】:

        【解决方案5】:

        除了 Apple 在这件事上我无话可说之外,用 class_copyMethodListmethod_getName 戳一个块也没有发现明显的暴露方法。所以我要说的是无法检查它们的类型。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2016-12-19
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多