Is it possible to create a category of the "Block" object in Objective-C
Asked Answered
S

5

12

I would like to add functions by creating a category for Objective-C Blocks.

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

Instead of just allowing the normal [aBlock copy], [aBlock retain], [aBlock release], [aBlock autorelease]. I could do thing like:

[aBlock mapTo:anArray];

Possible Category

@interface UnknownBlockClass (map)

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

@end
Spermophyte answered 19/1, 2011 at 5:7 Comment(3)
Incidentally, I've begun work on an Objective-C library that adds map and related functions to NSArray and NSSet: github.com/mdippery/collectionsExpellee
@mipadi: Are you aware of the existing map like functionality e.g. -enumerateObjectsUsingBlock: and valurForKey:Molnar
@JeremyP: Yes, but (a) that's only on 10.6, and (b) I'm implementing the rest of the Smalltalk collections protocol, as well as some functionality from Ruby's Enumerable class.Expellee
W
13

@pwc is correct in that you can't create a category for a class that you can't see.

However...

WHAT I AM ABOUT TO TELL YOU SHOULD BE USED STRICTLY AS AN EXERCISE IN LEARNING, AND NEVER IN ANY SORT OF PRODUCTION SETTING.

  1. Some runtime introspection reveals some interesting information. There are a number of classes that contain the word "Block". Some of them look promising: __NSStackBlock, __NSMallocBlock, __NSAutoBlock, and NSBlock.
  2. Some more introspection shows that the promising classes inherit from NSBlock

So it looks like any block is going to be some instance or subclass of NSBlock.

You can create a method on an object, like so:

@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

Then at runtime, you can move that method to the NSBlock class:

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);

After this, blocks should respond to the doFoo message.

USE AT YOUR OWN RISK, AND ONLY FOR EXPERIMENTING.

Wame answered 19/1, 2011 at 6:40 Comment(4)
Yikes! :) Another option is to just make a category on NSObject, and promise only to use it on blocks… Either way, it's a bit scary, and I'd prefer the safety of the explicit [Utility somethingWithBlock:block] and [arr map:block] over the scariness of [block doSomething] and [block map:arr]...Vise
Alien perhaps, but not scary. This is very common in Smalltalk, which inspired Objective-C. There is no control syntax in Smalltalk. So an if statement would look like [^BOOL{ return a > 1 } ifTrue:^{...}] (except a lot cleaner in Smalltalk: [ a > 1 ] ifTrue: [...].)Ruthy
But (correct me if I'm wrong), block-objects are not actually guaranteed to descend from NSObjectVise
@Jonathan i'm not sure about guaranteed, but runtime introspection shows that the private NSBlock class is a subclass of NSObject. So it works for fiddling, but (as I say in the post) it's beyond stupid to use this in any sort of production setting.Wame
A
6

A block winds up being an instance of type __NSGlobalBlock__, as seen in the following snippet:

    void (^aBlock)(void) = ^(void) {
        NSLog(@"Hello world");
    };

    // prints "type = __NSGlobalBlock__"
    NSLog(@"type = %@", [aBlock class]);

In order to create a category of a class, the compiler needs to be able to see the original @interface declaration of the class. I can't find the declaration for __NSGlobalBlock__ and probably for good reason.

This article and this article contain some useful information about the implementation of blocks.

To your original point, why not just make a category of NSArray for your mapTo method? It seems like a better place for that sort of functionality.

Updated

Let's say you can add a category to the Block object. How would you invoke the block from the category's method? To the best of my understanding, the only way to invoke a block is via the () operator (e.g., aBlock()). I don't think there's a way to tell from the Block object the number and types of parameters. So, what arguments would you pass in to the block invocation?

I'm not recommending you do this, but the following works...

@interface NSObject (BlockExtension)
- (void)foo;
@end

@implementation NSObject (BlockExtension)
- (void)foo
{
    // not sure how else to determine if self is a Block since neither
    // __NSGlobalBlock__ nor any of its superclasses (except NSObject) 
    // are accessible to the compiler
    if ([[[self class] description] isEqual:@"__NSGlobalBlock__"])
    {
        NSLog(@"foo");
        // now what?
        // can't call self(), it doesn't compile
        // how else can I invoke this block?
    }
}
@end

...

void (^aBlock)(void) = ^(void) {
    NSLog(@"Hello world");
};

// prints "foo"
[aBlock foo];
Asperse answered 19/1, 2011 at 5:52 Comment(2)
I was just using mapTo as an example. I can think of quite a few application for such a category: function composition, currying, etc.Spermophyte
Clever, yes, but dangerous. There is no guarantee that the various NSBlock classes will inherent from NSObject forever...Tarkany
G
1

Dave DeLong is right, you cannot add a category on a class that you cannot see, but as blocks are subclasses of NSBlock adding:

@interface NSBlock : NSObject
@end

Now you can 'see' NSBlock and add a category on it, e.g.:

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

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

Still probably not the best thing to do in code that is actually used in production...

Gaddis answered 25/9, 2011 at 1:8 Comment(0)
S
0
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]);

It is only OKAY to say that a block winds up being an instance of type "NSGlobalBlock" unless there are no captured variables in the scope, otherwise it will be created in the STACK and when it is copied that will move the block into HEAP and every reference will be retained!

Stowell answered 15/1, 2013 at 0:29 Comment(0)
W
-2

The simple answer is no. A __block variable is a C level object not an Objective C object. You can call [aBlock copy] but this invokes a C function block_copy() not the nsobject copy method. So the __block type is a C type and therefore you can't add categories.

correction:__block is an identifier in the C compiler not a typedef.

I'm not sure if this will achieve what you think it will, infact I'm not even quite sure what it does:

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

the __block identifier tells the complier that the variable should be mutable in referencing blocks and should be preserved if any referencing block is copied to the heap. what confuses me about your code is that __block is usually used to wrap a variable, not a block itself.

Waterside answered 19/1, 2011 at 5:29 Comment(2)
__block allows me to pass aBlock into the block. I renamed the block and forgot to rename the fib references.Spermophyte
Blocks are actually both C types and Objective-C types. You can associate objects with them using objc runtime APIs, you can put them in ObjC collections, etc... and yet they're still valid in C-only programs. It's a neat bit of tricky compatibility work by the compiler and runtime folks :)Latium

© 2022 - 2024 — McMap. All rights reserved.