The Objective-C “declared properties” feature provides a simple way to declare and implement an object’s accessor methods.
Overview
Property Declaration and Implementation
Using Properties
Subclassing with Properties
Performance and Threading
Runtime Difference
There are two aspects to this language feature: the syntactic elements you use to specify and optionally synthesize declared properties, and a related syntactic element that is described in “Dot Syntax.”
You typically access an object’s properties (in the sense of its attributes and relationships) through a pair of accessor (getter/setter) methods. By using accessor methods, you adhere to the principle of encapsulation (see “Mechanisms Of Abstraction” in Object-Oriented Programming with Objective-C > The Object Model). You can exercise tight control of the behavior of the getter/setter pair and the underlying state management while clients of the API remain insulated from the implementation changes.
Although using accessor methods has significant advantages, writing accessor methods is nevertheless a tedious process—particularly if you have to write code to support both garbage collected and reference counted environments. Moreover, aspects of the property that may be important to consumers of the API are left obscured—such as whether the accessor methods are thread-safe or whether new values are copied when set.
Declared properties address the problems with standard accessor methods by providing the following features:
The property declaration provides a clear, explicit specification of how the accessor methods behave.
The compiler can synthesize accessor methods for you, according to the specification you provide in the declaration. This means you have less code to write and maintain.
Properties are represented syntactically as identifiers and are scoped, so the compiler can detect use of undeclared properties.
There are two parts to a declared property, its declaration and its implementation.
A property declaration begins with the keyword @property
. @property
can appear anywhere in the method declaration list found in the @interface
of a class. @property
can also appear in the declaration of a protocol or category (protocols and categories are described in “Protocols” and “Categories and Extensions” respectively).
@property(attributes) type name;
@property
declares a property. An optional parenthesized set of attributes provides additional details about the storage semantics and other behaviors of the property—see “Property Declaration Attributes” for possible values. Like any other Objective-C type, each property has a type specification and a name.
Listing 4-1 illustrates the declaration of a simple property.
Listing 4-1 Declaring a simple property
@interface MyClass : NSObject |
{ |
float value; |
} |
@property float value; |
@end |
You can think of a property declaration as being equivalent to declaring two accessor methods. Thus
@property float value; |
is equivalent to:
- (float)value; |
- (void)setValue:(float)newValue; |
A property declaration, however, provides additional information about how the accessor methods are implemented (as described in “Property Declaration Attributes”).
You can decorate a property with attributes by using the form @property(attribute [, attribute2, ...])
. Like methods, properties are scoped to their enclosing interface declaration. For property declarations that use a comma delimited list of variable names, the property attributes apply to all of the named properties.
If you use the @synthesize
directive to tell the compiler to create the accessor method(s), the code it generates matches the specification given by the keywords. If you implement the accessor method(s) yourself, you should ensure that it matches the specification (for example, if you specify copy
you must make sure that you do copy the input value in the setter method).
The default names for the getter and setter methods associated with a property are propertyName and set
PropertyName:
respectively—for example, given a property “foo”, the accessors would be foo
and setFoo:
. The following attributes allow you to specify custom names instead. They are both optional and may appear with any other attribute (except for readonly
in the case of setter=
).
getter=getterName
Specifies the name of the get accessor for the property. The getter must return a type matching the property’s type and take no arguments.
setter=setterName
Specifies the name of the set accessor for the property. The setter method must take a single argument of a type matching the property’s type and must return void
.
If you specify that a property is readonly
then also specify a setter with setter=
, you will get a compiler warning.
Typically you should specify accessor method names that are key-value coding compliant (see Key-Value Coding Programming Guide)—a common reason for using the getter
decorator is to adhere to the is
PropertyName convention for Boolean values.
These attributes specify whether or not a property has an associated set accessor. They are mutually exclusive.
readwrite
Indicates that the property should be treated as read/write. This is the default.
Both a getter and setter method will be required in the @implementation
. If you use @synthesize
in the implementation block, the getter and setter methods are synthesized.
readonly
Indicates that the property is read-only.
If you specify readonly
, only a getter method is required in the @implementation
. If you use @synthesize
in the implementation block, only the getter method is synthesized. Moreover, if you attempt to assign a value using the dot syntax, you get a compiler error.
These attributes specify the semantics of a set accessor. They are mutually exclusive.
assign
Specifies that the setter uses simple assignment. This is the default.
retain
Specifies that retain
should be invoked on the object upon assignment. (The default is assign
.)
The previous value is sent a release
message.
This attribute is valid only for Objective-C object types. (You cannot specify retain
for Core Foundation objects—see “Core Foundation.”)
copy
Specifies that a copy of the object should be used for assignment. (The default is assign
.)
The previous value is sent a release
message.
The copy is made by invoking the copy
method. This attribute is valid only for object types, which must implement the NSCopying
protocol. For further discussion, see “Copy.”
Different constraints apply depending on whether or not you use garbage collection:
If you do not use garbage collection, for object properties you must explicitly specify one of assign
, retain
or copy
—otherwise you will get a compiler warning. (This encourages you to think about what memory management behavior you want and type it explicitly.)
To decide which you should choose, you need to understand Cocoa’s memory management policy (see Memory Management Programming Guide for Cocoa).
If you use garbage collection, you don't get a warning if you use the default (that is, if you don’t specify any of assign
, retain
or copy
) unless the property's type is a class that conforms to NSCopying
. The default is usually what you want; if the property type can be copied, however, to preserve encapsulation you often want to make a private copy of the object.
This attribute specifies that accessor methods are not atomic. (There is no keyword to denote atomic.)
nonatomic
Specifies that accessors are non-atomic. By default, accessors are atomic.
Properties are atomic by default so that synthesized accessors provide robust access to properties in a multi-threaded environment—that is, the value returned from the getter or set via the setter is always fully retrieved or set regardless of what other threads are executing concurrently. For more details, see “Performance and Threading.”
If you do not specify nonatomic
, then in a reference counted environment a synthesized get accessor for an object property uses a lock and retains and autoreleases the returned value—the implementation will be similar to the following:
[_internal lock]; // lock using an object-level lock |
id result = [[value retain] autorelease]; |
[_internal unlock]; |
return result; |
If you specify nonatomic
, then a synthesized accessor for an object property simply returns the value directly.
Properties support the full range of C style decorators. Properties can be deprecated and support __attribute__
style markup, as illustrated in the following example:
@property CGFloat x |
AVAILABLE_MAC_OS_X_VERSION_10_1_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_4; |
@property CGFloat y __attribute__((...)); |
If you want to specify that a property is an Interface Builder outlet, you can use the IBOutlet
identifier:
@property (nonatomic, retain) IBOutlet NSButton *myButton; |
IBOutlet
is not, though, a formal part of the list of attributes.
If you use garbage collection, you can use the storage modifiers __weak
and __strong
in a property’s declaration:
@property (nonatomic, retain) __weak Link *parent; |
but again they are not a formal part of the list of attributes.
You can use the @synthesize
and @dynamic
directives in @implementation
blocks to trigger specific compiler actions. Note that neither is required for any given @property
declaration.
Important: If you do not specify either @synthesize
or @dynamic
for a particular property, you must provide a getter and setter (or just a getter in the case of a readonly
property) method implementation for that property.
@synthesize
You use the @synthesize
keyword to tell the compiler that it should synthesize the setter and/or getter methods for the property if you do not supply them within the @implementation
block.
Listing 4-2 Using @synthesize
@interface MyClass : NSObject |
{ |
NSString *value; |
} |
@property(copy, readwrite) NSString *value; |
@end |
@implementation MyClass |
@synthesize value; |
@end |
You can use the form property=ivar
to indicate that a particular instance variable should be used for the property, for example:
@synthesize firstName, lastName, age = yearsOld; |
This specifies that the accessor methods for firstName
, lastName
, and age
should be synthesized and that the property age
is represented by the instance variable yearsOld
. Other aspects of the synthesized methods are determined by the optional attributes (see “Property Declaration Attributes”).
There are differences in the behavior that depend on the runtime (see also “Runtime Difference”):
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 (see Runtime Versions and Platforms in Objective-C 2.0 Runtime Programming Guide), instance variables are synthesized as needed. If an instance variable of the same name already exists, it is used.
@dynamic
You use the @dynamic
keyword to tell the compiler that you will fulfill the API contract implied by a property either by providing method implementations directly or at runtime using other mechanisms such as dynamic loading of code or dynamic method resolution. The example shown in Listing 4-3 illustrates using direct method implementations—it is equivalent to the example given in Listing 4-2.
Listing 4-3 Using @dynamic with direct method implementations
@interface MyClass : NSObject |
{ |
NSString *value; |
} |
@property(copy, readwrite) NSString *value; |
@end |
// assume using garbage collection |
@implementation MyClass |
@dynamic value; |
- (NSString *)value { |
return value; |
} |
- (void)setValue:(NSString *)newValue { |
if (newValue != value) { |
value = [newValue copy]; |
} |
} |
@end |
You can declare a property for any Objective-C class, Core Foundation data type, or “plain old data” (POD) type (see C++ Language Note: POD Types). For constraints on using Core Foundation types, however, see “Core Foundation.”
You can re-declare a property in a subclass, but (with the exception of readonly
vs. readwrite
) you must repeat its attributes in whole in the subclasses. The same holds true for a property declared in a category or protocol—while the property may be redeclared in a category or protocol, the property’s attributes must be repeated in whole.
If you declare a property in one class as readonly
, you can redeclare it as readwrite
in a class extension (see “Extensions”), a protocol, or a subclass—see “Subclassing with Properties.” In the case of a class extension redeclaration, the fact that the property was redeclared prior to any @synthesize
statement will cause the setter to be synthesized. The ability to redeclare a read-only property as read/write enables two common implementation patterns: a mutable subclass of an immutable class (NSString
, NSArray
, and NSDictionary
are all examples) and a property that has public API that is readonly
but a private readwrite
implementation internal to the class. The following example shows using a class extension to provide a property that is declared as read-only in the public header but is redeclared privately as read/write.
// public header file |
@interface MyObject : NSObject { |
NSString *language; |
} |
@property (readonly, copy) NSString *language; |
@end |
// private implementation file |
@interface MyObject () |
@property (readwrite, copy) NSString *language; |
@end |
@implementation MyObject |
@synthesize language; |
@end |
If you use the copy
declaration attribute, you specify that a value is copied during assignment. If you synthesize the corresponding accessor, the synthesized method uses the copy
method. This is useful for attributes such as string objects where there is a possibility that the new value passed in a setter may be mutable (for example, an instance of NSMutableString
) and you want to ensure that your object has its own private immutable copy. For example, if you declare a property as follows:
@property (nonatomic, copy) NSString *string; |
then the synthesized setter method is similar to the following:
-(void)setString:(NSString *)newString { |
if (string != newString) { |
[string release]; |
string = [newString copy]; |
} |
} |
Although this works well for strings, it may present a problem if the attribute is a collection such as an array or a set. Typically you want such collections to be mutable, but the copy
method returns an immutable version of the collection. In this situation, you have to provide your own implementation of the setter method, as illustrated in the following example.
@interface MyClass : NSObject { |
NSMutableArray *myArray; |
} |
@property (nonatomic, copy) NSMutableArray *myArray; |
@end |
@implementation MyClass |
@synthesize myArray; |
- (void)setMyArray:(NSMutableArray *)newArray { |
if (myArray != newArray) { |
[myArray release]; |
myArray = [newArray mutableCopy]; |
} |
} |
@end |
Declared properties fundamentally take the place of accessor method declarations; when you synthesize a property, the compiler only creates any absent accessor methods. There is no direct interaction with the dealloc
method—properties are not automatically released for you. Declared properties do, however, provide a useful way to cross-check the implementation of your dealloc
method: you can look for all the property declarations in your header file and make sure that object properties not marked assign
are released, and those those marked assign
are not released.
Note: Typically in a dealloc
method you should release object instance variables directly (rather than invoking a set accessor and passing nil
as the parameter), as illustrated in this example:
- (void)dealloc { |
[property release]; |
[super dealloc]; |
} |
- (void)dealloc { |
[self setProperty:nil]; |
[super dealloc]; |
} |
As noted in “Property Declaration Attributes,” you cannot specify the retain
attribute for non-object types. If, therefore, you declare a property whose type is a CFType and synthesize the accessors as illustrated in the following example:
@interface MyClass : NSObject |
{ |
CGImageRef myImage; |
} |
@property(readwrite) CGImageRef myImage; |
@end |
@implementation MyClass |
@synthesize myImage; |
@end |
then in a reference counted environment the generated set accessor will simply assign the new value to the instance variable (the new value is not retained and the old value is not released). This is typically incorrect, so you should not synthesize the methods, you should implement them yourself.
In a garbage collected environment, if the variable is declared __strong
:
... |
__strong CGImageRef myImage; |
... |
@property CGImageRef myImage; |
then the accessors are synthesized appropriately—the image will not be CFRetain'd, but the setter will trigger a write barrier.
The following example illustrates the use of properties in several different ways:
The Link protocol declares a property, next
.
MyClass adopts the Link protocol so implicitly also declares the property next
. MyClass also declares several other properties.
creationTimestamp
and next
are synthesized but use existing instance variables with different names;
name
is synthesized, and uses instance variable synthesis (recall that instance variable synthesis is not supported using the legacy runtime—see “Property Implementation Directives” and “Runtime Difference”);
gratuitousFloat
has a dynamic
directive—it is supported using direct method implementations;
nameAndAge
does not have a dynamic
directive, but this is the default value; it is supported using a direct method implementation (since it is read-only, it only requires a getter) with a specified name (nameAndAgeAsString
).
Listing 4-4 Declaring properties for a class
@protocol Link |
@property id <Link> next; |
@end |
@interface MyClass : NSObject <Link> |
{ |
NSTimeInterval intervalSinceReferenceDate; |
CGFloat gratuitousFloat; |
id <Link> nextLink; |
} |
@property(readonly) NSTimeInterval creationTimestamp; |
@property(copy) __strong NSString *name; |
@property CGFloat gratuitousFloat; |
@property(readonly, getter=nameAndAgeAsString) NSString *nameAndAge; |
@end |
@implementation MyClass |
@synthesize creationTimestamp = intervalSinceReferenceDate, name; |
// synthesizing 'name' is an error in legacy runtimes |
// in modern runtimes, the instance variable is synthesized |
@synthesize next = nextLink; |
// uses instance variable "nextLink" for storage |
@dynamic gratuitousFloat; |
// will warn unless -gratuitousFloat and -setGratuitousFloat: occur in @implementation |
- (CGFloat)gratuitousFloat { |
return gratuitousFloat; |
} |
- (void)setGratuitousFloat:(CGFloat)aValue { |
gratuitousFloat = aValue; |
} |
- (NSString *)nameAndAgeAsString { |
return [NSString stringWithFormat:@"%@ (%fs)", [self name], |
[NSDate timeIntervalSinceReferenceDate] - intervalSinceReferenceDate]; |
} |
- (id)init { |
if (self = [super init]) { |
intervalSinceReferenceDate = [NSDate timeIntervalSinceReferenceDate]; |
} |
return self; |
} |
- (void)dealloc { |
[nextLink release]; |
[name release]; |
[super dealloc]; |
} |
@end |
You can override a readonly
property to make it writable. For example, you could define a class MyInteger
with a readonly
property, value
:
@interface MyInteger : NSObject |
{ |
NSInteger value; |
} |
@property(readonly) NSInteger value; |
@end |
@implementation MyInteger |
@synthesize value; |
@end |
You could then implement a subclass, MyMutableInteger
, which redefines the property to make it writable:
@interface MyMutableInteger : MyInteger |
@property(readwrite) NSInteger value; |
@end |
@implementation MyMutableInteger |
@dynamic value; |
- (void)setValue:(NSInteger)newX { |
value = newX; |
} |
@end |
If you supply your own method implementation, the fact that you declared a property has no effect on its efficiency or thread safety.
If you use synthesized properties, the method implementations generated by the compiler depend on the specification you supply. The declaration attributes that affect performance and threading are retain
, assign
, copy
, and nonatomic
. The first three of these affect only the implementation of the assignment part of the set method, as illustrated below (the implementation may not be exactly as shown):
// assign |
property = newValue; |
// retain |
if (property != newValue) { |
[property release]; |
property = [newValue retain]; |
} |
// copy |
if (property != newValue) { |
[property release]; |
property = [newValue copy]; |
} |
The effect of the nonatomic
attribute depends on the environment. By default, the synthesized accessors are atomic. In a reference counted environment, guaranteeing atomic behavior requires the use of a lock; moreover a returned object is retained and autoreleased, as illustrated in “Atomicity.” If such accessors are invoked frequently, this may have a significant impact on performance. In a garbage collected environment, most synthesized methods are atomic without incurring this overhead.
It is important to understand that the goal of the atomic implementation is to provide robust accessors—it does not guarantee correctness of your code. Although “atomic” means that access to the property is thread-safe, simply making all the properties in your class atomic does not mean that your class or more generally your object graph is “thread safe”—thread safety cannot be expressed at the level of individual accessor methods. For more about multi-threading, see Threading Programming Guide.
In general the behavior of properties is identical on all runtimes (see Runtime Versions and Platforms in Objective-C 2.0 Runtime Programming Guide). There is one key difference: the modern runtime supports instance variable synthesis whereas the legacy runtime does not.
For @synthesize
to work in the legacy runtime, you must either provide an instance variable with the same name and compatible type of the property or specify another existing instance variable in the @synthesize
statement. With the modern runtime, if you do not provide an instance variable, the compiler adds one for you. For example, given the following class declaration and implementation:
@interface MyClass : NSObject { |
float sameName; |
float otherName; |
} |
@property float sameName; |
@property float differentName; |
@property float noDeclaredIvar; |
@end |
@implementation MyClass |
@synthesize sameName; |
@synthesize differentName=otherName; |
@synthesize noDeclaredIvar; |
@end |
the compiler for the legacy runtime would generate an error at @synthesize noDeclaredIvar;
whereas the compiler for the modern runtime would add an instance variable to represent noDeclaredIvar
.
© 2009 Apple Inc. All Rights Reserved. (Last updated: 2009-05-06)