1.23. Allocating and Making Use of Arrays

Problem

You want to store a series of objects into another object for later use.

Solution

Use NSArray and NSMutableArray classes to store objects into arrays that are fixed and that you can change, respectively.

Discussion

An object of type NSArray or any of its subclasses has the capability to store n number of other objects. These objects can then be accessed using their index. For instance, let’s say you have 10 pairs of socks. Now imagine placing them all on a flat surface from left to right and calling them socks 1, socks 2, socks 3, etc. So the leftmost sock is now addressed as socks 1, the one next to it is called socks 2, and so on. Isn’t that easier than saying something like “the blue socks next to my red socks”? That’s exactly what arrays do: they make arranging items much easier.

Note

You can place any object of type NSObject or any of its subclasses into an array of type (or subclasses of) NSArray.

The primary difference between NSArray and NSMutableArray is that a mutable array can be changed/modified after it has been allocated and initialized, whereas an immutable array, NSArray, cannot.

Let’s have a look at an example. Let’s create an instance of NSString and two instances of NSNumber and place them in an immutable array:

NSString *stringObject = @"My String";
NSNumber *signedNumber = [NSNumber numberWithInteger:-123];
NSNumber *unsignedNumber = [NSNumber numberWithUnsignedInteger:123];

NSArray *array = [[NSArray alloc] initWithObjects:
                  stringObject,
                  signedNumber,
                  unsignedNumber, nil];

NSLog(@"array = %@", array);

When you run this program, you get the following text printed to your console:

array = (
    "My String",
    "-123",
    123
)

As you can see, we used the initWithObjects: initializer of the array. When using this initializer, pass your objects that need to be placed inside the array, one by one. At the end, terminate the list with a nil so that the runtime knows when you are terminating your list. If you don’t do so, the LLVM Compiler will throw a warning similar to this:

warning: Semantic Issue: Missing sentinel in method dispatch

We can also use the arrayWithObjects: class method of NSArray to create an autorelease array, like so:

NSArray *array = [NSArray arrayWithObjects:
                  stringObject,
                  signedNumber,
                  unsignedNumber, nil];

You can call the count method on your array to get the number of objects in that array. You can go through your array using a for loop or using an enumerator. Let’s have a look at the solution with a for loop first:

NSArray *array = [NSArray arrayWithObjects:
                  stringObject,
                  signedNumber,
                  unsignedNumber,nil];

NSUInteger counter = 0;
for (counter = 0;
     counter < [array count];
     counter++){

  id object = [array objectAtIndex:counter];
  NSLog(@"Object = %@", object);

}

And here is the output:

Object = My String
Object = -123
Object = 123

As you can see, we use the objectAtIndex: method to get an object at a specific index. Remember that indexes are zero-based. In other words, when the counter reaches -1, the loop has to stop because there can be no negative indexes in an array.

As mentioned before, you can also use fast enumeration to go through objects of an array. Fast enumeration is a language feature in Objective-C that allows you to enumerate objects in an array or dictionary (or any other object that supports fast enumeration) without having to use any counter or for loop. The format is as follows:

for (Type variableName in array/dictionary/etc){ ... }

Suppose we want to code the previous example without the overhead of a counter variable. Here is how we can do it using fast enumeration:

for (id object in array){
    NSLog(@"Object = %@", object);
  }

The results are practically identical to the results we got from the previous version of this code that used a counter variable.

Mutable arrays are very interesting. As you probably have already guessed, immutable arrays cannot be modified once allocated and initialized. Mutable arrays, however, can be modified after their allocation and initialization. Let’s have a look at an example:

NSString *stringObject = @"My String";
NSNumber *signedNumber = [NSNumber numberWithInteger:-123];
NSNumber *unsignedNumber = [NSNumber numberWithUnsignedInteger:123];

NSArray *anotherArray = [[NSArray alloc] initWithObjects:
                         @"String 1",
                         @"String 2",
                         @"String 3", nil];

NSMutableArray *array = [[NSMutableArray alloc] initWithObjects:
                         stringObject,
                         signedNumber, nil];

[array addObject:unsignedNumber];
[array removeObject:signedNumber];
[array addObjectsFromArray:anotherArray];

for (id object in array){
  NSLog(@"Object = %@", object);
}

Before we go into analyzing the code, let’s have a look at its output:

Object = My String
Object = 123
Object = String 1
Object = String 2
Object = String 3

You might be asking, “what just happened?”. Well, let’s have a look what methods of the NSMutableArray class we actually used:

addObject:

This method allows us to add an object to the end of a mutable array.

removeObject:

Using this method, we can remove a specific object from the array. Remember that we pass an object to this method, not an index of the object. To remove an object using an index into the array, we must use the removeObjectAtIndex: method.

addObjectsFromArray:

With this method, we can add objects from one array (either mutable or immutable) into our mutable array.

Warning

Please bear in mind that during fast enumeration of a mutable array, you must not add to or remove anything from that array or you will get a runtime error. This is the default behavior of mutable arrays during fast enumeration.

If you are interesting in block objects (and we’ll see good reasons to be, later in the book!), you can also enumerate objects in your arrays using the enumerateObjectsUsingBlock: method. The block object passed to this method should:

  1. Return no value.

  2. Have three parameters:

    1. First parameter of type id, which will be the object being enumerated at each loop of enumeration.

    2. Second parameter of type NSUInteger, which will tell you the index of the current object being enumerated.

    3. Last but not least, a parameter of type *BOOL, which you can use to stop the enumeration. This is a pointer to a boolean variable which should be NO as long as you want the enumeration to proceed. You can change the value of this pointer to YES in order to stop the enumeration at any time. You would use this if you are looking for an object in an array and you would like to stop the enumeration as soon as you’ve found that object, since there is no point continuing the enumeration if you’ve already found your object.

  NSArray *myArray = [[NSArray alloc] initWithObjects:
                      @"String 1",
                      @"String 2",
                      @"String 3",
                      @"String 4", nil];

  [myArray enumerateObjectsUsingBlock:
   ^(__strong id obj, NSUInteger idx, BOOL *stop) {

     NSLog(@"Object = %@", obj);

  }];

If you need to sort an array, simply use the new block-based sorting methods of NSArray or NSMutableArray. Just remember that the sorting methods of NSArray return a new instance of NSArray and leave the original array intact, since NSArray cannot be modified (sorting can modify an array) after it has been allocated and initialized. This is in comparison to the sorting methods of NSMutableArray, where the original array will be the target of sorting and the sorting methods will not return a new array. Let’s look at sorting a mutable array:

NSMutableArray *myArray = [[NSMutableArray alloc] initWithObjects:
                      @"String 2",
                      @"String 4",
                      @"String 1",
                      @"String 3", nil];
  [myArray sortUsingComparator:
   ^NSComparisonResult(__strong id obj1, __strong id obj2) {

    NSString *string1 = (NSString *)obj1;
    NSString *string2 = (NSString *)obj2;
    return [string1 compare:string2];

  }];

  NSLog(@"myArray = %@", myArray);

The results will then be printed to the console, as follows:

myArray = (
    "String 1",
    "String 2",
    "String 3",
    "String 4"
)

So, what happened? We simply called the sortUsingComparator: method of our array. This method takes in a block object (marked by the initial ^ character) that has to return a value of type NSComparisonResult. This value can be any of the following:

NSOrderedSame

The two values being compared are equal.

NSOrderedAscending

The value on the left of the comparison is smaller than the value on the right. Think of it as this: transition from value 1 (left) to value 2 (right) is ascending, meaning that value 1 is smaller.

NSOrderedDescending

The value on the right is smaller than the value on the left. In other words, the transition from value 1 (left) to value 2 (right) is descending, meaning that value 1 is bigger than value 2.

So if we get String 3 as value 1 (left) and String 1 as value 2 (right), the sort function compares the two S characters and finds them the same, then the two t characters, and so on. Finally when the sort function reaches the 3 and the 1, it finds that 1 is lower than 3 in the UTF8String character set, and therefore that the second element is lower than the first.

The block object submitted to the sortUsingComparator: method takes two parameters:

First Object of type id

This is the first object in the comparison in each iteration.

Second Object of type id

This is the second object in the comparison in each iteration.

So when sorting the array, simply use a block-based approach. It’s the way Apple is pushing developers to go forward with their implementations, so it’s good to know about block objects.

Get iOS 5 Programming Cookbook now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.