Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

initializer, properties, accessors and copy/retain/readonly

I want to understand how to set the parameters of properties (accessors).

I took the following code from an example of Kal calendar.

// Holiday.h

@interface Holiday : NSObject
{
  NSDate *date;
  NSString *name;
  NSString *country;
}

@property (nonatomic, retain, readonly) NSDate *date;
@property (nonatomic, retain, readonly) NSString *name;
@property (nonatomic, retain, readonly) NSString *country;

- (id)initWithName:(NSString *)name country:(NSString *)country date:(NSDate *)date;

@end

// Holiday.m

#import "Holiday.h"

@implementation Holiday

@synthesize date, name, country;

- (id)initWithName:(NSString *)aName country:(NSString *)aCountry date:(NSDate *)aDate
{
  if ((self = [super init])) {
    name = [aName copy];
    country = [aCountry copy];
    date = [aDate retain];
  }
  return self;
}

- (void)dealloc
{
  [date release];
  [name release];
  [country release];
  [super dealloc];
}

@end

1) The properties are set to retain, but since the setter can't be used the retain makes no sense here.

2) In addition, in the initWithName method the values are set with copy. Why not directly defining the properties with copy and using the accessor methods?

@property (nonatomic, copy) NSString *name;
// ...
self.name = aName;

3) Do I need the readonly here? I don't know why they are used here. If I would use copy together with the setter the readonly prohibits me to set the value, because there is no setter.

4) In the initWithName method sometimes copy and sometimes retain is used. I would suggest to use always copy here, because the value should not get modified later.

5) What I can remember is that it is OK to copy/retain in the initWithName and release in the dealloc method.

So how would you suggest to use retain, copy and readonly in this example here?

like image 301
testing Avatar asked Oct 28 '10 15:10

testing


1 Answers

ETA: @DougW correctly points out that a property's ownership type (assign/retain/copy) does not affect the getter. It does still affect the setter. For readonly types, this matters if you are going to override the readonly portion of the declaration in a class extension, so you can use the setter within your implementation. A class extension property override is only allowed to change the readonly status of the property, so the rest of it - which means the atomicity and ownership types - must be declared appropriately in the header. Even if you're not overriding the property now, you might in future, so you might as well document how you want the memory managed for yourself by using the correct option to begin with.

Automatic reference counting (ARC) changes the runtime implementation details by overlaying its own memory management rules on top of the classic refcount rules, but the rules and advice for configuring your properties stay the same.


Why use retain with readonly? If you mark a property as retain, the synthesized accessor does something like this:

/* getter for retain property */
- (NSString *)name {
    return [[name retain] autorelease];
}

Now, if the object you sent -name to changes the name while you're still using it, the calling code will still have a valid reference to a string. If you declared it as assign, though, it would be like this:

/* getter for assign property */
- (NSString *)name {
    return name;
}

Now, as soon as name is changed by the object, it will have to be released to avoid a leak, which will invalidate the calling code's reference. The retain/copy/assign is really stating a memory-management policy: retain/copy says, "I promise that I hold a reference to the original/a copy of the value I provide here," while assign says, "I just have the value and claim no owning reference to this."

When the value doesn't need memory management, such as a plain int, then assign makes sense. When you're intentionally not retaining an object, such as a delegate, then assign makes sense. But, in most other cases, you'll want retain or copy.

Further, the implementation file can only override the readwrite/readonly portion of a property declaration, not the memory management portion. As declared, the .m file can have:

@interface Holiday (/*class extension*/)
@property(nonatomic, retain, readwrite) NSDate *date;
/* override other properties to make them readwrite... */
@end

Non-public setters for the overridden property declarations will then be synthesized along with the public accessors.

Why not use setters/accessors during -init? Because setters/accessors frequently perform KVO notification, which you want to avoid while your object is not fully initialized, i.e., during -init (when it's half-initialized on its way to full initialization) and -dealloc (when it's half-initialized on its way to being fully un-initialized).

Why use copy with readonly? As in response to your first question: because if copy versus retain versus assign affects both the setters and the getters. A copy getter would look like this:

/* getter for copy property */
- (NSString *)name {
    return [[name copy] autorelease];
}

Why sometimes copy and sometimes retain? copy is generally used with value objects (passive objects representing a value); retain is generally used with other objects. Sometimes, efficiency concerns come into play (most likely prematurely...), and you might opt to use retain where you would normally use copy.

How would you use copy/retain along with readonly here? Pretty much as they did. I'd override the declarations in a class extension so I can use setters to change the properties' values outside of -init and -dealloc, where I would only use direct instance variable access. I would also nil out the ivars after releasing them in -dealloc, e.g.,

[name release], name = nil;

This helps avoid sending messages to or otherwise referencing an already-released object.

like image 122
Jeremy W. Sherman Avatar answered Nov 07 '22 13:11

Jeremy W. Sherman