2

I have an Objective-C object where I'm passing a Block to it's constructor. It's a special case where I want to fire that Block in a view controller when it loads completely. However, in that Block I also want to reference the object that I'm passing the Block into. Consider this example:

typedef void (^MyBlock)();

//constructor of my object
-(id)initMyObjectWithBlock:(MyBlock)block{
    self = [super init];
    if(self){
        myivar = block; //to be used later
    }
}

//somewhere else in my app
MyObject *obj = [[MyObject alloc] initMyObjectWithBlock:^{
   [obj doSomething];
}];

At that [obj doSomething] line, I am getting "Variable is uninitialized when captured by block" warning, which makes sense. In that Block, I need a reference to the "parent" object (obj in this case). Is there any way to achieve this? I do know workarounds and patterns to my specific problem, but I wanted to know if such a reference is possible.

jscs
  • 63,694
  • 13
  • 151
  • 195
Can Poyrazoğlu
  • 33,241
  • 48
  • 191
  • 389
  • How can be a reference in a closure to an object that does not exist yet? The "work arounds" are no work arounds. – Amin Negm-Awad Mar 08 '14 at 23:51
  • It does not exist at the time it is created, but it will exist when it will be used (as the block will not be fired immediately as I've written). That's why I'm asking the question: is there any way to reference an object in such a way? BTW workarounds are workarounds, as I've said, that solve my specific problem, and they work. – Can Poyrazoğlu Mar 09 '14 at 00:00
  • Or, just pass the owner in to the Block when it's called. – jscs Mar 09 '14 at 00:13
  • Can you let us know how you use this captured object? I don't understand why you would ever need this behavior... – Murillo Mar 09 '14 at 00:22
  • @JoshCaswell yes it is possible, but as I said earlier, I want to know if such mechanism is possible. – Can Poyrazoğlu Mar 09 '14 at 00:59
  • @Murillo it is init method of a view controller, where the block will be fired when view controller loads a certain thing. as I said, there **are** workarounds, but I wanted to know if this is possible to achieve. – Can Poyrazoğlu Mar 09 '14 at 01:00
  • Yes, the question I linked tells you how. – jscs Mar 09 '14 at 01:01
  • @Can Poyrazoğlu The closure is built, when it is created. This is the nature and the purpose of a closure. Therefore the warning is the inner essence of a closure. You can change this behavior with __block. But then it is no "real" closure any more. That is, why I put "work around" in parenthesis. – Amin Negm-Awad Mar 09 '14 at 10:08
  • I see. I was referring to "workaround" as "another way to solve my specific problem, with another approach". Anyway, I'll be examining __block variables more carefully. – Can Poyrazoğlu Mar 09 '14 at 12:52

3 Answers3

3
typedef void (^MyBlock)(MyObject*);

//constructor of my object
-(id)initMyObjectWithBlock:(MyBlock)block{
    self = [super init];
    if(self){
        myivar = block;
    }
}

//somewhere else in app
MyObject *obj = [[MyObject alloc] initMyObjectWithBlock:^(MyObject* myObj){
   [myObj doSomething];
}];

//and somewhere else in app
obj.myivar(obj);
Cy-4AH
  • 4,370
  • 2
  • 15
  • 22
0

If you simply set your object to nil before creating an instance as following, it should take care of your warning.

__block TestClass *obj = nil;
obj = [[TestClass alloc] initMyObjectWithBlock:^{
    [obj doSomething];
}];

Regarding object, it does exit since you are not calling block inside your initMyObjectWithBlock initializer.

Although above code works, but as Josh mentioned, there is a retain cycle. To get around retain cycle, you can do something like:

TestClass *obj = nil;
__block __weak TestClass *objWeak = nil;

obj = [[TestClass alloc] initMyObjectWithBlock:^{
    TestClass *newObj = objWeak;

    [newObj doSomething];
}];

objWeak = obj;

Where you are simply creating a dummy variable to pass in to your block.

Yas Tabasam
  • 10,517
  • 9
  • 48
  • 53
  • @JoshCaswell - Sorry, missed `__block` keyword. This works just fine and with no warning! Now please can you remove your downvote? – Yas Tabasam Mar 09 '14 at 00:21
  • Done, but note that setting to `nil` doesn't change the way this works. Also, see the duplicate I've linked above; there's still a retain loop here. – jscs Mar 09 '14 at 00:37
  • @JoshCaswell - Thanks. Yes, it does have retain loop. Edited my answer. Not saying, its an ideal pattern but edited code should work just fine. – Yas Tabasam Mar 09 '14 at 01:01
  • I think this is the closest we can get to my goal.. – Can Poyrazoğlu Mar 09 '14 at 01:03
  • Very much code to do a simple thing. One might think that the mass of code finds its reason in an anti-conceptionel approach. – Amin Negm-Awad Mar 09 '14 at 10:10
  • 1
    Although original answer would've worked just fine in this particular situation but I edited my answer anyways. As per @Rob's suggestions, removed qualifier from `newObj`. Thanks Rob. – Yas Tabasam Mar 10 '14 at 06:14
0

This is an addition to the answer of Cy-4AH. It was to long to put it into a comment.

First of all: Running into the "work-arounds" is a code smell. There could be two reasons for it:

A. MyObject is a symbiosis of two different task: 1. Fire, when $something happened. 2. Do something with 1) when it fired. Breaking MyObject into MyObject1 and MyObject2 could help. But of course we would need more information.

B. Very often it is a part of the concept that when one object fires, something should happen exactly to this object. (Getting more information, changing a state, …) In this case the pattern is quite easy: Pass self to the block:

From Cy-4AH:

typedef void (^MyBlock)(MyObject*);

//constructor of my object
-(id)initMyObjectWithBlock:(MyBlock)block{
    self = [super init];
    if(self){
        myivar = block;
    }
    return self; // Added this line for obvious reasons
}

//somewhere else in app
MyObject *obj = [[MyObject alloc] initMyObjectWithBlock:^(MyObject* myObj){
   [myObj doSomething];
}];

Now a little change:

Inside MyObject, when it is calling the block

self.myivar( self );

As Cy-4AH said, then you do not need all the __block __unsafe_unretained __weak trash to cover a conceptional problem.

Amin Negm-Awad
  • 16,582
  • 3
  • 35
  • 50