Safari Books Online is a digital library providing on-demand subscription access to thousands of learning resources.
A very common pattern in object-oriented design is the use of accessors (getters and setters) to change object state. The dot notation provides a simpler syntax for using accessors. Objective-C provides a way to automate the process (this is good because writing accessors can be quite boring).
Dot notation provides a simple notation to invoke accessors. Assuming controller is an instance of CalculatorController and CalculatorController has a getter model that returns a CalculatorModel, then the following lines are equivalent and compile to the same executable:
double v = [[controller model] value]; double v = controller.model.value;
Similarly,
[[controller model] setValue:1.1]; controller.model.value = 1.1;
The notation understands compound assignments:
[[controller model] setValue:(1.0 + [[controller model] value]) ]; controller.model.value += 1.0;
A warning will be emitted if accessors do not use the following type pattern. For instance, setters that return values (such as a BOOL) will cause a warning:
– (Type) value; – (void) setValue:(Type)v;
Using this notation, we can write CalculatorController’s dealloc as follows:
– (void) dealloc; { self.textView = nil; [model release]; model = nil; // model has no setter [super dealloc]; }
Dot notation has the same pitfalls as accessors do. In the following example, you’ll get 0 on the simulator and a random value on the iPhone because str is nil and rangeOfString: returns a structure.
NSString* str = nil; int oslo = [str rangeOfString:@"Oslo"].location;
Did you Know?
The implementation of dot notation is extremely simpleminded. If something looks like a getter, it can be used like one. For instance, retain looks like a getter:
CalculatorModel* model = controller.model.retain;
This is not a recommended practice, but you might encounter it when reading other people’s code.
If a property only has a getter, and you try to write to it with dot notation, the compiler will signal an error:
error: object cannot be set – either readonly property or no setter found
If a property only has a setter, you cannot read it or write it with dot notation:
error: request for member '...' in something not a structure or union
On the other hand, invoking a getter or a setter directly might or might not generate a warning.
There are three disadvantages to dot notation:
It makes searching for properties in source-code files harder (search for “.property =” versus setProperty:).
It encourages you to chain many properties, which, like nesting method invocations, makes debugging nil issues harder.
It does not work with id types.
Although Apple claims dot notation works with the id type, it does not. You can’t write
[array objectAtIndex:0].text = @"foo";
but you can write
[[array objectAtIndex:0] setText:@"foo"];
Property specification lets you specify how accessors behave. For instance, you can state whether a property will be retained. Synthesis automatically generates accessors based on the specification. Again this saves you from writing boring code.
A problem with simply declaring a setter is that its users cannot know how it behaves. For instance, although most setters retain their argument, some don’t, and some copy it. Programmers must rely on the source code (if available), the documentation (if correct), or reverse engineering to know what happens.
Property specifications are placed in interfaces and are written as follows:
@property (attributes) type name;
The optional list of (attributes) is comma separated. Default attributes need not be mentioned. type is the type of the value being accessed, and name is its name. For instance:
@interface CalculatorModel : NSObject { float value; ... } @property float value; ... @end
The property specification replaces the accessor declarations. If you implement the accessors yourself, be aware that the property specification is simply a declaration of intent, and is not enforced by the compiler.
The following sections examine the three ways in which accessors can differ.
By default, all properties are readwrite—that is, they have both a setter and a getter. You can, however, specify that they only have a getter with the attribute readonly. For instance:
@interface CalculatorController : NSObject { CalculatorModel* model; UITextView* textView; } @property (readonly, retain) CalculatorModel* model; @property (retain) UITextView* textView; ... @end
By default, all properties are assigned. A copy of the argument is made. This makes sense for all nonpointers.
However, pointers are generally qualified either by retain or copy attributes. Usually, objects want to own the objects to which their pointers reference, so that they know their pointers are valid. They can do so either by retaining the object (as we did in setTextView:) or by copying the object. We will encounter a rare counterexample of an assign to a pointer property specification in Hour 11, “Displaying Tables.”
By default, all properties are assumed to be atomic—that is, the accessors are thread safe. Thread safe accessors guarantee that when you use the getter, you obtain a pointer to a valid object. They do not guarantee that other threads will not change the contents of the object you are given.
If you know your objects will not be used by multiple threads, use the nonatomic attribute. Thread-unsafe accessors make no guarantees with respect to threads, but they are faster.
As property specifications completely define the behavior of accessors, Objective-C can generate code for them. To do this, add the following to @implementation:
@synthesize name, name2...;
For instance, the three nonatomic memory variants are as follows:
@property (nonatomic, memory attribute) id pointer; @synthesize pointer;
| memory attribute | Generated Code |
|---|---|
| ASSIGN: | { pointer = p; }; |
| RETAIN: | { if (p != pointer) |
| { [pointer release]; pointer = [p retain]; } } | |
| COPY: | { if (p != pointer) |
| { [pointer release]; pointer = [p copy]; } } |
Atomic getters and setters use a lock on the object to guarantee thread-safe access. Furthermore, getters retain, then autorelease the result, so that it is still available if other threads release it.
In the next hour, you’ll learn about the different variants of copy. If you need to use a different way of copying, such as mutableCopy, you must implement the getter and setter by hand.