30
Properties

In the last chapter, you created a class called Appliance that had two properties: productName and voltage. Let’s review how those properties work.

In Appliance.h, you declared two instance variables to hold the data:

{​
 ​ ​ ​ ​N​S​S​t​r​i​n​g​ ​*​p​r​o​d​u​c​t​N​a​m​e​;​
 ​ ​ ​ ​i​n​t​ ​v​o​l​t​a​g​e​;​
}​

You also declared accessor methods for them. You could have declared the accessors like this:

-​ ​(​v​o​i​d​)​s​e​t​P​r​o​d​u​c​t​N​a​m​e​:​(​N​S​S​t​r​i​n​g​ ​*​)​s​;​
-​ ​(​N​S​S​t​r​i​n​g​ ​*​)​p​r​o​d​u​c​t​N​a​m​e​;​
-​ ​(​v​o​i​d​)​s​e​t​V​o​l​t​a​g​e​:​(​i​n​t​)​x​;​
-​ ​(​i​n​t​)​v​o​l​t​a​g​e​;​

However, you used the @property construct instead:

@​p​r​o​p​e​r​t​y​ ​(​c​o​p​y​)​ ​N​S​S​t​r​i​n​g​ ​*​p​r​o​d​u​c​t​N​a​m​e​;​
@​p​r​o​p​e​r​t​y​ ​i​n​t​ ​v​o​l​t​a​g​e​;​

In Appliance.m, you could have implemented the accessor methods explicitly like this:

-​ ​(​v​o​i​d​)​s​e​t​P​r​o​d​u​c​t​N​a​m​e​:​(​N​S​S​t​r​i​n​g​ ​*​)​s​
{​
 ​ ​ ​ ​p​r​o​d​u​c​t​N​a​m​e​ ​=​ ​[​s​ ​c​o​p​y​]​;​
}​

-​ ​(​N​S​S​t​r​i​n​g​ ​*​)​p​r​o​d​u​c​t​N​a​m​e​
{​
 ​ ​ ​ ​r​e​t​u​r​n​ ​p​r​o​d​u​c​t​N​a​m​e​;​
}​

-​ ​(​v​o​i​d​)​s​e​t​V​o​l​t​a​g​e​:​(​i​n​t​)​x​
{​
 ​ ​ ​ ​v​o​l​t​a​g​e​ ​=​ ​x​;​
}​

-​ ​(​i​n​t​)​v​o​l​t​a​g​e​
{​
 ​ ​ ​ ​r​e​t​u​r​n​ ​v​o​l​t​a​g​e​;​
}​

However, you used the @synthesize construct to implement them:

@​s​y​n​t​h​e​s​i​z​e​ ​p​r​o​d​u​c​t​N​a​m​e​,​ ​v​o​l​t​a​g​e​;​

Objective-C compiler trivia: When compiling for iOS or a 64-bit Mac OS X program, you don’t need to declare the instance variables. The @property/@synthesize calls are sufficient to make the space for the data.

Open up Appliances and comment out the instance variables in Appliance.h:

{​
 ​ ​ ​ ​/​/​ ​N​S​S​t​r​i​n​g​ ​*​p​r​o​d​u​c​t​N​a​m​e​;​
 ​ ​ ​ ​/​/​ ​i​n​t​ ​v​o​l​t​a​g​e​;​
}​

Rebuild and run the program.

In this book, we always declare the instance variables. It is a nice form of documentation and makes your code usable within 32-bit Mac OS X programs. Uncomment the instance variables.

Property attributes

Now let’s take a closer look at the different attributes you can use to control how the accessors for a property will be created.

Mutability

A property can be declared readwrite or readonly. The default is readwrite, which means that both a setter and a getter method are created. If you don’t want a setter method to be created, you mark the property as readonly:

@​p​r​o​p​e​r​t​y​ ​(​r​e​a​d​o​n​l​y​)​ ​i​n​t​ ​v​o​l​t​a​g​e​;​

Lifetime specifiers

A property can also be declared unsafe_unretained, strong, weak, or copy. This option determines how the setter handles its memory management.

unsafe_unretained is the default and the simplest: it just assigns the passed-in value to the property. Imagine this declaration and definition:

@​p​r​o​p​e​r​t​y​ ​(​u​n​s​a​f​e​_​u​n​r​e​t​a​i​n​e​d​)​ ​i​n​t​ ​a​v​e​r​a​g​e​S​c​o​r​e​;​
/​/​ ​"​@​p​r​o​p​e​r​t​y​ ​i​n​t​ ​a​v​e​r​a​g​e​S​c​o​r​e​"​ ​w​o​u​l​d​ ​a​l​s​o​ ​w​o​r​k​ ​h​e​r​e​
.​.​.​
@​s​y​n​t​h​e​s​i​z​e​ ​a​v​e​r​a​g​e​S​c​o​r​e​;​

This would result in a setter method that’s pretty much equivalent to:

-​ ​(​v​o​i​d​)​s​e​t​A​v​e​r​a​g​e​S​c​o​r​e​:​(​i​n​t​)​d​
{​
 ​ ​ ​ ​a​v​e​r​a​g​e​S​c​o​r​e​ ​=​ ​d​;​
}​

In Appliance, voltage is an unsafe, unretained property. You will always use unsafe_unretained for properties that hold non-objects.

strong, as you learned in Chapter 20, will ensure that a strong reference is kept to the passed-in object. It will also let go of ownership of the old object (which will then deallocate itself if it has no other owners). For properties that hold objects, you will usually use strong.

weak does not imply ownership of the object pointed to. It will synthesize a setter that sets the property to the passed-in object. If this object is deallocated, the property will be set to nil. (Note that if the pointer is unsafe_unretained and the object it points to is deallocated, you will have a dangling pointer. Sending a message to a dangling pointer usually crashes your program.)

copy forms a strong reference to a copy of the object passed in. But, there is a detail in this that most people misunderstand...

copy

The copy option makes a copy of an object and then changes the pointer to refer to this copy. Imagine you had a property declaration and definition like this:

@​p​r​o​p​e​r​t​y​ ​(​c​o​p​y​)​ ​N​S​S​t​r​i​n​g​ ​*​l​a​s​t​N​a​m​e​;​
@​s​y​n​t​h​e​s​i​z​e​ ​l​a​s​t​N​a​m​e​;​

The generated setter method would look somewhat like this:

-​ ​(​v​o​i​d​)​s​e​t​L​a​s​t​N​a​m​e​:​(​N​S​S​t​r​i​n​g​ ​*​)​d​
{​
 ​ ​ ​ ​l​a​s​t​N​a​m​e​ ​=​ ​[​d​ ​c​o​p​y​]​;​
}​

Use of the copy attribute is most common with object types that have mutable subclasses. For example, NSString has a subclass called NSMutableString. You can imagine that your setLastName: method might be passed a mutable string:

/​/​ ​C​r​e​a​t​e​ ​a​ ​m​u​t​a​b​l​e​ ​s​t​r​i​n​g​
N​S​M​u​t​a​b​l​e​S​t​r​i​n​g​ ​*​x​ ​=​ ​[​[​N​S​M​u​t​a​b​l​e​S​t​r​i​n​g​ ​a​l​l​o​c​]​ ​i​n​i​t​W​i​t​h​S​t​r​i​n​g​:​@​"​O​n​o​"​]​;​

/​/​ ​P​a​s​s​ ​i​t​ ​t​o​ ​s​e​t​L​a​s​t​N​a​m​e​:​
[​m​y​O​b​j​ ​s​e​t​L​a​s​t​N​a​m​e​:​x​]​;​

/​/​ ​'​c​o​p​y​'​ ​p​r​e​v​e​n​t​s​ ​t​h​i​s​ ​f​r​o​m​ ​c​h​a​n​g​i​n​g​ ​t​h​e​ ​l​a​s​t​N​a​m​e​
[​x​ ​a​p​p​e​n​d​S​t​r​i​n​g​:​@​"​ ​L​e​n​n​o​n​"​]​;​

What if the object passed in is not mutable? It seems wasteful to make a copy of an immutable object. The copy method just calls copyWithZone: and passes nil as the argument. For example, in NSString, the copyWithZone: method is overridden to look like this:

-​ ​(​i​d​)​c​o​p​y​W​i​t​h​Z​o​n​e​:​(​N​S​Z​o​n​e​ ​*​)​z​
{​
 ​ ​ ​ ​r​e​t​u​r​n​ ​s​e​l​f​;​
}​

That is, it doesn’t make a copy at all. (Note that NSZone and memory zoning in general are all but deprecated, vestigial features of Cocoa programming, so we won’t go further into them here. copyWithZone: still has some use, however, and has not been entirely phased out.)

For objects that come in mutable and immutable versions, the copy method returns an immutable copy. For example, NSMutableString has a copy method that returns an instance of NSString. If you want the copy to be a mutable object, use the mutableCopy method.

There is no property lifetime specifier called mutableCopy. If you wish for your setter to set the property to be a mutable copy of an object, you must implement the setter yourself so that it calls the mutableCopy method on the incoming object. For example, in OwnedAppliance, you might create a setOwnerNames: method:

-​ ​(​v​o​i​d​)​s​e​t​O​w​n​e​r​N​a​m​e​s​:​(​N​S​S​e​t​ ​*​)​n​e​w​N​a​m​e​s​
{​
 ​ ​ ​ ​o​w​n​e​r​N​a​m​e​s​ ​=​ ​[​n​e​w​N​a​m​e​s​ ​m​u​t​a​b​l​e​C​o​p​y​]​;​
}​

More about copying

Most Objective-C classes have no copyWithZone: method at all. Objective-C programmers make fewer copies than you might think.

Curiously, the copy and mutableCopy methods are defined in NSObject like this:

-​ ​(​i​d​)​c​o​p​y​
{​
 ​ ​ ​ ​r​e​t​u​r​n​ ​[​s​e​l​f​ ​c​o​p​y​W​i​t​h​Z​o​n​e​:​N​U​L​L​]​;​
}​

-​ ​(​i​d​)​m​u​t​a​b​l​e​C​o​p​y​
{​
 ​ ​ ​ ​r​e​t​u​r​n​ ​[​s​e​l​f​ ​m​u​t​a​b​l​e​C​o​p​y​W​i​t​h​Z​o​n​e​:​N​U​L​L​]​;​
}​

Thus, if you have some code like this:

A​p​p​l​i​a​n​c​e​ ​*​b​ ​=​ ​[​[​A​p​p​l​i​a​n​c​e​ ​a​l​l​o​c​]​ ​i​n​i​t​]​;​
A​p​p​l​i​a​n​c​e​ ​*​c​ ​=​ ​[​b​ ​c​o​p​y​]​;​

You will get an error like this:

-​[​A​p​p​l​i​a​n​c​e​ ​c​o​p​y​W​i​t​h​Z​o​n​e​:​]​:​ ​u​n​r​e​c​o​g​n​i​z​e​d​ ​s​e​l​e​c​t​o​r​ ​s​e​n​t​ ​t​o​ ​i​n​s​t​a​n​c​e​ ​0​x​1​0​0​1​1​0​1​3​0​

Advice on atomic vs. nonatomic

This is an introductory book on programming, and the atomic/nonatomic option relates to a relatively advanced topic known as multithreading. Here is what you need to know: the nonatomic option will make your setter method run a tiny bit faster. If you look at the headers for Apple’s UIKit, every property is marked as nonatomic. You should make your properties nonatomic, too.

(I give this advice to everyone. In every group, however, there is someone who knows just enough to be a pain. That person says, But when I make my app multithreaded, I’ll need the protection that atomic setter methods get me. And I should say, I don’t think you will write multithreaded code anytime soon. And when you do, I don’t think atomic setter methods are going to help. But what I really say is OK, then you should leave your setters atomic. Because you can’t tell someone something they aren’t ready to hear.)

In Appliance.h, make your accessors non-atomic:

@​p​r​o​p​e​r​t​y​ ​(​c​o​p​y​,​ ​n​o​n​a​t​o​m​i​c​)​ ​N​S​S​t​r​i​n​g​ ​*​p​r​o​d​u​c​t​N​a​m​e​;​
@​p​r​o​p​e​r​t​y​ ​(​n​o​n​a​t​o​m​i​c​)​ ​i​n​t​ ​v​o​l​t​a​g​e​;​

Sadly, at this time, the default for properties is atomic, so you do have to make this change.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset