Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing and calling dynamic blocks in Objective C

As part of a unit test framework, I'm writing a function genArray that will generate NSArrays populated by a passed in generator block. So [ObjCheck genArray: genInt] would generate an NSArray of random integers, [ObjCheck genArray: genChar] would generate an NSArray of random characters, etc. In particular, I'm getting compiler errors in my implementation of genArray and genString, a wrapper around [ObjCheck genArray: genChar].

I believe Objective C can manipulate blocks this dynamically, but I don't have the syntax right.

ObjCheck.m

+ (id) genArray: (id) gen {
    NSArray* arr = [NSMutableArray array];

    int len = [self genInt] % 100;

    int i;
    for (i = 0; i < len; i++) {
        id value = gen();

        arr = [arr arrayByAddingObject: value];
    }

    return arr;
}

+ (id) genString {
    NSString* s = @"";

    char (^g)() = ^() {
        return [ObjCheck genChar];
    };

    NSArray* arr = [self genArray: g];
    s = [arr componentsJoinedByString: @""];

    return s;
}

When I try to compile, gcc complains that it can't do gen(), because gen is not a function. This makes sense, since gen is indeed not a function but an id which must be cast to a function.

But when I rewrite the signatures to use id^() instead of id, I also get compiler errors. Can Objective C handle arbitrarily typed blocks (genArray needs this), or is that too dynamic?

like image 992
mcandre Avatar asked Oct 13 '11 01:10

mcandre


People also ask

How to Pass by reference in Objective-C?

To pass the value by reference, argument pointers are passed to the functions just like any other value. So accordingly, you need to declare the function parameters as pointer types as in the following function swap(), which exchanges the values of the two integer variables pointed to by its arguments.

How do you declare a block in Objective-C?

Declaring Block VariablesStart with a return type followed by the variable name and a parameter list. You'll need a caret (^) before the variable name and two sets of parentheses. With that in mind, the following example declares a completion block, identical to the one you just saw, but without using custom typedefs.

What is the purpose of block in Objective-C?

Blocks are a language-level feature added to C, Objective-C and C++, which allow you to create distinct segments of code that can be passed around to methods or functions as if they were values. Blocks are Objective-C objects, which means they can be added to collections like NSArray or NSDictionary .

What does __ block do?

The __block Storage Type __block variables live in storage that is shared between the lexical scope of the variable and all blocks and block copies declared or created within the variable's lexical scope.


3 Answers

Given that blocks are objects, you can cast between block types and id whenever you want, though if you cast the block to the wrong block type and call it, you're going to get unexpected results (since there's no way to dynamically check at runtime what the "real" type of the block is*).

BTW, id^() isn't a type. You're thinking of id(^)(). This may be a source of compiler error for you. You should be able to update +genArray: to use

id value = ((id(^)())(gen))();

Naturally, that's pretty ugly.

*There actually is a way, llvm inserts an obj-c type-encoded string representing the type of the block into the block's internal structure, but this is an implementation detail and would rely on you casting the block to its internal implementation structure in order to extract.

like image 98
Lily Ballard Avatar answered Oct 31 '22 13:10

Lily Ballard


Blocks are a C-level feature, not an ObjC one - you work with them analogously to function pointers. There's an article with a very concise overview of the syntax. (And most everything else.)

In your example, I'd make the gen parameter an id (^gen)(). (Or possibly make it return a void*, using id would imply to me that gen generates ObjC objects and not completely arbitrary types.)

like image 38
millimoose Avatar answered Oct 31 '22 12:10

millimoose


No matter how you declare your variables and parameters, your code won't work. There's a problem that runs through all your compiler errors and it would be a problem even if you weren't doing convoluted things with blocks.

You are trying to add chars to an NSArray. You can't do that. You will have to wrap them them as some kind of Objective C object. Since your only requirement for this example to work is that the objects can be inputs to componentsJoinedByString, you can return single-character NSStrings from g. Then some variety of signature like id^() will work for genArray. I'm not sure how you parenthesize it. Something like this:

+ (id) genArray: (id^()) gen;

+ (id) genString {
    ...
    NSString * (^g)() = ^() {
        return [NSString stringWithFormat:@"%c", [ObjCheck genChar]];
    };
    ...
}

NSString * is an id. char is not. You can pass NSString * ^() to id ^(), but you get a compiler error when you try to pass a char ^() to an id ^(). If you gave up some generality of genArray and declared it to accept char ^(), it would compile your call to genArray, but would have an error within genArray when you tried to call arrayByAddingObject and the argument isn't typed as an id.

Somebody who understands the intricacies of block syntax feel free to edit my post if I got some subtle syntax errors.

Btw, use an NSMutableArray as your local variable in genArray. Calling arrayByAddingObject over and over again will have O(n^2) time performance I imagine. You can still declare the return type as NSArray, which is a superclass of NSMutableArray, and the callers of genArray won't know the difference.

like image 1
morningstar Avatar answered Oct 31 '22 13:10

morningstar