views:

554

answers:

5

I have several years of experience in Obj-c and Cocoa, but am just now getting back into it and the advances of Obj-C 2.0 etc.

I'm trying to get my head around the modern runtime and declaring properties, etc. One thing that confuses me a bit is the ability in the modern runtime to have the iVars created implicitly. And of course this implies that in your code you should always be using self.property to access the value.

However, in init* and dealloc(assuming you're not using GC) methods we should be using the iVar directly (in the current runtime).

So questions are:

  1. Should we use property accessors in init* and dealloc with Modern Runtime?

  2. If so, why is this different? Is it just because the compiler can't see the iVar?

  3. If I need to override an accessor, can I still access that iVar that will be defined at runtime or do I have to define an actual iVar that the runtime will then use?

  4. Again, if I can access the synthesized iVar, why can't I continue to do this for the init* and dealloc methods?

I read the docs several times, but they seemed a bit vague about all of this and I want to be sure that I understand it well in order to decide how I want to continue coding.

Hope that my questions are clear.


Quick summary of testing:

  1. If you don't declare the ivar in legacy, compiler is completely unhappy

  2. If you use #ifndef __OBJC2__ around ivar in legacy compiler is happy and you can use both ivar directly and as property

  3. In modern runtime, you can leave the ivar undefined and access as property

  4. In modern runtime, trying to access ivar directly without declaration gives error during compile

  5. @private declaration of ivar, of course, allows direct access to ivar, in both legacy and modern

Doesn't really give a clean way to go forward right now does it?

A: 

There is another SO question with similar information, but it isn't quite a duplicate.

The bottom line, from the Objective-C 2.0 documentation, and quoted from Mark Bessey's answer is as follows:

There are differences in the behavior that depend on the runtime (see also “Runtime Differences”):

For the legacy runtimes, instance variables must already be declared in the @interface block. If an instance variable of the same name and compatible type as the property exists, it is used—otherwise, you get a compiler error.

For the modern runtimes, instance variables are synthesized as needed. If an instance variable of the same name already exists, it is used.

My understanding is as follows:

You should not use property accessors in init* and dealloc methods, for the same reasons that you should not use them in the legacy runtime: It leaves you open to potential errors if you later override the property methods, and end up doing something that shouldn't be done in init* or dealloc.

You should be able to both synthesize the ivar and override the property methods as follows:

@interface SomeClass
{
}
@property (assign) int someProperty;
@end

@implementation SomeClass
@synthesize someProperty; // this will synthesize the ivar
- (int)someProperty { NSLog(@"getter"); return someProperty; }
- (void)setSomeProperty:(int)newValue
{
    NSLog(@"setter");
    someProperty = newValue;
}
@end

Which leads me to think that you would be able to access the synthesized ivar in your init* and dealloc methods as well. The only gotcha I could think of is that the @synthesize line may have to come before the definitions of your init* and dealloc methods in the source file.

In the end, since having the ivars declared in the interface still works, that is still your safest bet.

e.James
Hmm, this comment seems to contradict what Barry mentions in Quoting Greg Parker and also intuitively seems wrong (though intuition can be crap sometimes :-) ). It seems the private iVar declaration would be the way to go.
littleknown
Based on his answer, Barry Wark has more experience in this matter than I do. I would trust his intuition over mine : )
e.James
+1  A: 

Since instance variables themselves can only be synthesized in the modern runtime (and must be declared in the @interface under 32-bit or pre-Leopard), it's safest / most portable to also declare the ivar

  • Should we use property accessors in init* and dealloc with Modern Runtime?

My rule of thumb is "possibly" for -init*, and "usually not" for -dealloc.

When initializing an object, you want to make sure to properly copy/retain values for ivars. Unless the property's setter has some side effect that makes it inappropriate for initialization, definitely reuse the abstraction the property provides.

When deallocating an object, you want to release any ivar objects, but not store new ones. An easy way to do this is to set the property to nil (myObject.myIvar = nil), which basically calls [myObject setMyIvar:nil]. Since messages to nil are ignored, there is no danger in this. However, it's overkill when [myIvar release]; is usually all you need. In general, don't use the property (or directly, the setter) in situations where deallocation should behave differently than setting the variable.

I can understand eJames' argument against using property accessors in init/dealloc at all, but the flipside is that if you change the property behavior (for example, change from retain to copy, or just assign without retaining) and don't use it in init, or vice versa, the behavior can get out of sync too. If initializing and modifying an ivar should act the same, use the property accessor for both.

  • If so, why is this different? Is it just because the compiler can't see the ivar?

The modern runtime deals with class size and layout more intelligently, which is why you can change the layout of ivars without having to recompile subclasses. It is also able to infer the name and type of the ivar you want from the name and type of the corresponding property. The Objective-C 2.0 Runtime Programming Guide has more info, but again, I don't know how deeply the details explained there.

  • If I need to override an accessor, can I still access that iVar that will be defined at runtime or do I have to define an actual iVar that the runtime will then use?

I haven't tested this, but I believe you're allowed to access the named ivar in code, since it actually does have to be created. I'm not sure whether the compiler will complain, but I would guess that since it will let you synthesize the ivar without complaining, it is also smart enough to know about the synthesized ivar and let you refer to it by name.

  • Again, if I can access the synthesized iVar, why can't I continue to do this for the init* and dealloc methods?

You should be able to access the property and/or ivar anytime after the instance has been allocated.

Quinn Taylor
Unfortunately the docs don't go much deeper into this. That was the reason for the questions here. You're point about the iVars "having to be created" though is misleading. The reason the modern runtime can layout the class structure better is because it does it at runtime. Therefore these variables don't yet exist. However, I think I'll go run some tests in a 64-bit app and report back.
littleknown
+7  A: 

In the current (OS X 10.5/GCC 4.0.1) compiler, you cannot directly access the runtime-synthesized ivars. Greg Parker, one of the OS X runtime engineers put it this way on the cocoa-dev list (March 12, 2009):

You can't in the current compiler. A future compiler should fix that. Use explicit @private ivars in the meantime. An @private ivar should not be considered part of the contract - that's what @private means, enforced by compiler warnings and linker errors.

And why isn't there a way to explicitly declare instance variables in the .m file for the new runtime?

Three reasons: (1) there are some non-trivial design details to work out, (2) compiler-engineer-hours are limited, and (3) @private ivars are generally good enough.

So, for now you must use dot-notation to access properties, even in init and dealloc. This goes against the best practice of using ivars directly in these cases, but there's no way around it. I find that the ease of using runtime-synthesized ivars (and the performance benefits) outweigh this in most cases. Where you do need to access the ivar directly, you can use a @private ivar as Greg Parker suggests (there's nothing that prevents you from mixing explicitly declared and runtime-synthesized ivars).

Update With OS X 10.6, the 64-bit runtime does allow direct access to the synthesized ivars via self->ivar.

Barry Wark
Thanks Barry, this is what I thought and was hoping to confirm. As I mention in a comment below, I'm going to give this a test before really marking as answered.
littleknown
Okay, I just confirmed that this actually works as described here. See edit above for results.
littleknown
It appears that you can leave out the "self->" part and just use the ivar name directly as well. This doesn't seem to work for the iPhone runtime at present though.
briankc
@briankc That's awesome. Keep in mind that accessing the ivars directly will bypass KVO notifications, so it should probably only be used in the init/dealloc methods.The iPhone and OS X runtimes often leapfrog each other. Apple seems to have gotten on an alternating release schedule which naturally puts the most recent runtime on one platform before the other.
Barry Wark
A: 

I am running into the same problem. The way I am working around not being able to access the synthesized instance variables is the following:

public header

@interface MyObject:NSObject {
}
@property (retain) id instanceVar;
@property (retain) id customizedVar;
@end

private header / implementation

@interface MyObject()
@property (retain) id storedCustomizedVar;
@end

@implementation MyObject
@synthesize instanceVar, storedCustomizedVar;
@dynamic customizedVar;

- customizedVar {
  if(!self.storedCustomizedVar) {
    id newCustomizedVar;
    //... do something
    self.storedCustomizedVar= newCustomizedVar;
  }
  return self.storedCustomizedVar;
}

- (void) setCustomizedVar:aVar {
  self.storedCustomizedVar=aVar;
}

@end

It's not that elegant, but at least it keeps my public header file clean.

If you use KVO you need to define customizedVar as dependent key of storedCustomizedVar.

NikWest
A: 

I'm relatively new to Obj-C (but not to programming) and have also been confused by this topic.

The aspect that worries me is that it seems to be relatively easy to inadvertently use the iVar instead of the property. For example writing:

myProp = someObject;

instead of

self.myProp = someObject;

Admittedly this is "user" error, but it's still seems quite easy to do accidentally in some code, and for a retained or atomic property it could presumably lead to problems.

Ideally I'd prefer to be able to get the runtime to apply some pattern to the property name when generating any iVar. E.g. always prefix them with "_".

In practice at the moment I'm doing this manually - explicitly declaring my ivars, and deliberately giving them different names from the properties. I use an old-style 'm' prefix, so if my property is "myProp", my iVar will be "mMyProp". Then I use @synthesize myProp = mMyProp to associate the two.

This is a bit clumsy I admit, and a bit of extra typing, but it seems worth it to me to be able to disambiguate a little bit more clearly in the code. Of course I can still get it wrong and type mMyProp = someObject, but I'm hoping that the 'm' prefix will alert me to my error.

It would feel much nicer if I could just declare the property and let the compiler/runtime do the rest, but when I have lots of code my gut instinct tells me that I'll make mistakes that way if I still have to follow manual rules for init/dealloc.

Of course there are also plenty of other things I can also do wrong...

Sam Deane