Show Menu

Do you have a job opening?

Submit your 30 day Job Listing for FREE

mouse cursor hand pointer icon templates for designers

Ah, pointers. Nothing strikes more fear into the heart of novice C programmers, or developers who aren’t used to them. Add to that the notion of double pointers (**) and address-of operators (&) and you are pretty much guaranteed to scare them away. But they are really not that hard, as long as you understand 3 things:

  • A pointer contains a memory address value
  • A pointer has a memory address of its own
  • All parameters, including pointers, are copied with every method call

What are pointers anyway? Why do we need them? First we need to talk about data types. In C-based languages like Objective-C there essentially two main types of data: primitives (also known as value types) and objects (also known as reference types). There are only a handful of primitives out there, and they differ slightly by operating system. Here are the basic ones:

  • boolean (true or false)
  • short (a small number without a decimal point)
  • integer (a number without a decimal point)
  • long (a big number without a decimal point)
  • float (a number with a decimal point)
  • decimal (a big number with a decimal point)
  • char (an alphanumeric character)

That’s pretty much it! Just letters and numbers. What do they all have in common? They are relatively small, finite chunks of data. Because of that, their memory is allocated on the stack — the in-memory set of commands executed by a program. You may have also heard of structs. Structs, short for “structures,” are groupings of various primitive data types. They can’t run any code. They are still pretty small, and their memory is also allocated on the stack.

Objects, on the other hand, allow for much more complexity. Objects are like structs in that they group of various primitive data types (“properties”) to support some sort of real-world abstraction, like “Car”. However, they also contain code that can be executed (“methods”). Because of this, objects have substantially larger memory footprints. It would be inefficient and impractical to store them on the stack. So, we simply store them somewhere else and just keep a reference to them on the stack (except for block objects). We store their actual data in an area of memory called the “heap” and just keep a “pointer” to them on the stack. A pointer is nothing more than a primitive data type that contains the address of a block of memory. There’s nothing magical about them. A sample memory address formatted as a string looks like 0x00072A4C on 32-bit systems.

Now, here is what most programmers forget. In C, when a method is called, its instructions are placed onto the stack, and its parameters are copied along with it (this is called “passing by value”). This means that when you invoke a method, the parameters it receives are physically different in memory from the values sent from the caller. What does that mean for our programs? Well, when working with primitives, the underlying values are the same, so this behavior is essentially transparent. The following program would print the number 8, as expected:

int x = 5;
int y = 3;
int z = [self add:x and:y];
NSLog(@"%d", z);

-(int) add:(int)a and:(int)b
{
  return a + b;
}

But what about pointers? Remember, a pointer contains a real memory address value. But it also has a memory address of its own! When a pointer parameter is copied into a method call, the new pointer gets a new memory address, but still “points” to the same object. The following example prints the number 2, as expected.

Foo* foo = [[Foo alloc] init];
foo.someProperty = 1;
[self doSomething:foo];
NSLog(@"%d", foo.someProperty);

-(void) doSomething:(Foo*)myFoo
{
  myFoo.someProperty = 2;
}

But what if a method wants to change an input pointer to point to a different object? There is a big problem: it can only change its copied pointer, not the original pointer! The following example prints the number 1, which may be counter-intuitive:

Foo* foo = [[Foo alloc] init];
Foo.someProperty = 1;
[self doSomething:foo];
NSLog(@"%d", foo.someProperty);

-(void) doSomething:(Foo*)myFoo
{
  myFoo = [[Foo alloc] init];
  myFoo.someProperty = 2;
}

We can work around this by adding an additional “level of indirection.” What if instead of passing the original pointer as a parameter, we instead pass a pointer to it? A “double pointer” so to speak (this is called “passing by reference”). That way after the double pointer is copied into the receiving method, we can still resolve it to the original pointer that we want! This program will now print the number 2:

Foo* foo = [[Foo alloc] init];
foo.someProperty = 1;
[self doSomething:&foo];
NSLog(@"%d", foo.someProperty);

-(void) doSomething:(Foo**)myFoo
{
  *myFoo = [[Foo alloc] init];
  *myFoo.someProperty = 2;
}

Notice the slightly different syntax. First, we change the method to accept a double pointer to a Foo object. Then, inside the method we “dereference” the double pointer in order work with it as a normal pointer. We do this using an asterisk. Finally, in the calling code, we pass the address of the Foo pointer into the method. We do this using the address-of operator (&).

Cases like this are not a rare occurrence. We see them often with the NSError class, for example. Apple’s preferred way for methods to communicate errors to their callers is to accept a double pointer to an NSError object. Inside the method body, we dereference it to get to the original pointer, and initialize it to a new NSError object. The calling code now has access to this new NSError object.

Foo* foo;
NSError* error;
[self doSomething:foo error:&error];

-(void) doSomething:(Foo*)myFoo error:(NSError**)myError
{
  if(!myFoo)
  {
    *myError = [NSError errorWithDomain:@"Foo" code:-1
      userInfo:@{ NSLocalizedDescriptionKey:@"myFoo cannot be nil" }];
  }
}

So, most of the time you can work with reference type parameters in a method just as you do primitive types.

However, the rule of thumb is that to change the caller’s pointer to point to a different object, you have to use a double pointer. The syntax is a bit tricky but you can always re-trace your steps as long as you remember those 3 basic rules.

Hopefully this helps alleviate some of the confusion with double pointers in Objective-C!

having issues?

We have a Questions and Answer section where you can ask your iOS Development questions to thousands of iOS Developers.

Ask Question

FREE Download!

Get your FREE Swift 2 Cheat Sheet and quick reference guide PDF download when you sign up to SwiftMonthly


Sharing is caring

If you enjoyed this tutorial, please help us and others by sharing using one of the social media buttons below.


Written by:

Martin is a New York area software developer and MBA with 10+ years of experience on the Microsoft stack. Over the past few years he has also expanded into iOS development using native Objective-C. he architects and develops full-stack web applications, iOS apps, database systems, and backend services.

Comments

comments