Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Objective-C self->_ivar access with explicit vs implicit self->

Tags:

General Problem

Until now, I always thought self->_ivar is equivalent to _ivar. Today I found out that this is not entirely true.

See, for example the following code snippet:

@interface TestClass : NSObject {     NSString *_testIVar; } @end  @implementation TestClass  - (instancetype)init {     if ((self = [super init])) {         _testIVar = @"Testing Only";     }     return self; }  - (void)test {     {         NSInteger self = 42;         NSLog(@"without arrow: %@", _testIVar);        /* OK              */         NSLog(@"with    arrow: %@", self->_testIVar);  /* COMPILER ERROR! */     } }  @end 

Even though I hid the original self with some NSInteger also named self, the implicit ivar syntax _testIVar still finds the "original" self whereas self->_testIVar obviously does not. In the latter case the compiler correctly complains with

Member reference type 'NSInteger' (aka 'long') is not a pointer

In the first case however, it just works.

The Real-world Problem

This example might seem rather artificial but it's not at all. For example the ExtObjC project (used by ReactiveCocoa ) defines the very handy @weakify(var) and @strongify(var) which help against strongly capturing self (and other objects) in blocks by defining a really handy syntax (no need to write the odd and cumbersome to write __weak typeof(self) weakSelf = self; [...] ^{ __strong typeof(self) strongSelf = weakSelf; [...] } anymore). For example:

- (void)someMethod {     @weakify(self);     dispatch_async(self.someQueue, ^{         @strongify(self);         NSLog(@"self @ %p", self);     } } 

Without @weakify and @strongify, the block would capture a strong reference to self. With the @weakify and @strongify it doesn't. So the deallocation of self would not be postponed until the block has been run. The main advantage though is that you don't need to remember to use weakSelf or strongSelf instead of self because the "original" self is hidden.

That's very handy, the ExtObjC implements @weakify / @strongify by generating something similar like the following with macros:

- (void)someMethod {     __weak typeof(self) _weakSelf = self;     dispatch_async(self.someQueue, ^{         __strong typeof(self) self = _weakSelf;         NSLog(@"self @ %p", self);     } } 

Fair enough, that's even better because we can just continue to use self without actually capturing a strong reference to self. However, as soon as we use the implicit-ivars-of-self-syntax, a strong reference to the "original" self will still be captured!

- (void)someMethod {     @weakify(self);     dispatch_async(self.someQueue, ^{         @strongify(self);  /* compiler warning: Unused variable self here!!! */         NSLog(@"self->_testIVar: %@", _testIVar);     } } 

Misc

When using ivars in blocks, we're definitely capturing self. See for example this screenshot: Unused and captured self.

Another fun thing about the screenshot is that the warning messages are

Unused variable 'self'

and in the line below

Capturing 'self' strongly in this block is likely to lead to a retain cycle

That's why I think there are two versions of self :-)

Question

The actual question here is: What exactly does _testIVar mean? How does it find the "original" self pointer?

To clarify (also see my screenshot): As @MartinR pointed out (which is what I think as well), there is some special version of self which cannot be changed and is only used for implicit-self-ivar-access. Is that documented somewhere? Basically where is defined what the implicit self refers to? It seems to behave the same way that for example Java does (with this) but with the difference that this is a reserved keyword that you cannot override.

The question is also not how to "fix" it, just writing self->_testIVar will be what I want in the @weakify/@strongify example. It's more that I thought by using @weakify/@strongify you cannot make the mistake of implicitly strongly capturing self anymore but that simply does not seem to be the case.

like image 424
Johannes Weiss Avatar asked Oct 29 '13 15:10

Johannes Weiss


People also ask

What are implicit and explicit type conversions in C language?

What are implicit and explicit type conversions in C language? Converting one data type into another data type is called type conversions. The compiler provides implicit type conversions when operands are of different data types.

Can I use setters instead of Ivars when initializing an object?

In the course of initializing an object, it calls setters instead of directly manipulating ivars. As long as those setters are purely synthesized, there is no problem.

How to access instance variables directly in Objective-C?

The solution is simple: in Objective-C init and dealloc methods, access instance variables directly instead of going through properties. In non-ARC code, check your property attributes for “retain” or “assign”. Then code your direct access to match.


1 Answers

All Objective-C methods are called with two hidden arguments (from the "Objective-C Runtime Programming Guide"):

  • The receiving object
  • The selector for the method

and a method can refer to the receiving object as self (and to its own selector as _cmd).

Now _ivar is equivalent to self->_ivar where self is this implicit first function parameter. As long as you don't define a new variable self in an inner scope, _ivar == self->_ivar holds true.

If you define a new variable self in an inner scope then you have

  • The locally defined self,
  • the "implicit self" which is the first function parameter,

and _ivar still refers to the "implicit self"! This explains the compiler warnings in your block, which seem to contradict each other:

  • "Unused variable 'self'" refers to the locally defined self,
  • "Capturing 'self' strong in this block ..." refers to the "implicit self" of the function.

The following code demonstrates also this:

@interface MyClass : NSObject {     NSString *_ivar; } @end  @implementation MyClass  - (void)test {     _ivar = @"foo"; // Set instance variable of receiver     {         MyClass *self = [MyClass new]; // Redefine self in inner scope         self->_ivar = @"bar"; // Set instance variable of redefined self         NSLog(@"%@ - %@", self->_ivar, _ivar);         // Output: bar - foo     } }  @end 
like image 94
Martin R Avatar answered Sep 21 '22 21:09

Martin R