wibosco
wibosco

Reputation: 967

Passing a block as a parameter

Is it possible to pass a fully formed block (a block with all its parameters included) into a method to then have that block executed in the method?

At the moment I have this structure repeated in my project:

 if (//block exists)
        {
            if (self.returnOnMainThread)
            {
                dispatch_async(dispatch_get_main_queue(), ^
                               {
                                   //call block here
                               });
            }
            else
            {
                //call block here
            }
        }

However ideally I'd like to abstract the above clunk of code out into a method similar to:

- (void) reportSuccessWithBlock:(GenericBlockType)block{
        if (block)
        {
            if (self.returnOnMainThread)
            {
                dispatch_async(dispatch_get_main_queue(), ^
                               {
                                   block;
                               });
            }
            else
            {
                block;
            }
        }

}

EDIT:

The block's type wouldn't be known in advance.

So the call path could look like this

- (void) someMethod:(void (^)(NSArray *array))success
{
//Some code here setting up the array to be passed back

   [self reportSuccessWithBlock:success(array)];

}

There are a few assumptions in the above code (that I don't know are possible):

  1. Methods can accept generic block types
  2. A block can be passed with all its parameters present but without actually executing

Upvotes: 1

Views: 14042

Answers (2)

trojanfoe
trojanfoe

Reputation: 122391

OK, I think the idea of using NSArray parameters will work, so you can give your blocks the same signature:

typedef void ^(MYBLOCK)(NSArray *args);

And implementing your method as:

- (void)reportSuccessWithBlock:(GenericBlockType)block
                  andArguments:(NSArray *)args
{
    if (block != nil)
    {
       if (self.returnOnMainThread)
       {
            dispatch_async(dispatch_get_main_queue(), ^{
                block(args);
            });
       }
       else
       {
           block(args);
       }
    }
}

And then it's just a case of making sure the right type of argument is given to the block in the right order (sounds trivial, but will cause all sorts of aggro if you get this wrong).

MYBLOCK block1 = ^(NSArray *args) {
    // I accept NSNumber, NSString, NSValue
    NSAssert(args.count == 3, @"Invalid argument count");
    NSNumber *arg1 = args[0];
    NSString *arg2 = args[1];
    NSValue *arg3 = args[2];

    // Do my thing
};

and calling it like:

[someClass reportSuccessWithBlock:block1
                     andArguments:@[ @(1), @"Hello", @(cgpoint) ]];

Upvotes: 1

max_
max_

Reputation: 24481

If you have two different blocks as you said you do in the comments, you can merge the two as follows. However, this is quite hacky, and you will need to check the class of the object when the block is called.

- (void) reportSuccessWithBlock:(void (^) (id object, NSUInteger value)) block {

    if (!block)
        return;

    if (self.returnOnMainThread) {
        dispatch_async(dispatch_get_main_queue(), block((id)someObject, value)); // return 0 instead if there is no value
    }

    else {
        block((id) someObject, value)); // return 0 instead if there is no value
    }
}

Therefore, when the block is called, all you would need to do is check the class and do whatever with the returned objects:

- (void) someMethod {

    [self reportSuccessWithBlock:^(id object, NSUInteger value) {

        if ([object isKindOfClass:[NSArray class]]) {

            // returned an NSArray and `value` is 0 (unset)

        }

        else if ([object isKindOfClass:[NSDictionary class]]) {

            // returned an NSDictionary and the `value` is not 0 (unset)

        }

        else {

            // something has gone wrong somewhere!

        }

    }];

}

Upvotes: 2

Related Questions