【问题标题】:Is it possible to create a category of the "Block" object in Objective-C是否可以在 Objective-C 中创建“块”对象的类别
【发布时间】:2011-01-19 05:07:15
【问题描述】:

我想通过为 Objective-C 块创建一个类别来添加功能。

__block int (^aBlock)(int) = ^int( int n ){
    if( n <= 1 ) return n;
    return aBlock( n - 1 ) + aBlock( n - 2 );
};

而不是只允许正常的[aBlock copy][aBlock retain][aBlock release][aBlock autorelease]。我可以这样做:

[aBlock mapTo:anArray];

可能的类别

@interface UnknownBlockClass (map)

- (NSArray *)mapTo:(NSArray *)array_;

@end

【问题讨论】:

  • 顺便说一句,我已经开始着手开发一个 Objective-C 库,该库将映射和相关函数添加到 NSArrayNSSet: github.com/mdippery/collections
  • @mipadi:你知道现有的地图之类的功能吗? -enumerateObjectsUsingBlock:valurForKey:
  • @JeremyP:是的,但是 (a) 仅在 10.6 上,并且 (b) 我正在实现 Smalltalk 集合协议的其余部分,以及来自 Ruby 的 Enumerable 类的一些功能。跨度>

标签: objective-c cocoa objective-c-blocks


【解决方案1】:

@pwc 是正确的,因为您不能为看不到的类创建类别。

不过……

我要告诉你的内容应该严格地用作学习中的练习,并且永远不要在任何形式的生产环境中使用。

  1. 一些运行时内省揭示了一些有趣的信息。有许多类包含单词“Block”。其中一些看起来很有希望:__NSStackBlock__NSMallocBlock__NSAutoBlockNSBlock
  2. 更多的自省表明有前途的类继承自NSBlock

所以看起来任何块都将成为NSBlock 的某个实例或子类。

您可以在对象上创建方法,如下所示:

@implementation Foo
- (void) doFoo {
  //do something awesome with self, a block
  //however, you can't do "self()".  
  //You'll have to cast it to a block-type variable and use that
}
@end

然后在运行时,您可以将该方法移至NSBlock 类:

Method m = class_getInstanceMethod([Foo class], @selector(doFoo));
IMP doFoo = method_getImplementation(m);
const char *type = method_getTypeEncoding(m);
Class nsblock = NSClassFromString(@"NSBlock");
class_addMethod(nsblock, @selector(doFoo), doFoo, type);

在此之后,块应响应 doFoo 消息。

使用风险自负,仅用于实验。

【讨论】:

  • 哎呀! :) 另一种选择是只在NSObject 上创建一个类别,并承诺只在块上使用它……不管怎样,这有点吓人,我更喜欢明确的[Utility somethingWithBlock:block][arr map:block] 的安全性[block doSomething][block map:arr] 的可怕之处...
  • 也许是外星人,但并不可怕。这在 Smalltalk 中很常见,它启发了 Objective-C。 Smalltalk 中没有控制语法。所以 if 语句看起来像 [^BOOL{ return a > 1 } ifTrue:^{...}] (Smalltalk 中更简洁的除外:[ a > 1 ] ifTrue: [...]。)
  • 但是(如果我错了,请纠正我),实际上并不能保证块对象从NSObject...
  • @Jonathan 我不确定保证,但运行时自省显示私有NSBlock NSObject 的子类.所以它适用于摆弄,但是(正如我在帖子中所说)在任何类型的生产环境中使用它都非常愚蠢。
【解决方案2】:

一个块最终成为__NSGlobalBlock__ 类型的实例,如下面的 sn-p 所示:

无效(^aBlock)(无效)= ^(无效){ NSLog(@"Hello world"); }; // 打印“type = __NSGlobalBlock__” NSLog(@"type = %@", [aBlock 类]);

为了创建一个类的类别,编译器需要能够看到该类的原始@interface 声明。我找不到 __NSGlobalBlock__ 的声明,这可能是有充分理由的。

This articlethis article 包含一些关于块实现的有用信息。

就您的初衷而言,为什么不为您的mapTo 方法创建一个NSArray 类别?对于这种功能来说,这似乎是一个更好的地方。

更新

假设您可以向 Block 对象添加一个类别。您将如何从类别的方法中调用该块?据我所知,调用块的唯一方法是通过() 运算符(例如aBlock())。我认为没有办法从 Block 对象中分辨出参数的数量和类型。那么,您会向块调用传递哪些参数?

我不建议您这样做,但以下工作...

@interface NSObject(块扩展) - (无效)富; @结尾 @implementation NSObject(块扩展) -(无效)富 { // 不确定如何确定 self 是否是块,因为两者都不是 // __NSGlobalBlock__ 也不是它的任何超类(除了 NSObject) // 编译器可以访问 if ([[[self class] description] isEqual:@"__NSGlobalBlock__"]) { NSLog(@"foo"); // 怎么办? // 不能调用 self(),它不会编译 // 我还能怎么调用这个块? } } @结尾 ... 无效(^aBlock)(无效)= ^(无效){ NSLog(@"Hello world"); }; // 打印“foo” [aBlock foo];

【讨论】:

  • 我只是以mapTo 为例。对于这样一个类别,我能想到不少应用:函数组合、柯里化等。
  • 聪明,是的,但很危险。不能保证各种 NSBlock 类将永远来自 NSObject...
【解决方案3】:

Dave DeLong 是对的,您不能在看不到的类上添加类别,但由于块是 NSBlock 的子类,因此添加:

@interface NSBlock : NSObject
@end

现在您可以“看到”NSBlock 并在其上添加一个类别,例如:

@interface NSBlock (map)
- (NSArray *)mapTo:(NSArray *)array;
@end

@implementation NSBlock (map)
- (NSArray *)mapTo:(NSArray *)array
{
    ...
}
@end

在实际用于生产的代码中可能仍然不是最好的做法......

【讨论】:

    【解决方案4】:
    WRONG: A block winds up being an instance of type __NSGlobalBlock__, as seen in the     
    following snippet:
    
    int i = 0;
    id o = [class self];
    
    void (^aBlock)(void) = ^(void) {
    
        [o setValue:0];
    
        NSLog(@"Hello world %d", i);
    };
    
    // prints "type = __NSGlobalBlock__" 
    
    // Now it prints __NSStackBlock__ 
    // and when moved into HEAP prints __NSMallocBlock__
    
    NSLog(@"type = %@", [aBlock class]);
    

    只能说一个块最终成为“NSGlobalBlock”类型的实例,除非范围内没有捕获的变量,否则它将在堆栈中创建,当它被复制,会将块移动到 HEAP 中,并且每个引用都将被保留!

    【讨论】:

      【解决方案5】:

      简单的答案是否定的。 __block 变量是 C 级对象而不是 Objective C 对象。您可以调用 [aBlock copy] 但这会调用 C 函数 block_copy() 而不是 nsobject 复制方法。所以 __block 类型是 C 类型,因此不能添加类别。

      更正:__block 是 C 编译器中的标识符而不是 typedef。

      我不确定这是否会达到你认为的效果,事实上我什至不太确定它会做什么:

      __block int (^aBlock)(int) = ^int( int n ){
      if( n <= 1 ) return n;
      return fib( n - 1 ) + fib( n - 2 );
      };
      

      __block 标识符告诉编译器该变量在引用块中应该是可变的,并且如果任何引用块被复制到堆中,则应该保留该变量。让我对您的代码感到困惑的是 __block 通常用于包装变量,而不是块本身。

      【讨论】:

      • __block 允许我将aBlock 传递到块中。我重命名了块并忘记重命名 fib 引用。
      • 块实际上既是 C 类型又是 Objective-C 类型。您可以使用 objc 运行时 API 将对象与它们关联,您可以将它们放入 ObjC 集合中,等等……但它们在纯 C 程序中仍然有效。这是编译器和运行时人员的一项巧妙的兼容性工作:)
      猜你喜欢
      • 2011-01-15
      • 1970-01-01
      • 2013-02-01
      • 1970-01-01
      • 2018-08-19
      • 2016-03-24
      • 2011-04-09
      • 2021-08-18
      • 1970-01-01
      相关资源
      最近更新 更多