2

I've recently realized that it is not proper form to access the properties of a class from inside the init using self.whatever = thing, and that you should instead access the properties directly using _whatever = thing. This works fine when I'm setting values for a base class. However, when I try to set values inside the init of a subclass, I get the error "Use of undeclared identifier _whatever". Using self.whatever from inside the init of the subclass works fine.

Just for clarity, for @property int whatever declared in the interface of a base object, this compiles:

-(id) init {
    self = [super init];
    if (self) {
        [self createName];

        self.whatever = 100;
    }
    return self;
}

And this doesn't:

-(id) init {
    self = [super init];
    if (self) {
        [self createName];

        _whatever = 100;
    }
    return self;
}

I think I am misunderstanding something here. I tried searching for what I'm doing wrong, but I don't know the right words to search for.

As requested, the header for the base class:

#import <Foundation/Foundation.h>

@interface DTCharacter : NSObject

@property BOOL isIdle;

@end

I'm using auto synthesize so there is nothing about this variable in the implementation of the base class.

Also the header file of the subclass does not mention or reference the variable.

I'm trying to assign it here in the implementation of the subclass:

-(id) init {
    self = [super init];

    if (self) {
        [self createName];

        _isIdle = YES;  //says use of undeclared identifier
        //this would work: self.isIdle = YES;
    }
return self;
}
bazola
  • 270
  • 5
  • 16
  • Instance variables, by default, are private to the class. To make them visible to children, you have to declare them in your class `@interface`. – Crowman May 02 '14 at 22:30
  • I am declaring them in the @interface of the base class, not in the implementation. – bazola May 02 '14 at 22:35
  • In your header file, or in an `@interface` section of your `*.m` file? You need to do the former. If you are doing this, post a brief but complete header and implementation file for the two classes that shows the error. – Crowman May 02 '14 at 22:37
  • In my header. I will post those in a moment. – bazola May 02 '14 at 22:39

3 Answers3

5

Accessing properties in init or dealloc can be problematic if the property accessor methods make assumptions about the state of the object, which might not be fulfilled in a partially constructed object.

But this applies only to the properties of the class itself, not to the properties of the superclass. After

self = [super init];

the "super-part" of self is fully constructed, therefore it is safe to call

self.whatever = 100;

in the subclass init method, to set a property declared in the superclass.

_whatever = 100;

does not work in the subclass because the automatically synthesized instance variable is visible only in the superclass itself. You could declare it explicitly to make it visible to the subclass, but there is no need to do so.

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • 2
    There is Apples recomendation: [Don’t Use Accessor Methods in Initializer Methods and dealloc](https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmPractical.html#//apple_ref/doc/uid/TP40004447-SW6) – Cy-4AH May 02 '14 at 22:47
  • Thanks for this, clarifies it completely for me. One thing, is it then considered good form (or acceptable form) to use self.whatever inside the init of subclasses? Or should it still be avoided since it appears on the surface to be against Objective-C best practices? – bazola May 02 '14 at 22:49
  • 1
    @bazola: As I understand it, it is perfectly acceptable and preferable to use `self.whatever` if "whatever" is a property of the *superclass*, because the superclass init method has *completed*. It does not matter that the call is from the init method of a subclass. – Martin R May 02 '14 at 22:52
  • Yes, I have tried say the same in my first comment in my answer. – Cy-4AH May 02 '14 at 22:56
  • 2
    Ideally it'd be `self = [super initWithWhatever:100];` with nothing more needed. – stevesliva May 03 '14 at 02:44
2

Martin's answer is the best, in that you really don't need to do this for superclass properties (and arguably should not, on the theory that class implementations should be private, even from subclasses, to the extent possible). However, if you do want to do it, here's an example to show how:

main.m:

#import <Foundation/Foundation.h>
#import "PGRBase.h"

int main(int argc, const char * argv[])
{

    @autoreleasepool {
        PGRChild * theObject = [PGRChild new];
        NSLog(@"Property is %d\n", theObject.prop);

    }
    return 0;
}

PGRBase.h:

#import <Foundation/Foundation.h>

@interface PGRBase : NSObject {
    int _prop;
}

@property (readwrite, nonatomic, assign) int prop;

@end

@interface PGRChild : PGRBase

@end

PGRBase.m:

#import "PGRBase.h"

@implementation PGRBase

- (instancetype)init
{
    self = [super init];
    if ( self ) {
        _prop = 1;
    }
    return self;
}

@end

@implementation PGRChild

- (instancetype)init
{
    self = [super init];
    if ( self ) {
        _prop = 2;
    }
    return self;
}

@end

The NSLog() call logs Property is 2.

Crowman
  • 25,242
  • 5
  • 48
  • 56
0

There is code snippet how make ivars accessible for child classes:

@interface SomeBase : NSObject
{
@protected
    WhateverType* _whatever;
}
@end

so _whatever will be visible for child classes of SomeBase.

Cy-4AH
  • 4,370
  • 2
  • 15
  • 22
  • Makes sense, I've just been letting it auto synthesize my properties up to this point. Thanks! – bazola May 02 '14 at 22:23
  • Actually this is not working.. It will let me do what you are describing in the implementation of the base class, but it will not let me do it inside the subclass. And if I try to just do whatever = 100 instead of self.whatever, it still doesn't recognize the variable. – bazola May 02 '14 at 22:28
  • @bazola So if `whatever` is ivar of base class, then you better use property to access it even in `init` of child class. – Cy-4AH May 02 '14 at 22:34
  • 1
    @Cy-4AH: Your statement is not correct. Instance variable are by default synthesized with an underscore prefix. – Martin R May 02 '14 at 22:39
  • @MartinR, I am not sure from which version of Xcode ivar by default become underscore prefix. `@synthesize whatever = _whatever;` works on all versions. – Cy-4AH May 02 '14 at 22:42
  • @Cy-4AH: `@synthesize whatever = _whatever;` is the *default* since Xcode 4.4 which introduced automatic property synthesis (https://developer.apple.com/library/iOs/releasenotes/ObjectiveC/ObjCAvailabilityIndex/index.html) – Martin R May 02 '14 at 22:44
  • @MartinR, Thanks. I just get used for old fashion `@synthesize`. Now I know that for making `@synthesize whatever = _whatever;` you don't need even write `@synthesize` – Cy-4AH May 02 '14 at 22:58