`
janedoneway
  • 浏览: 568878 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

Using Properties in Objective-C Tutorial

 
阅读更多

From: http://www.raywenderlich.com/2712/using-properties-in-objective-c-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 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 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.

A Simple Proposition

So writing getters and setters can be useful to give other classes access to your instance variables, and also sometimes make managing memory easier.

But writing these methods over and over again can be a royal pain! Nobody likes doing the same thing over and over, so Objective-C has a really useful feature to help with this: properties.

We can replace all of the getter/setter code you wrote earlier with just a few lines! Try it out to see for yourself. Go to RootViewController.h, remove the getter and setter prototypes and replace it with this:

@property (nonatomic, retain) NSArray * sushiTypes;
@property (nonatomic, retain) NSString * lastSushiSelected;

This is step one one of using properties: to create the property declarations.

A property declaration starts with the @property keyword. Then in parenthesis you put the property attributes (which we’ll discuss in a second). Finally, you give the type and name of the property.

Property attributes are special keywords to tell compiler how to generate the getters and setters. Here you specify two property attributes: nonatomic, which tells the compiler not to worry about multithreading, and retain, which tells the compiler to retain the passed-in variable before setting the instance variable.

In other situations, you might want to use the “assign” property attribute instead of retain, which tells the compiler NOT to retain the passed-in variable. Or perhaps the “copy” property attribute, which makes a copy of the passed-in variable before setting.

Ok! To finish using the properties, switch over to RootViewController.m, delete the getters and setters you wrote earlier, and add this to the top of the file:

@synthesize sushiTypes = _sushiTypes;
@synthesize lastSushiSelected = _lastSushiSelected;

This line is the line that instructs the compiler to automatically create the getter and setter code based on the property definitions that you added in the header file. You start with the @synthesize keyword, then give the name of the property, then (if it has a different name) tell it the instance variable that should back the property.

And that’s it! Compile and run the code, and it should work just as before. But if you compare your code now to what you started with, I think you’ll agree that it’s a little bit easier to understand, and less error prone.

Plus you just learned about properties and how they work!

Synthesize A Strategy

I’d like to wrap up the article with a little strategy that I sometimes like to use to simplify memory management in Objective-C.

If you follow these rules, you’ll keep yourself out of memory-related trouble most of the time. Of course, blindly following rules is not a substitute for understanding what’s going on, but it can make things simpler and help you avoid mistakes, especially when you are first starting.

I’ll list the rules out first, and then we’ll have a discussion of each one.

  1. Always make a property for every instance variable.
  2. If it’s a class, mark it with the retain attribute. Otherwise, mark it with the assign attribute.
  3. Whenever creating a variable, use the alloc/init/autorelease idiom.
  4. Whenever setting a variable, always use “self.xxx = yyy” (in other words, use the property).
  5. For each of your instance variables, call “self.xxx = nil” in dealloc. If it’s an outlet or something you created in viewDidLoad, do the same in viewDidUnload.

Ok, now onto the discussion!

For rule #1: By making an instance variable for each property, you can let the compiler write the memory management code for you. The drawback is it doesn’t keep the private data of your class well encapsulated so can lead to more connected code if you are not careful.

For rule #2: By retaining variables whenever they are set, you can make sure that you can access your instance variables at any time.

For rule #3: When you create a variable, you want to use the alloc/init/autorelease idiom (like you can see creating the NSArray with the sushiTypes earlier). This way, the memory will be freed up for you automatically later on. If you need to keep the variable around long-term, you should assign it to a property, put it in an array, or the like to increment the reference count.

For rule #4: By using the self.xxx syntax (i.e. using the property) whenever you set a variable, you’ll go through the properties and hence make sure to release the old variable/retain the new variable. Note some programmers worry about using getters/setters/properties in initializers and dealloc, but I don’t think it’s something to worry about if you wrote & understand the code.

For rule #5: Calling “self.xxx = nil” will go through your properties and decrement the reference count. Don’t forget about viewDidUnload!

Simplified Strategy for Cocos2D

There’s a lot of rabid Cocos2D fans on this blog, so wanted to add a special section for you guys ;]

The above strategy is usually overkill for Cocos2D, because most of the time your instance variables are just handy references to objects you want to use a lot in your current layer. And Cocos2D will keep a retain count on these objects as long as they are currently in the layer.

So to avoid making unnecessary properties for everything, here’s what I like to use for Cocos2D:

  1. Don’t use properties at all.
  2. Set instance variables directly to your created sprites, etc.
  3. Since they’ll be children of the current layer, Cocos2D will keep the retain count up as long as they’re there.
  4. When you remove the object from the layer, set the instance variable to nil.

I just personally find it simpler and faster to develop this way.

Note this strategy doesn’t work if you want to keep track of an object not currently in the layer – you’ll have to use an alternative strategy there.

Where To Go From Here?

Here is the updated sample project that we developed in the above tutorial, now using properties.

If you have any questions about properties or memory management in general, feel free to leave a question in the forums below! Also please chime in if you have any different memory management strategies, tips, or advice that may be useful to other developers.

That about wraps up this tutorial series on memory management. Hopefully it has helped take a bit of the mystery out of memory management in Objective-C!

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics