2

I tend to regularly use self.property in my Objective C code when accessing variables as I understand that this makes use of the getter/setter methods (either explicitly coded or automatically generated).

Another coder told me recently that it's best to use _property instead, throughout the code. But my understanding is that _property is really only for use in init and dealloc methods.

Today I found a bug in some code that uses initWithCoder. I was using _property in the initWithCoder method and had to change it to self.property. This was when setting an NSData object. Here is the original code:

@property (nonatomic, strong) NSData *data;

- (id)initWithCoder:(NSCoder *)decoder {
    self = [super init];
    if (!self) {
        return nil;
    }
    _data = [decoder decodeObjectForKey:@"data"];
    return self;
}

And I had to change it to this, to get it to work:

@property (nonatomic, strong) NSData *data;

- (id)initWithCoder:(NSCoder *)decoder {
    self = [super init];
    if (!self) {
        return nil;
    }
    self.data = [decoder decodeObjectForKey:@"data"];
    return self;
}

My question is - why is initWithCoder the exception here? If it's generally accepted that it's best to use _property in an init method, then why is initWithCoder different?

It seems now like the general rule is:

Use self.property except in init/dealloc methods, where _property should be used (except in initWithCoder, where self.property should be used).

Is this correct?

JasonMArcher
  • 14,195
  • 22
  • 56
  • 52
Darren
  • 1,417
  • 13
  • 23
  • 1
    I cant see a reason why in `initWithCoder:` your code `self.property` would work when `_property` wouldn't. You got it correct that `self.property` uses the _setter_ of this property to initialize it, whereas `_property` doesn't take the detour through the setter but rather initializes the _instance variable_ that belongs to your property directly... This seems strange :/ – nburk Jun 18 '14 at 13:02
  • When you say "to get it to work," what bug did you encounter. I don't see a problem with the original ivar-based init. The coder who told you to use ivars exclusively is incorrect; that is not best practice. There was a time when there was more disagreement within the ObjC community, but Apple has finally documented that you should use the accessors except in init and dealloc. – Rob Napier Jun 18 '14 at 13:07
  • I just made a test, it works with both `_data` and `self.data`. What version of iOS are you targeting? much earlier versions would not auto-generate the `_data` variable for the property `data`. – Daniel Jun 18 '14 at 13:08
  • The error occurs when returning from initWithCoder, not while in initWithCoder. I put a breakpoint in initWithCoder and the assignment to _data was okay. The problem occurs just after the [NSKeyedUnarchiver unarchiveObjectWithFile:cachePathForKey]; statement. When it returns from this, the object contains garbage, unless I use self.data instead of _data – Darren Jun 18 '14 at 13:09
  • The deployment target for the app is 7.0 – Darren Jun 18 '14 at 13:10
  • simpleBob I suspect you're onto something. It seems like auto-generation is not happening for some reason, but since the target is 7.0 this confuses me. – Darren Jun 18 '14 at 13:13
  • @simpleBob: before property accessor auto-generation, you had to use `synthesize` to get your accessor generated (or implement them manually). Otherwise you would get an error when using either of them. This seems not to be the case, since the accessor is actually working. Similarly, the `_data` ivar is also there, otherwise the compiler would have complained. – sergio Jun 18 '14 at 13:18
  • @simpleBob: btw, autogeneration depends on Xcode version; from 4.4 on, synthesize is not required anymore. – sergio Jun 18 '14 at 13:20
  • which object was garbage? just the one pointed to by `_data` or the object returned by `unarchiveObjectWithFile` (`self`, so to say)? – sergio Jun 18 '14 at 13:23
  • @sergio all objects returned were garbage. – Darren Jun 18 '14 at 13:23
  • The only thing I see that could possibly be an issue is that `_data = ...` will not copy the returned object, while `self.data = ...` will do so (if the property is declared as copy). So you could try if manually copying the returned value fixes the problem. Event then it would still be weird though. – Alfonso Jun 18 '14 at 13:33
  • @sergio auto generation of properties does NOT depend on the version of an IDE, such as Xcode. It depends on the compiler. For example with clang you can check with `__has_feature(objc_default_synthesize_properties)`to see if auto generation is available (which may not be the case depending on the compiler options). – Daniel Jun 18 '14 at 13:50
  • @sergio also, it would be possible that he is using `@synthesize` or `@dynamic`, since he is not showing his whole code. I mean, `@property` should be enclosed by `@interface` and `initWitchCoder` should be in `@implementation`. – Daniel Jun 18 '14 at 13:56
  • @Darren just for the fun of it, try to change the name of the property. Maybe, somewhere, the name of the corresponding variable is being changed, or a getter/setter exists. – Daniel Jun 18 '14 at 14:00
  • @simpleBob: that was exactly what I meant, but I was referring to Xcode version because usually you don't customise Xcode by running an older or newer version of the compiler that it ships with… :-) If you have 5.0, you do not need to check which compiler version you have… – sergio Jun 18 '14 at 14:29
  • @simpleBob: and again, auto generation is not the issue here, since accessors are generated (either "auto", or through synthesize, that does not change things) and do work; the only thing that would make a difference is custom accessors, but it seems this is not the case either... – sergio Jun 18 '14 at 14:33

1 Answers1

1

I do not think it is generally true that you must use properties in initWithCoder:. I have a lot of code (and have seen a lot) where ivar access is used in initWithCoder:, if that may help as a hint.

If you were not using ARC, then your implementation setting _data would have a problem in that the object would be soon autorelased. But under ARC your code is correct.

So, I tend to think that something different was causing the issue in your case. As an example, if you use KVO, then you should use properties, otherwise the KVO-related notifications are not generated. You should provide more information as to what exactly led you to think that the assignment to _data was the cause of the issue, and about the visible effect of that issue in other parts of your code.

sergio
  • 68,819
  • 11
  • 102
  • 123
  • It is strange. I checked the app and it is using ARC. I looked through all the modules in Build Phases and none are set to non-ARC. What led me to think it is the _data use is that simply changing that line to self.data makes the app work. Otherwise the app crashes just after the [NSKeyedUnarchiver unarchiveObjectWithFile:cachePathForKey]; statement, when I try to do something with the data. It does not crash while in initWithCoder, it crashes after returning from the NSKeyedUnarchiver statement. – Darren Jun 18 '14 at 13:22
  • what kind of crash do you get? can you please post the crash log from the console? when you say it crashes "after" do you mean it does not get to the next statement? how does the full `unarchiveObjectWithFile` look like? – sergio Jun 18 '14 at 13:33
  • It looks like this is a problem with the object being released on return from unarchiveObjectWithFile. When I create my object using MyCachedObject *tmp = [NSKeyedUnarchiver unarchiveObjectWithFile:cachePathForKey]; it crashes. However, if I allocate the object and then call unarchiveObjectWithFile, like this: MyCachedObject *tmp = [[MyCachedObject alloc] init]; tmp = [NSKeyedUnarchiver unarchiveObjectWithFile:cachePathForKey]; it works. Seems like the object is not being retained on return from the call. – Darren Jun 18 '14 at 14:06
  • that is an interesting finding. how does your method go on after `MyCachedObject *tmp = [NSKeyedUnarchiver …` that might explain what is happening. and again, sorry for being demanding, what is the crash message? that could also help... – sergio Jun 18 '14 at 14:33
  • Yes, the message is EXC_BAD_ACCESS (code=1, address=0xe000000c). This does seem to be a problem with over-releasing and it is most likely related to something else in the app, however it does seem interesting that using self.property instead of _property would cause the object to be retained. If I weren't using ARC it would seem less mysterious. – Darren Jun 18 '14 at 14:40
  • would you mind defining `dealloc` in your class and put a breakpoint in there to see when it is deallocated (and if)? – sergio Jun 18 '14 at 14:51
  • I tried that and it didn't go into the dealloc. I suspect you're right and it's something else in the project causing the object to be released. – Darren Jun 18 '14 at 16:14