Instance Variables
Last time we talked about
local
variables and function parameters. Today we will look at instance
variables of Objective-C classes and we'll touch on methods as well.
Objective-C classes are defined by an @interface
section
and an @implementation
section. The
@interface
section declares the instance variables and
looks like this:
@interface Cat : NSObject {
NSString *name;
int age;
}
- (id)initWithName:(NSString *)aName
andAge:(int)anAge;
// ...
@end
The instance variables (or ivars) are declared between the
curly braces, after the class name and superclass but before method
declarations. Instance variables are declared like any local or global
variable, but have a different scope. By default, instance variables
are visible in all instance methods of a class and its subclasses.
(Instance methods are the ones that begin with a minus sign.) The
implementation of the -init
method might look like this:
// example of using instance variables
@implementation Cat
- (id)initWithName:(NSString *)aName
andAge:(int)anAge
{
self = [super init];
if ( ! self) return nil;
name = [aName copy];
age = anAge;
return self;
}
// ...
@end
This is a pretty standard Objective-C -init
method. Note
that unlike JavaScript, Python, Ruby and many other languages, there's
no need to prefix instance variables with this
,
self
, @
or something similar. But this leads
to a problem: what if a method parameter has the same name as an
instance variable? If our -init
method was defined like
this:
// example of name shadowing
- (id)initWithName:(NSString *)name
andAge:(int)age
{
self = [super init];
if ( ! self) return nil;
name = [name copy]; // causes a warning
age = age; // causes a warning
return self;
}
If you compile this code, you'll see warnings like local
declaration of 'name' hides instance variable
. You should heed
these warnings! Code like
age = age;
assigns parameter age
to itself, leaving the instance
variable age
unchanged.
Unfortunately there's no elegant way to deal with parameters shadowing
instance variables. Objective-C does allow you to use the "pointer to
member" or "arrow" (->
) operator on self
to
access instance variables, like this:
// example of pointer to member
- (id)initWithName:(NSString *)name
andAge:(int)age
{
self = [super init];
if ( ! self) return nil;
self->name = [name copy]; // still causes a warning
self->age = age; // still causes a warning
return self;
}
If you know C++ or Java, you would think this would work, but the
Objective-C compiler still produces a warning. This is probably one of
the most niggling little differences between Objective-C and other
languages that you just need to get over. The only practical solution
is make sure that parameter names don't clash with instance variables.
For initializers and setters, most Objective-C programmers simply
prefix their parameter names with "a
", "an
"
or "the
". It's not very elegant, but it works. If you're
tempted to simply prefix your instance variable names with an
underscore, I recommend against that. Apple uses the leading
underscore convention in framework classes in order to prevent name
clashes when application writers have to extend framework classes.
The struct
behind the curtain
If you're familiar with C or C++, seeing an expression like
self->name
should give you a clue to the inner workings of
Objective-C. Underneath, Objective-C objects are pretty much just
struct
s and functions. The @interface
of our
Cat
class:
@interface Cat : NSObject {
NSString *name;
int age;
}
// ...
@end
becomes a struct
that looks something like:
// pseudocode for struct generated for Cat class
struct Cat {
Class isa; // inherited from NSObject
NSString *name;
int age;
};
The instance variables you define are tacked on to those defined by the
superclass, and its superclass and so on. So if we defined a subclass
of Cat
:
@interface LolCat : Cat {
UIImage *picture;
NSString *caption;
int upVotes;
}
@end
the Objective-C compiler would generate a structure in memory that
looked something like:
// pseudocode for struct generated for LolCat class
struct LolCat {
Class isa; // inherited from NSObject
NSString *name; // inherited from Cat
int age; // inherited from Cat
UIImage *picture;
NSString *caption;
int upVotes;
};
Similarly, Objective-C methods are simply regular C functions
underneath with extra parameters automatically added by the compiler.
So our -init
method
- (id)initWithName:(NSString *)aName
andAge:(int)anAge
{
// ...
}
is compiled into something that resembles:
// pseudocode for function generated for -initWithName:andAge: method
id initWithName_andAge(id self, SEL _cmd, NSString *aName, int anAge) {
// ...
}
The parameters self
and _cmd
are added to
each instance method by the compiler. Naturally, self
is
a pointer to the memory for the instance, organized like a
struct
as we've shown. The _cmd
parameter
holds the method selector (which is basically the method name)
and can be used to do very crazy dynamic stuff we won't dive into today.
Instance variable scope
We mentioned earlier that by default, instance variables are visible in
all instance methods of a class and its subclasses. This is referred
to as protected scope in Objective-C. You can change the
scope of instance variables to be private, public or
package as well as protected, but in general these other
scopes aren't used very frequently in Objective-C. Going back to our
Cat
example, you would use the scope specifiers like this:
@interface Cat : NSObject {
// protected by default
double weight;
@private
int lives;
@protected
int age;
@public
NSString *name;
@package
UIColor *color;
}
// ...
@end
Private scope restricts visibility of the instance variable to the
class it's defined in; subclasses are not allowed to use it. Protected
is the default if you don't specify a scope, and allows all subclasses
to read and write to the instance variable. Public scope is rarely
seen in Objective-C; the pointer to member or arrow (->) operator is
used to access public instance variables:
// accessing public instance variables
Cat *cat = [[Cat alloc] init];
NSLog(@"The cat's name is %@", cat->name);
Package scope is used by framework creators; it makes the instance
variable public within the framework and private outside it.
Properties
Since instance variables aren't visible outside classes by default,
most Objective-C programmers create getters and setters when they want
to expose instance variables. Before Objective-C 2.0, these were
written by hand for each instance variable:
// getter and setter declarations
@interface Cat : NSObject {
NSString *name;
int age;
}
- (NSString *)name;
- (void)setName:(NSString *)aName;
- (id)age;
- (void)setAge:(int)anAge;
// ...
@end
The normal Objective-C convention is to give the getter the same name
as the instance variable and to prefix the setter with "set". The
implementation of simple setters is boilerplate but not trivial due to
the need to manage retain counts:
// getter and setter definitions
@implementation Cat
- (NSString *)name {
return name;
}
- (void)setName:(NSString *)aName {
if (name != aName) {
[name release];
name = [aName copy];
}
}
// ...
@end
Writing setters manually is both laborious and error prone, so
Objective-C 2.0 introduced properties, allowing getters and setters to
be generated automatically. Rewriting the Cat
class using
properties:
// property declaration
@interface Cat : NSObject {
NSString *name;
int age;
}
@property(copy, nonatomic) NSString *name;
@property(assign, nonatomic) int age;
// ...
@end
And its implementation:
// property implementation
@implementation Cat
@synthesize name, age;
// ...
@end
Which is much better. Properties can be called in two ways. Dot
notation is the most terse and similar to many other languages:
// property dot notation example
Cat *cat = [[Cat alloc] init];
NSLog(@"The cat is %d years old", cat.age);
cat.name = @"Ritz";
But you can use normal method calls as well:
// property method call example
Cat *cat = [[Cat alloc] init];
NSLog(@"The cat is %d years old", [cat age]);
[cat setName:@"Ritz"];
These two examples are exactly equivalent. Dot notation is simply
syntactic sugar for getter and setter method calls.
Which is which?
While it's very convenient, property dot notation makes Objective-C a
little confusing at first. You will sometimes see properties and
instance variables mixed together in the same method:
- (void)celebrateBirthday {
age++; // instance variable
if (age > 9) {
self.name = // property
[NSString stringWithFormat:@"Old %@",
name]; // instance variable
}
}
At first glance, name
and self.name
don't
seem that different but they are. We'll examine those differences, and
look more at
properties,
next time.