CMSC 498i
CMSC 498i
Understanding Objective–C objects
In an attempt to help everyone get comfortable with Objective–C, I thought would be useful to elaborate on some of the issues I think people were having in the lab, and to make some comparisons between Objective-C and C++ to show comparisons of uses that you may be more familiar with seeing.
Lets assume we’ve been asked to implement a “RationalNumber” class. One could implement this in C++ as follows
First, you need to declare the class in a “.h”, specifying things like inheritance, constructors, and member variables / functions. Here we see a declaration of a RationalNumber class that inherits from some “Value” class. There are 2 member variables to store the numerator and denominator. There are 2 property getters, and one constructor.
// RationalNumber.h
class RationalNumber : public Value
{
private:
int m_numerator;
int m_denominator;
public:
RationalNumber(int num, int denom);
int getNumerator();
int getDenominator();
};
This could be equivalently defined in ObjC as follows (also in a “.h”). Note that I’m using NSInteger when int would have sufficed. It’s nice to use NSInteger anywhere you would have used an int. This will help maintain compatibility with other Cocoa APIs. If you dig a little bit, you’ll discover that NSInteger is really just a integer that is 32-bits wide on 32 bit systems, and 64-bits wide on 64 bit systems. Also note that I named my ‘getters’ -numerator, instead of -getNumerator. Typically Cocoa getter methods do not use the prefix “get’ in their method names.
// RationalNumber.h
@interface RationalNumber : Value
{
@private
NSInteger numerator_;
NSInteger denominator_;
}
- (id)initWithNumerator:(NSInteger)numerator denominator:(NSInteger)denominator;
- (NSInteger)numerator;
- (NSInteger)denominator;
@end
So now lets take a look at the implementation of these. First, C++
// RationalNumber.cpp
#include "RationalNumber.h"
RationalNumber::RationalNumber(int num, int denom) {
m_numerator = num;
m_denominator = denom;
}
RationalNumber::getNumerator() {
return m_numerator;
}
RationalNumber::getDenominator() {
return m_denominator;
}
In Objective-C, this would look like this
// RationalNumber.m
#import "RationalNumber.h"
@implementation RationalNumber
- (id)initWithNumerator:(NSInteger)numerator denominator:(NSInteger)denominator {
if ((self = [super init])) {
numerator_ = numerator;
denominator_ = denominator;
}
return self;
}
- (NSInteger)numerator {
return numerator_;
}
- (NSInteger)denominator {
return denominator_;
}
@end
Now, if we’d like to make use of Objective-C 2.0 properties, we can reduce the amount of code we have to write. It doesn’t seem like much of a win here, but in general it will. Writing setters and getters can be tedious. And depending on the level of thread safety you want to achieve, it can actually be subtle to really get correct. @property / @synthesize will help you out. Think of @property as a just a more formal way to declare your setters and getters. Think of @synthesize as free code! When you use @synthesize, you have the option to delete your setter and getter implementation, and let the compiler take care of it behind the scenes for you! Using @property, the header now looks like this:
@interface RationalNumber : Value
{
@private
NSInteger numerator_;
NSInteger denominator_;
}
- (id)initWithNumerator:(NSInteger)numerator denominator:(NSInteger)denominator;
@property (assign, readonly) NSInteger numerator;
@property (assign, readonly) NSInteger denominator;
@end
If we use @synthesize, our implementation file will end up looking like this
// RationalNumber.m
#import "RationalNumber.h"
@implementation RationalNumber
@synthesize numerator = numerator_;
@synthesize denominator = denominator_;
- (id)initWithNumerator:(NSInteger)numerator denominator:(NSInteger)denominator {
if ((self = [super init])) {
numerator_ = numerator;
denominator_ = denominator;
}
return self;
}
@end
Random Information
Copying
when you want to make a copy of an object, you would go ahead and call [object copy]. So, why are you implementing - (id)copyWithZone:(NSZone *)zone ? It turns out that NSObject has an implementation of -copy that will call -copyWithZone:. Don’t worry too much about the reasons and in fact you can ignore the zone parameter (it is mostly of historical importance). All you need to remember is that when you implement copy support, the method is –(void)copyWithZone:(NSZone *)zone, and when you are asking for a copy of another object, you send it the -copy message. Here is an example. We’ll add copy support to our rational number class.
First, declare conformance to the NSCopying protocol
@interface RationalNumber : Value < NSCopying >
Now that you’ve done that, the compiler will complain if you don’t implement -copyWithZone:, so lets do that
- (id)copyWithZone:(NSZone *)zone {
RationalNumber *number = [[RationalNumber alloc] initWithNumerator:[self numerator]
denominator:[self denominator]];
return number;
}
Now, in our other code, we can ask for a copy of the RationalNumber.
- (void)someMethod {
RationalNumber *number = [[RationalNumber alloc] initWithNumerator:10 denominator:5];
RationalNumber *copy = [number copy];
// ... do stuff...
[copy release];
copy = nil;
[number release];
number = nil;
}
Class Methods – Often in Cocoa, you will see some methods declared with a “+” in front of it. Those designate class methods. Keep in mind, those methods are not associated with any particular instance and therefor do not have access to any instance variable data.
Factory methods – One very common type of class method you will see is a factory method. Usually these methods take a couple of parameters and return to you an auto released instance of the object you’d like to have. For the most part, you’ll find these methods are pure convenience that ease in the set up, or simply allow you to not have to do an autorelease yourself. For example, lets say we want to add a convenience factory method to the RationalNumber class. The purpose of the method is to create and return to us a RationalNumber instance that is autoreleased and complete configured and ready to use.
We first declare the method in the “.h” header file
+ (RationalNumber *)numberWithNumerator:(NSInteger)num denominator:(NSInteger)num;
Then we implement the method in the “.m” implementation file.
+ (RationalNumber *)numberWithNumerator:(NSInteger)num denominator:(NSInteger)num {
return [[[RationalNumber alloc] initWithNumerator:num denominator:num] autorelease];
}
Notice all the groovy objective-c message sending going on? This is something that seems to trip up and confuse folks new to Objective-C. To help clarify what is going on, we’ll rewrite the factory method as the following equivalent code. Keep in mind that alloc creates memory for an object and returns the un–initialized object. -init methods initialize the receiver and by convention return the receiving object so that we can nest objective-c message like this. Finally, autorelease schedules a release, and then returns the receiving object as a convenience since often autorelease is called right before returning an object from a method implementation. By doing this, we can return an object and autorelease it all on one line. Long story short, this is equivalent code:
+ (RationalNumber *)numberWithNumerator:(NSInteger)num denominator:(NSInteger)num {
RationalNumber *number = [RationalNumber alloc];
number = [number initWithNumerator:num denominator:num];
number = [number autorelease];
return number;
}
Documentation – Start taking a look at documentation as you work on the first project and all subsequent projects. You’ll probably spend more time reading documentation of methods and classes than anything else. I almost never write any code without having the documentation up for reference. Check out NSObject for example: http://developer.apple.com/mac/library/documentation/cocoa/reference/Foundation/Classes/NSObject_Class/Reference/Reference.html. Typically what you will want to do is visit the reference for the class you are working with and reading 2 things to get started. The conceptual overview at the beginning will give you a big picture view of the class. Just after that section will be a dense listing of the available methods for a class. These are organized functionally so you can quickly get an overview of all the capabilities of a class. As we move on during the semester, you’ll sometimes want to reference the documentation in the super class to see what inherited functionality an object has.
The docs are very good. Please use them as much as you can.
Newbie – Implementing your own Rational class
Lets suppose you are an experienced C programmer and want to implement your first objective-C class. What might it look like? What would a veteran Objective-C programmer do differently! Some of us tried just that. Follow this link to see an example of the difference between a C programmers first attempt and what the code would look “the objective-c way”. Follow this link.
Posted: Chuck Pisula – 2/19/10