Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 877
iHunter Avatar asked Feb 18 '23 23:02

iHunter


1 Answers

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.

like image 83
Steven Fisher Avatar answered Apr 29 '23 16:04

Steven Fisher