简体   繁体   中英

Block in block, with __weak self

I'm trying to figure out if I do this right:

If I have one block, I'll do this:

__weak MyClass *weakSelf = self;  
[self performBlock:^{                 //<< Should I use self, or weakSelf here?

    [weakSelf doSomething];

} afterDelay:delay];

But what happens if there's a block in a block? Would this be correct?

__weak MyClass *weakSelf = self;
[self performBlock:^{

    [weakSelf doSomething];

    [self performBlock:^{

        [weakSelf doSomething]; 
    } afterDelay:1.0f];

} afterDelay:delay];

Also, in the function below, do I need to use [block copy]?

- (void)performBlock:(void (^)(void))block afterDelay:(float)delay
{
    if (block)
    {
        if (delay > 0)
        {
            [self performSelector:@selector(executeBlockAfterDelay:) withObject:[block copy] afterDelay:delay];
        }
        else
        {
            [self executeBlockAfterDelay:[block copy]];
        }
    }
}

- (void)executeBlockAfterDelay:(void(^)(void))block
{
    if (block)
        block();
}

Rather than implementing -performBlock:afterDelay: , just use dipatch_after() . Among other things, that's not a message delivered to an object, so there's no question of what receiver to target it at.

Actually, there's no memory management issue here at all. One typically only needs to do a "weak self" approach when an object retains a block and the block (perhaps implicitly) retains that same object. However, the object is not retaining the block. It is being retained by the framework until the -performSelector:withObject:afterDelay: fires, but that's not a retain cycle.

If there were a retain cycle, then you should not reference self in the blocks. So, your nested case is wrong in invoking a message on self rather than weakSelf .

Finally, yes, you do need [block copy] whenever you are keeping a block after execution leaves the scope of its declaration or if you pass it to non-block-specific API that does. That is, you don't need to copy a block when you pass it to, say, dispatch_async() because that's a block-aware API that knows to make its own copy as necessary. But -performSelector:withObject:afterDelay: is not block-aware. It just treats its argument as a generic object and retains it. So, you do have to copy the block when passing it to that.

In this case (below) use just strong self , because the block is copied just for those few seconds. And usually if you want the self to perform block, you want to it to stay alive until that time, so strong reference is perfectly okay.

[self performBlock:^{
    [self doSomething]; // strong is OK
} afterDelay:delay];

Block inside a block? In your case those two block are just delayed one-shot blocks, so the same as above, use strong. But there are differences between blocks. If you store the block for longer time , maybe for multiple invocations you should avoid retain-cycles.

Example:

self.callback = ^{
    [self doSomething]; // should use weakSelf
};

This may cause retain-cycle. In fact it depends on how the block is used. We see that the block is stored (copied) in property for later use. However, you can prevent the retain-cycles by nullifying block that will not be used any more. In this case:

self.callback(); //invoke
self.callback = nil; //release

When using ARC, you don't have to copy blocks yourself. There were bugs in early versions after blocks were added, but now the compiler under ARC knows when to copy blocks. It is clever enough to copy it in this case:

[self performSelector:@selector(executeBlockAfterDelay:) withObject:block afterDelay:delay];

One the most important thing to understand about blocks is that they capture a piece of code (including values) in an abstract entity that can be manipulated as an atomic object (keep it somewhere, pass it, copy, etc...). Actually it is implemented in a way that guarantee that by default your block will remain valid and executable safely later.

Then capturing and retaining the required dependencies inside the block is necessary.

Unfortunately, in some cases (quite often actually) the block is retained by the instance that creates it and it retains itself that instance. This is called a retain loop and makes your object and your block impossible to dealloc unless you break one of the retaining relation yourself. This can happen if you reference your block with an instance variable for example and you don't nillify it manually.

This is probably the main issue with blocks especially because sometime, you don't know that your block retains your self instance (NSAssert within your block for example). Then:

  • If you execute your block immediately and release it (use your block with dispatch release it after execution) there is no risk since you are sure your object referenced by self still exist.

  • But if the execution is delayed it is important to retain your object within your block. But in that case your object should not retain your block to avoid a retain loop (A retains B and B retains A). If you define and optionally reference your block in the private scope of method it is perfect.

  • About copy. Yes it can be safer to use copy if your block in passed as a method argument to be sure you have a clean exclusive block in this scope with a +1 retainCount. But maybe ARC already do it for you. Not sure about that. For example it performWithSelector seems to do it for free, then copy is not dangerous. Just a useless. Sometime the compiler can optimise that by removing it but it has to be checked.

I usually do this:

__unsafe_unretained __block id blockSelf = self;

and then use it in my blocks no issues.

So in your case:

__unsafe_unretained __block MyClass *blockSelf = self;
[self performBlock:^{
    [weakSelf doSomething];
    [self performBlock:^{
        [weakSelf doSomething]; 
    } afterDelay:1.0f];
} afterDelay:delay];

Also to make your life a tad easier - make a utilities class and put this in the header

void RunAfterDelay(NSTimeInterval delayInSeconds, dispatch_block_t block);

and then this in the .m file

void RunAfterDelay(NSTimeInterval delayInSeconds, dispatch_block_t block)
{
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC), dispatch_get_main_queue(), block);
}

Import the utilities into your prefix and you can go:

__unsafe_unretained __block MyClass *blockSelf = self;
RunAfterDelay(1.0f,^{
    [blockSelf doSomething];
    RunAfterDelay(delay,^{
        [blockSelf doSomething];
    })
});

I find it a bit nicer to read than the verbose default ones.

Hope this helps :)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM