Tuesday, April 13, 2010

Objective-C Tuesdays: 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 structs 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.

6 comments:

Mike said...

I think this is the best article in the series so far. Thank you.

Don McCaughey said...

Thanks! It's good to know people are getting something out of them!

Davide Bettio said...

Thanks for this great article!

Don McCaughey said...

Thanks!

Sarah-jane said...

Thanks!
Explains things so clearly.

prashanth said...

thanks a lot..
article is very good..
please explain each step after the program..