Properties Tutorial for iOS

Learn how to use properties in this properties tutorial, the third in a 3-part series on memory management. By Ray Wenderlich.

Leave a rating/review
Save for later
Share

Learn about Objective-C Properties from the ground up!

Learn about Objective-C Properties from the ground up!

Update 4/12/13: These days, you should probably be using Apple’s new Automatic Reference Counting (ARC) technology instead of doing manual memory management. For more details on ARC, check out our ARC tutorial.

This is the third article in a three-part series on working with memory in Objective-C on the iPhone.

In the first article in the Objective-C tutorial series, we covered how to manage memory in Objective-C by using instance variables and reference counting.

In the second article in the series, we covered how to check your apps for memory leaks or memory-related mistakes, via using Instruments and other helpers.

In this third and final article in the series, we’ll talk all about properties and Objective-C. We’ll talk about what properties are, how they work, and some simple rules that you can use to avoid most memory-related issues.

If you don’t have it already, download the sample project for the test application we’ve been building in this Objective-C tutorial series, which we’ll be using as a starting point.

Retain Your Memory

Let’s take a moment to review where we’re currently at with the project in terms of memory management.

There are currently two instance variables in RootViewController: _sushiTypes, and _lastSushiSelected.

@interface RootViewController : UITableViewController {
    NSArray * _sushiTypes;
    NSString * _lastSushiSelected;
}

@end

For _sushiTypes, the array is created with alloc/init in viewDidLoad, and released in viewDidUnload and dealloc:

// In viewDidLoad.  Afterwards, retain count is 1.
_sushiTypes = [[NSArray alloc] initWithObjects:@"California Roll", 
               @"Tuna Roll", @"Salmon Roll", @"Unagi Roll", 
               @"Philadelphia Roll", @"Rainbow Roll",
               @"Vegetable Roll", @"Spider Roll", 
               @"Shrimp Tempura Roll", @"Cucumber Roll",
               @"Yellowtail Roll", @"Spicy Tuna Roll",
               @"Avocado Roll", @"Scallop Roll",
               nil];

// In viewDidUnload and dealloc.  Afterwards, retain count is 0.
[_sushiTypes release];
_sushiTypes = nil;

For _lastSushiSelected, it’s set when the user selects a row in the table view, and it’s released either a) right before it’s set, or b) in dealloc.

// In tableView:didSelectRowAtIndexPath.  Releases any existing object first and then retains the new object.
[_lastSushiSelected release];
_lastSushiSelected = [sushiString retain];

// In dealloc
[_sushiTypes release];
_sushiTypes = nil;

This approach definitely works, but requires you to think carefully about what’s going on with memory each time you set these variables. So let’s see if there’s an easier way!

Get Chair, Set Coding

If you’re familiar with other languages such as Java or C#, you’re probably familiar with the concepts of getters and setters.

When you have an instance variable such as _sushiTypes, you often want another class to be able to access it. But it’s generally bad practice to allow classes to directly access instance variables, because it makes your code brittle.

So instead, you might make a method called “getSushiTypes” (or maybe just “sushiTypes” to save typing three characters) and a method named “setSushiTypes”. This is a bit nicer because later on you could change the name of the instance variable without breaking any other classes, or maybe add extra functionality into the methods (such as maybe logging out whenever anyone tries to get the variable).

Adding getters and setters like this can be useful for more than just external classes that want to use the variables – it can also be a handy way to centralize the memory management code. Let’s see what I mean by adding a getter and setter for these two variables.

First add the signatures for the getters and the setters to the bottom of RootViewController.h:

- (NSArray *)sushiTypes;
- (void)setSushiTypes:(NSArray *)sushiTypes;
- (NSString *)lastSushiSelected;
- (void)setLastSushiSelected:(NSString *)lastSushiSelected;

Then add the implementations at the bottom of RootViewController.m:

- (NSArray *)sushiTypes {
    return _sushiTypes;
}

- (void)setSushiTypes:(NSArray *)sushiTypes {
    [sushiTypes retain];
    [_sushiTypes release];
    _sushiTypes = sushiTypes;
}

- (NSString *)lastSushiSelected {
    return _lastSushiSelected;
}

- (void)setLastSushiSelected:(NSString *)lastSushiSelected {
    [lastSushiSelected retain];
    [_lastSushiSelected release];
    _lastSushiSelected = lastSushiSelected;
}

The getters are pretty self-explanatory – they just return the instance variables.

The setters increase the reference count of the passed-in variable, decrease the reference count of the old instance variable, and then set the instance variable to the passed-in variable. This way, the object correctly has a reference count for the object stored in the instance variable as long as it is set.

You might be wondering why the setters call retain/release and then set the instance variable, in that order. Taking the actions in this order protects you against the case where you’re setting a variable to the same object. If you’re not sure why, mentally step through the process of what would happen to see for yourself!

Notice how naming the instance variables with an underscore (rather than without one) made writing these methods kind of convenient. If we had named the instance variable “sushiTypes”, we couldn’t have named the parameter to setSushiTypes also “sushiTypes” because the names would have conflicted. It’s also a nice and easy way to tell at a glance when you’re using an instance variable and when you’re not.

Finally, note that these getters and setters are not thread-safe. But that isn’t a problem for this app as the getters/setters will only be accessed from the main thread.

Now That You’re Set Up, Get Using

Now that you have the new getters and setters, modify the code in the rest of the class to make use of them. Let’s start with sushiTypes:

// In viewDidLoad
self.sushiTypes = [[[NSArray alloc] initWithObjects:@"California Roll", 
               @"Tuna Roll", @"Salmon Roll", @"Unagi Roll", 
               @"Philadelphia Roll", @"Rainbow Roll",
               @"Vegetable Roll", @"Spider Roll", 
               @"Shrimp Tempura Roll", @"Cucumber Roll",
               @"Yellowtail Roll", @"Spicy Tuna Roll",
               @"Avocado Roll", @"Scallop Roll",
               nil] autorelease];

// In viewDidUnload and dealloc
self.sushiTypes = nil;

Calling “self.sushiTypes = xxx” is exactly the same as calling “[self setSushiTypes:xxx]” – the dot notation just “looks better” to me.

So basically, instead of setting the _sushiTypes instance variable directly, we’re now going through the setter. Recall that the setter increments the reference count for what you pass in by 1. So we can’t just pass in the result of alloc/init anymore (because otherwise the reference count would be 2, which is incorrect) – we have to call autorelease at the end.

In viewDidUnload and dealloc, instead of calling release and setting to nil manually we can just use the setter. If you substitute nil into setSushiTypes you’ll see why:

[nil retain];  // Does nothing
[_sushiTypes release];
_sushiTypes = nil;

By the way, so you’re aware – some people say “never use getters/setters in dealloc or init”. They say this because since getters/setters are just arbitrary functions, they could contain side effects and mess things up. But I say – if you wrote them and know they are OK – use them if it simplifies your code! Which is definitely does here IMHO.

Now modify the code to make use of the setters for lastSushiSelected:

// In tableView:didSelectRowAtIndexPath, delete the two lines about _lastSushiSelected and replace with:
self.lastSushiSelected = sushiString;

// In dealloc
self.lastSushiSelected = nil;

Wow – that was a lot less having to worry about memory, wasn’t it? The setters contained all of the code to take care of the memory management in one spot.