1

I've asked a similar question before, but this time the situation is a bit different.

For one, this time around I'm using ARC (inviting a whole new world of problems). Second: The code I have basically works, but I'm wondering if I could do with less code.

Right now, my code looks like this:

__weak CustomType *Object;
void (^doAverage)(CustomType *, int, int) = ^(CustomType *Trigger, int Val1, int Val2) {
    //Calculations with Trigger
}

CustomType *(^Add)(int, CustomType *) = ^(int Val1, CustomType *NewObject) {
    //Checks prerequisites, and returns either the passed object after adding it to 
    //an array or nil, if the prerequisites haven't been met.
}

Now, further down the line, I'm doing this:

Object = Add(-1, [CustomType makeObjectWithParameters]);
[Object addCallback:^{ doAverage(Object, 56, 57); }];

Inside addCallback, the passed block with the call to doAverage is sent a copy message and saved for later execution.

This works, no retain cycles, the callbacks fold if the object is deallocated.

But it's not 'as elegant' as I'd like.

What I'd like would be something along these lines:

__block __weak CustomType *Object;

CustomType *(^Add)(int, CustomType *) = ^(int Val1, CustomType *NewObject) {
    //Checks prerequisites, and returns either the passed object after adding it to 
    //an array or nil, if the prerequisites haven't been met.
    Object = NewObject;
}

And further down the line:

[Add(-1, [CustomType makeObjectWithParameters]) addCallback:^{ doAverage(Object, 56, 57); }];

However if I do it like this, it'll change the Object passed to the callback to whatever Add I called last, which isn't what I want.

Basically I'd need to set the value of Object in a block and then copy that reference as a parameter, instead of passing the reference to the variable itself.

Furthermore, as a second step, it'd be about perfect, if I didn't have to pass the parameter Object in the first place, but could use it in the block. But if I do that, the variable in the block will default to nil when the block is finally called (well past the expiration of the scope it was defined in).

So instead of:

void (^doAverage)(CustomType *, int, int) = ^(CustomType *Trigger, int Val1, int Val2) {
    //Calculations with Trigger
}

I'd like to do use:

void (^doAverage)(int, int) = ^(int Val1, int Val2) {
    //Calculations with Object
}

With whatever value Object (weak reference) had at that moment in time.

Is either of the two somehow possible?

Thank you very much.

Edit:

Following the below suggestion, I refactored my code like this:

typedef BOOL (^stackBlock_t)();
typedef BOOL (^CallBlock_t)(__weak id Object);

-(void) addCallback(CallBlock_t)B {
    stackBlock_t stackBlock = ^{
        __weak id Object = self;
        return B(Object);
    };

    Callback = [stackBlock copy];
}

I'm trying to call this construct with:

[Add(-1, CustomType makeObjectWithParameters]) addCallBack:^{ doAverage(Object, 56, 57); }];

However Object isn't defined for the doAverage block for some reason, and I don't quite know why. After all, it's a block of type CallBlock_t, which takes a parameter of type id and the name Object. Shouldn't it 'know' about the variable Object then?

Edit 2: After a bit of tinkering around (namely using the actual type and stuff), it now gives me a different error message: Incompatible block pointer types sending 'void (^)(void)' to parameter of type 'CallBlock_t', though I'm not. I want that block to return a BOOL and take the argument Object...what am I doing wrong?

Edit 3: I may have found it. I was missing something in my addCallback call. I have yet to test it, but it would seem, that I'm required to call it like this:

[Add(-1, CustomType makeObjectWithParameters]) addCallBack:^(CustomObject *Object){ doAverage(Object, 56, 57); }];

Thinking about it, it makes sense...it's horrible on the eyes, but it makes sense. And it works like a charm. I don't even need excess variables of type __block any more.

ATaylor
  • 2,598
  • 2
  • 17
  • 25
  • so what have you tried? does it not work? – newacct Jun 20 '13 at 21:26
  • @newacct I've already posted what I tried. I tried it with __weak __block, which lead to every instance of Add using the very same variable instead of a copy of the current state. For clarification purposes, I'm using a block to invoke a block with specific parameters. However one of these parameters...namely `Object`, does not behave like I'd like it to. – ATaylor Jun 21 '13 at 05:23

2 Answers2

1

The block is just capturing a reference (strong or weak) to the storage location so its instance count and lifetime is tied to that storage location (except the magic sauce where the stack pointer gets heap-allocated when the block is copied the first time and the references updated).

So knowing that, it should be fairly obvious why Object is always the same value; all the copied blocks point to the same captured location, just as if you had done all this inside a single function using stack-allocated values.

In addCallback, you can do this:

- (void)addCallback:(void^(CustomType*))action {
   void(^stackBlock)(void) = ^{
      __weak CustomType *localObj = Object;
      action(localObj);
   };
   self.callback = [stackBlock copy];
}

This will cause it to capture whatever the current value was at the time addCallback was invoked, which should match the first part you asked for.

The second part I don't think is possible given the way everything is setup. Only addCallback captures the point-in-time value of Object and without using a parameter it has no way to pass that to doAverage because it doesn't even know what function it is calling. Unfortunately there isn't a clean, easy way to do dynamic block dispatch, otherwise you could have addCallback take a block and a varargs parameter list, then invoke the block with the object and the supplied parameters.

russbishop
  • 16,587
  • 7
  • 61
  • 74
  • I see. I'll definitely try the 'Block calling the Block calling the block with parameters' option. As useful as Blocks are, they can be really difficult to comprehend, wouldn't you agree? – ATaylor Jun 24 '13 at 05:24
  • It seems I got it to work, thanks to your prod in the right direction. Thanks a lot :) – ATaylor Jun 24 '13 at 06:59
  • Umm... the `__weak` is completely pointless. Your code is exactly identical to `void (^stackBlock)() = ^{ action(Object); };` – newacct Jun 27 '13 at 21:11
  • oops, you are quite correct... the new block actually captures a strong reference to Object every time. – russbishop Jun 28 '13 at 01:58
0

"Something that can hold code that acts on separate local variables" looks like an instance of a class.

ilya n.
  • 18,398
  • 15
  • 71
  • 89
  • Sort of, yes, but I wasn't exactly willing to create an entire class for an arbitrary piece of code. Also, I asked this question about half a year ago and it's been marked as 'answered'. So thank you for your input, but it's not helping me any. – ATaylor Dec 02 '13 at 14:44