iHunter
iHunter

Reputation: 6205

Is `typeof(self) self = weakSelf` construction legitimate inside block?

I'm wondering is the variable declaration from the question topic is legitimate. Imagine the following code:

__weak typeof(self) weakSelf = self;
[self doSomethingThatMayCauseRetainCycleWithBlock:^{
    typeof(self) self = weakSelf; // <---- !!!!
    if (self == nil) return;

    NSAssert(self.someProperty != nil, @"This doesn't lead to retain cycle!");

    [self doSomething];
    self.someProperty = someValue;

    // even
    self->someIvar = anotherValue;
}

This code works perfectly in Xcode 4.5.2, only giving a warning that Declaration shadows a local variable.

What's the point of this quirk:

  1. Having redeclared self as a strong reference to a weak variable, you can safely copy/move code inside/outside the block without a risk to occasionally create a retain cycle (except for ivars, but they are evil).
  2. NSAssert in a block doesn't cause retain cycle anymore.

Update I discovered that this technique is used in libextobjc for @weakify/@strongify macros.

Upvotes: 4

Views: 2993

Answers (2)

Steven Fisher
Steven Fisher

Reputation: 44876

There's two parts to this answer:

  1. The underlying cause of your Declaration shadows a local variable warning, GCC_WARN_SHADOW, and why it's probably a bad warning to have turned on.
  2. Building a replacement for NSAssert that doesn't need this trickery

GCC_WARN_SHADOW

The warning you're getting is from GCC_WARN_SHADOW, one of the more useless warnings (in my opinion, anyway). The compiler is doing the right thing here, but thanks to GCC_WARN_SHADOW it's drawing your attention to you possibly doing something other than you intended.

This is the sort of things the more paranoid compiler warnings are good at. The downside is that it's difficult (not impossible) to drop a particular warning to indicate that you know what you're doing.

With GCC_WARN_SHADOW on, this code will generate a warning:

int value = MAX(1,MAX(2,3));

Despite that, it will function perfectly.

The reason for this is that it compiles down to this, which is ugly but (to the compiler) perfectly clear:

({
   int __a = (1);
   int __b = (({
       int __a = (2);
       int __b = (3);
       __a < __b ? __b : __a;
   }));
   __a < __b ? __b : __a;
})

So what you're proposing will work well. NSAssert is implemented using a macro which uses the self variable. If you define self in your scope, it'll pick up that self instead.

Replacing NSAssert

But that said, if you think GCC_WARN_SHADOW has a use, there's another solution. It might be better anyway: Provide your own macro.

#define BlockAssert(condition, desc, ...) \
    do { \
        __PRAGMA_PUSH_NO_EXTRA_ARG_WARNINGS \
        if (!(condition)) { \
            [[NSAssertionHandler currentHandler] handleFailureInMethod:_cmd \
            object:strongSelf file:[NSString stringWithUTF8String:__FILE__] \
                lineNumber:__LINE__ description:(desc), ##__VA_ARGS__]; \
        } \
        __PRAGMA_POP_NO_EXTRA_ARG_WARNINGS \
    } while(0)

This is essentially a copy-paste of NSAssert, but it uses strongSelf instead of self. In places you use the strongSelf pattern, it'll work; in places where strongSelf is not defined, you'll get a compiler error: Use of undeclared identifier: 'strongSelf'. I think that's a pretty good hint.

Upvotes: 5

Ash Furrow
Ash Furrow

Reputation: 12421

Strictly speaking, there is nothing wrong with your code — the variable declaration is legitimate. However, you will probably get a compiler warning about the local variable shadowing the instance one.

GCD blocks are actually C functions, not Objective-C methods. When compiled, every Objective-C instance method has an extra parameter added to it, which is the self pointer. self isn't stored in the object struct like other variables.

For this reason, I would hesitate to use this code in a library I was going to share. The code may break with newer versions of then compiler because you're actually hacking the runtime a little more than is immediately apparent. Additionally, it's quirky code, as you point out :) I'm not sure that anyone else reading it would immediately understand what's going on.

Upvotes: 3

Related Questions