iOS Programming: The Big Nerd Ranch Guide, 3/e (Big Nerd Ranch Guides) (17 page)

Read iOS Programming: The Big Nerd Ranch Guide, 3/e (Big Nerd Ranch Guides) Online

Authors: Aaron Hillegass,Joe Conway

Tags: #COM051370, #Big Nerd Ranch Guides, #iPhone / iPad Programming

BOOK: iOS Programming: The Big Nerd Ranch Guide, 3/e (Big Nerd Ranch Guides)
13.9Mb size Format: txt, pdf, ePub
Properties

Each time we’ve added an instance variable to
BNRItem
, we’ve declared and implemented a pair of accessor methods. Now we’re going to see how to use
properties
instead. Properties are a convenient alternative to writing out accessors for instance variables – one that saves a lot of typing and makes your class files much clearer to read.

 
Declaring properties

A property is declared in the interface of a class where methods are declared. A property declaration has the following form:

 
@property NSString *itemName;

When you declare a property, you are implicitly declaring a setter and a getter for the instance variable of the same name. So the above line of code is equivalent to the following:

 
- (void)setItemName:(NSString *)str;
- (NSString *)itemName;
 

Each property has a set of attributes that describe the behavior of the accessor methods. The attributes are declared in parentheses after the
@property
directive. Here is an example:

 
@property (nonatomic, readwrite, strong) NSString *itemName;
 

There are three property attributes. Each attribute has two or three options, one of which is the default and does not have to explicitly declared.

 

The first attribute of a property has two options:
nonatomic
or
atomic
. This attribute has to do with multi-threaded applications and is outside the scope of this book. Most Objective-C programmers typically use
nonatomic
: we do at Big Nerd Ranch, and so does Apple. In this book, we’ll use
nonatomic
for all properties.

 

Let’s change
BNRItem
to use properties instead of accessor methods. In
BNRItem.h
, replace all of your accessor methods with properties that are nonatomic.

 
- (id)initWithItemName:(NSString *)name
        valueInDollars:(int)value
          serialNumber:(NSString *)sNumber;
- (void)setItemName:(NSString *)str;
- (NSString *)itemName;
- (void)setSerialNumber:(NSString *)str;
- (NSString *)serialNumber;
- (void)setValueInDollars:(int)i;
- (int)valueInDollars;
- (NSDate *)dateCreated;
- (void)setContainedItem:(BNRItem *)i;
- (BNRItem *)containedItem;
- (void)setContainer:(BNRItem *)i;
- (BNRItem *)container;
@property (nonatomic) BNRItem *containedItem;
@property (nonatomic) BNRItem *container;
@property (nonatomic) NSString *itemName;
@property (nonatomic) NSString *serialNumber;
@property (nonatomic) int valueInDollars;
@property (nonatomic) NSDate *dateCreated;
@end

Unfortunately,
nonatomic
is not the default option, so you will always need to explicitly declare your properties to be
nonatomic
.

 

The second attribute of a property is either
readwrite
or
readonly
. A
readwrite
property declares both a setter and getter, and a
readonly
property just declares a getter. The default option for this attribute is
readwrite
. This is what we want for all of
BNRItem
’s properties with the exception of
dateCreated
, which should be
readonly
. In
BNRItem.h
, declare
dateCreated
as a
readonly
property so that no setter method is declared for this instance variable.

 
@property (nonatomic
, readonly
) NSDate *dateCreated;
 

The final attribute of a property describe its memory management. The most common options tell us whether the accessed instance variable has a
strong
or
weak
reference to the object it points to. The default option is
assign
, which is for properties like
valueInDollars
that do not point to an object. The rest of
BNRItem
’s instance variables are all strong references to objects with the exception of
container
, which is weak. Add the
strong
attribute option to the appropriate properties and
weak
to the
container
property.

 
@property (nonatomic
, strong
) BNRItem *containedItem;
@property (nonatomic
, weak
) BNRItem *container;
@property (nonatomic
, strong
) NSString *itemName;
@property (nonatomic
, strong
) NSString *serialNumber;
@property (nonatomic) int valueInDollars;
@property (nonatomic, readonly
, strong
) NSDate *dateCreated;

Build and run the application. You should see the exact same behavior as the last time you ran it. The only difference is that
BNRItem.h
is much cleaner.

 
Synthesizing properties

In addition to using a property to declare accessor methods, you can
synthesize
a property to generate the code for the accessor methods in the implementation file. Right now,
BNRItem.m
defines the accessor methods declared by each property. For example, the property
itemName
declares two accessor methods,
itemName
and
setItemName:
, and these are defined in
BNRItem.m
like so:

 
- (void)setItemName:(NSString *)str
{
    itemName = str;
}
- (NSString *)itemName
{
    return itemName;
}
 

When you synthesize a property, you don’t have to type out the accessor definitions. You can synthesize a property by using the
@synthesize
directive in the implementation file. In
BNRItem.m
, add a synthesize statement for
itemName
and delete the implementations of
setItemName:
and
itemName
.

 
@implementation BNRItem
@synthesize itemName;
- (void)setItemName:(NSString *)str
{
    itemName = str;
}
- (NSString *)itemName
{
    return itemName;
}

You can synthesize properties in the same synthesize statement or split them up into multiple statements. In
BNRItem.m
, synthesize the rest of the instance variables and delete the rest of the accessor implementations.

 
@implementation
@synthesize itemName;
@synthesize containedItem, container, serialNumber, valueInDollars,
    dateCreated;
- (void)setSerialNumber:(NSString *)str
{
    serialNumber = str;
}
- (NSString *)serialNumber
{
    return serialNumber;
}
- (void)setValueInDollars:(int)i
{
    valueInDollars = i;
}
- (int)valueInDollars
{
    return valueInDollars;
}
- (NSDate *)dateCreated
{
    return dateCreated;
}
- (void)setContainedItem:(BNRItem *)i
{
    containedItem = i;
    // When given an item to contain, the contained
    // item will be given a pointer to its container
    [i setContainer:self];
}
- (BNRItem *)containedItem
{
    return containedItem;
}
- (void)setContainer:(BNRItem *)i
{
    container = i;
}
- (BNRItem *)container
{
    return container;
}
 

Usually, synthesized accessors work fine, but sometimes you need an accessor method to do some additional work. This is the case for
setContainedItem:
. Here is our original implementation:

 
- (void)setContainedItem:(BNRItem *)i
{
    containedItem = i;
    [i setContainer:self];
}

The synthesized setter won’t include the second line establishing the reciprocal relationship between the
container
and the
containedItem
. Its implementation just looks like this:

 
- (void)setContainedItem:(BNRItem *)i
{
    containedItem = i;
}
 

Because we need this setter to do additional work, we cannot rely on the synthesized method and must write the implementation ourselves. Fortunately, writing our own implementation does not conflict with synthesizing the property. Any implementation we add will override the synthesized version. In
BNRItem.m
, add back the implementation of
setContainedItem:
.

 
- (void)setContainedItem:(BNRItem *)i
{
    containedItem = i;
    [i setContainer:self];
}
 

Build and run the application again. It should work the same as always, but your code is much cleaner.

 

Synthesizing a property that you declared in the header file is optional, but typical. The only reason not to synthesize a property is if both the getter and the setter methods have additional behavior you need to implement.

 
Instance variables and properties

With properties, we can go even one step further in code clarity. By default, a synthesized property will access the instance variable of the same name. For example, the
itemName
property accesses the
itemName
instance variable: the
itemName
method returns the value of the
itemName
instance variable, and the
setItemName:
method changes the
itemName
instance variable.

 

If there is no instance variable that matches the name of a synthesized property, one is automatically created. So declaring an instance variable
and
synthesizing a property is redundant. In
BNRItem.h
, remove all of the instance variables as well as the curly brackets.

 
@interface BNRItem : NSObject
{
    NSString *itemName;
    NSString *serialNumber;
    int valueInDollars;
    NSDate *dateCreated;
    
BNRItem *containedItem;
    __weak BNRItem *container;
}
 

Build and run the application. Notice there are no errors and everything works fine. All of the instance variables (like
itemName
and
dateCreated
) still exist even though we no longer explicitly declare them.

 
Copying

There is one more change we need to make to our properties – specifically, the two properties that point to instances of
NSString
.

 

In general, when you have a property that points to an instance of a class that has a mutable subclass (like
NSString
or
NSArray
), it is safer to make a copy of the object to point to rather than pointing to an existing object that could have other owners.

 

For instance, imagine if a
BNRItem
was initialized so that its
itemName
pointed to an instance of
NSMutableString
.

 
NSMutableString *mutableString = [[NSMutableString alloc] init];
BNRItem *item = [[BNRItem alloc] initWithItemName:mutableString
                                   valueInDollars:5
                                     serialNumber:@"4F2W7"]];

This code is valid because an
NSMutableString
is also an instance of its superclass,
NSString
. The problem is that the string pointed to by
mutableString
can be changed without the knowledge of the
BNRItem
that also points to it.

 

You may be wondering why this is a real problem. In your application, you’re not going to change this string unless you mean to. However, when you write classes for others to use, you can’t be sure how they will use your classes, and you have to program defensively.

 

In this case, the defense is to declare this property using the memory management attribute
copy
instead of
strong
. In
BNRItem.h
, change the
itemName
and
serialNumber
properties to
copy
.

 
@property (nonatomic,
copy
) NSString *itemName;
@property (nonatomic,
copy
) NSString *serialNumber;
 

Now the generated setter method for the synthesized
itemName
property looks like this:

 
- (void)setItemName:(NSString *)str
{
    itemName = [str copy];
}

Instead of setting
itemName
to point to the
NSString
object pointed to by
str
, this setter sends the message
copy
to that
NSString
. The
copy
method returns a new
NSString
object (not an
NSMutableString
) that has the same values as the original string, and
itemName
is set to point at the new string. In terms of ownership,
copy
gives you a strong reference to the object pointed to. The original string is not modified in any way: it doesn’t gain or lose an owner, and none of its data changes.

 

Other books

Kiss Me Book 1 by Chloe Parks
Reasons to Be Happy by Katrina Kittle
Find Me in Darkness by Julie Kenner
Scar Girl by Len Vlahos
JPod by Douglas Coupland
On the Loose by Christopher Fowler
Cherringham--Snowblind by Neil Richards