Objective-C self->_ivar access with explicit vs implicit self->
Asked Answered
L

1

24

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.

Laing answered 29/10, 2013 at 15:0 Comment(14)
I'm surprised to learn that self isn't a reserved word o_OPettis
But in every - (instancetype)init method you do if ((self = [super init])) { ... }, no? So you assign to self, therefore it can't be a reserved keyword.Laing
@Joe, I don't think that is the case because if you write self->_block = ^{ NSLog(@"%@", _block); }; (assuming that _block is an ivar of block type), there will be a compiler warning "Capturing 'self' strongly in this block is likely to lead to a retain cycle". So it does retain self and not only the ivar.Laing
I meant reserved as in preventing you from declaring variables with that name inside a class method. Irrelevant to your question really just a commentary.Pettis
@BradAllred, ah, I see you point.Laing
It's interesting that you get a compiler error if you don't redefine self in a new block (i.e. at the top-level of a method). However thinking about it, that's no different from any other variable.Carolecarolee
My conjecture is that _ivar is equivalent to self->_ivar where self is the implicit first argument that each Objective-C method call has, even if there is a local variable of the same name. I don't have an official reference for that (otherwise I would write an answer :-) , but my first attempts to read the generated assembler code confirm this conjecture.Distressed
@MartinR that's exactly what I think. The screenshot I added makes that pretty clear :-)Laing
@JohannesWeiß: On the other hand, self = [super init] also overwrites self, and in that case it is expected that _ivar = ... sets the instance variable of the "new self". So this might be a special issue with blocks.Distressed
There is not other self. Objects are structures. if you write self->_testItVar you access data member of structure self, if you write only _testIVar you access ivar from current structure that is visible. And if you write self->_testIVar, you send message to self to return value of _testIVar. In your example with block, that block is mean as function and _testIVar without self is visible in function. Sorry for bad English.Stalinsk
@Stalinsk So, the "structure that is visible" is a reference to the original (non-overridden) self, isn't it? If yes, that's basically a non-accessible separate self variable. But where's that defined?Laing
the structure that is visible is structure in stack :) dont forget a stack.Stalinsk
@MartinR: "So this might be a special issue with blocks." I don't see anything different. It seems consistent to me. Direct instance variable access never uses a self variable that you define yourself. It always uses the implicit self variable that the compiler defines for the method.Chlorous
@newacct: Yes, that comment was written before I investigated the problem further and wrote the answer.Distressed
D
21

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
Distressed answered 29/10, 2013 at 18:6 Comment(5)
"So _ivar (in an instance method) refers to the instance variable of the receiver of the method." Not necessarily true. If you assign to self (not create a new variable in an inner scope; but assign to the self variable that already exists), then access instance variables, they will access instance variables on the object that self now points to, which is not necessarily the same as the receiver of the method.Chlorous
@newacct: Yes, you are right. I will try to improve the answer later. Thank you for the correction!Distressed
@newacct: Actually the statement "A method refers to the receiving object as self" from the Apple documentation would then also be wrong if you assign a new value e.g. with self = [super init].Distressed
@newacct: I have rewritten the answer, your feedback is welcome.Distressed
Good to know. I always use explicit self-> access and this gives me a reason. Thanks :)Badman

© 2022 - 2024 — McMap. All rights reserved.