Bartłomiej Semańczyk
Bartłomiej Semańczyk

Reputation: 61834

What is the difference between 'property', '_property', 'self.property' and 'self._property' with read-write and readonly properties?

I would like to understand it very well, but so far I didn't find complex answer considering all possibilities in one place. I know that in modern objective-c we don't create ivars just after @implementation Album between { }.

Just for tests I created Album.h:

@interface Album : NSObject

@property (nonatomic, copy, readonly) NSString *title, *artist;
@property (nonatomic, copy) NSString *title2, *artist2;

- (id)initWithTitle:(NSString*)title;

@end

And Album.m:

@implementation Album

- (id)initWithTitle:(NSString*)title {
    self = [super init];
    if (self) {

        //READ-ONLY
        title = title; //1, still nil after compile
        _title = title; //2,
        self.title = title; //3, "assignment to readonly property"
        self._title = title; //4, "property '_title' not found"

        artist = @"Shakira"; //5, "use of undeclared identifier 'artist'"
        _artist = @"Shakira"; //6
        self.artist = @"Shakira"; //7, "assignment to readonly property"
        self._artist = @"Shakira"; //8, "property '_artist' not found"

        //READ-WRITE
        title2 = title; //9, "use of undeclared identifier 'title2'"
        _title2 = title; //10
        self.title2 = title; //11
        self._title2 = title; //12, "property '_title2' not found"

        artist2 = @"Shakira"; //13, "use of undeclared identifier 'artist2'"
        _artist2 = @"Shakira"; //14
        self.artist2 = @"Shakira"; //15
        self._artist2 = @"Shakira"; //16, "property '_artist2' not found"

    }
    return self;
}

@end

Now I use it:

Album *album = [[Album alloc] initWithTitle:@"Live from Paris"];

The questions are:

  1. Why 4, 8, 12, 16 properties not found while 2, 6, 10, 14 exists?
  2. What is the difference between 10 and 11 or 14 and 15?
  3. What I actually do in 1?
  4. Where did I declare properties 2, 6, 10, 14?
  5. Why I can assign to readonly properties in 2, 6 but not in 3, 7?

Upvotes: 3

Views: 442

Answers (2)

Amin Negm-Awad
Amin Negm-Awad

Reputation: 16660

Some explanations at the beginning

A. @property declares methods with the name -property and, if it is not read only, -setProperty:. Nothing else is done by @property itself!

B. @property does not synthesize an ivar. What? Say it again! @property does not synthesize an ivar. (I can repeat that as many times as you want.)

Ivars are synthesized, when you use an explicit @synthesize or – and that makes it a little bit difficult to understand – when you have auto synthesize. But there is still an (implicit, unshown) synthesize!

To have an implicit synthesize, the following conditions have to be met:

  • You need an @property.
  • You do not implement the accessors manually.

Since you can see the first condition in source code and cannot see the second condition, it is easy to assume that @property synthesized the ivar. But it is not true. Let's try it:

@interface Foo : NSObject
@property NSString *foo;
@end

@implementation Foo
// Both accessors are implemented -> no auto synthesize -> no ivar
- (NSString*)foo
{
  return _foo; // Error: No ivar _foo
}

- (NSString*)setFoo:(NSString*)foo
{
  _foo=foo; // Error: No ivar _foo
}
@end

The reason is: If @property itself would synthesize the ivar, it would be impossible to have a declared property that is not backed with an ivar.

(Additionally no ivar is synthesized, if you already have one that fits. But we do not need that here.)

C. An synthesized ivar has the identifier _property, not property. (In some cases a existing ivar without underscore would be taken as an already existing ivar, but we do need that here, too.)

D. The dot notation does not access an ivar. It sends a message. If the statement is an lvalue, is uses the setter, if it is a rvalue, it uses the getter. You can simply translate that:

self.property = …; // [self setProperty:…];
… = self.property; // … = [self property];

Why 4, 8, 12, 16 properties not found while 2, 6, 10, 14 exists?

At 4, … you use dot notation, send a message with the selector set_Property. There is no such method, because the synthesized method is -setProperty:: Error

At 2, … you access the ivars directly. It's identifier is _property. Everything is fine.

What is the difference between 10 and 11 or 14 and 15?

At 11, 15 you set the value of the property using a setter. At 10, 14 you set the ivar directly. The code of the setter is not executed.

Let's have an example:

@interface Foo : NSObject
@property NSString *foo;
@end

@implementation Foo
// ivar _foo is (auto) synthsized, because there is no getter.
- (void)setFoo:(NSString*)foo
{
  NSLog( @"Setter executed");
  _foo = foo;
}
@end

In line 10, 14 you will see no log, because the setter is not executed. In line 11, 15 you will see a log.

If you have additional things to do in the setter (or getter) accept of only setting the ivar, the result will differ.

What I actually do in 1?

titleis not an ivar (the identifier of the ivar is _title), but a parameter var. (Look at the head of the method definition.) You assign the value of the parameter var to itself, what is a kind of meaningless.

You assign nothing to the ivar, because you do not use the ivar.

Where did I declare properties 2, 6, 10, 14?

See above A. and B.

Why I can assign to readonly properties in 2, 6 but not in 3, 7?

The ivar is never read only. The readonly and readwrite options are for the method declaration. (See above A. - C.)

In 2, 6 you set the ivar directly. It cannot be read only, so everything is pretty fine.

In 3, 7 you send a message to a method that does not exists, because the property is read only.

Upvotes: 4

Jef
Jef

Reputation: 4728

when you type

self.title = @"foo";

it is really shorthand for

[self setTitle:@"foo" ];

You don't implement

-(void)setTitle:(NSString *)title 

the compiler has synthesised it for you. If you could see what it has put there it might look something like this

-(void)setTitle:(NSString *)title{
_title = [title copy];
}

So you see _title is an instance variable. title is not, it is a method which returns an NSString. It has been synthesised for you, but it looks like this

-(NSString *)title{
return _title;
}

They are fundamentally different things. If title is readonly then the setTitle: method is never synthesised, although the _title iVar is still there. So when you type self.title = @"bar" the compiler looks for -(void)setTitle .. which is not there. -> unhappy compiler

The best practice is to avoid using the _underScore variables (known as backing iVars) directly. Always use self.title = @"some Title" instead. If you ever want to use key value observing, which is one of cocoas best features, you'll be glad you did.

Upvotes: 4

Related Questions