O'Reilly logo

iOS 7 Programming Cookbook by Vandad Nahavandipoor

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

1.0. Introduction

iOS 7 has introduced a lot of new features to users, as well as tons of new APIs for us programmers to use and play with. You probably already know that the user interface has drastically changed in iOS 7. This user interface had stayed intact all the way from the first version of iOS till now, and because of this, many apps were coded on the assumption that this user interface would not ever change. Graphic designers are now faced with the challenge of creating the user interface and thinking about the user experience in a way that makes it great for both pre- and post-iOS 7 user interfaces (UIs).

In order to write apps for iOS 7, you need to know some of the basics of the Objective-C programming language that we will use throughout this book. Objective-C, as its name implies, is based on C with extensions that allow it to make use of objects. Objects and classes are fundamental in object-oriented programming (OOP) languages such as Objective-C, Java, C++, and many others. In Objective-C, like any other object-oriented language (OOL), you have not only access to objects, but also to primitives. For instance, the number –20 (minus twenty) can be expressed simply as a primitive in this way:

NSInteger myNumber = -20;

This simple line of code will define a variable named myNumber with the data type of NSInteger and sets its value to 20. This is how we define variables in Objective-C. A variable is a simple assignment of a name to a location in memory. In this case, when we set 20 as the value of the myNumber variable, we are telling the machine that will eventually run this piece of code to put the aforementioned value in a memory location that belongs to the variable myNumber.

All iOS applications essentially use the model-view-controller (MVC) architecture. Model, view, and controller are the three main components of an iOS application from an architectural perspective.

The model is the brain of the application. It does the calculations and creates a virtual world for itself that can live without the views and controllers. In other words, think of a model as a virtual copy of your application, without a face!

A view is the window through which your users interact with your application. It displays what’s inside the model most of the time, but in addition to that, it accepts users’ interactions. Any interaction between the user and your application is sent to a view, which then can be captured by a view controller and sent to the model.

The controller in iOS programming usually refers to the view controllers I just mentioned. Think of a view controller as a bridge between the model and your views. This controller interprets what is happening on one side and uses that information to alter the other side as needed. For instance, if the user changes some field in a view, the controller makes sure the model changes in response. And if the model gets new data, the controller tells the view to reflect it.

In this chapter, you will learn how to create the structure of an iOS application and how to use views and view controllers to create intuitive applications.

Note

In this chapter, for most of the user interface (UI) components that we create, we are using a Single View Application template in Xcode. To reproduce the examples, follow the instructions in Creating and Running Our First iOS App. Make sure that your app is universal, as opposed to an iPhone or iPad app. A universal app can run on both iPhone and iPad.

Creating and Running Our First iOS App

Before we dive any deeper into the features of Objective-C, we should have a brief look at how to create a simple iOS app in Xcode. Xcode is Apple’s IDE (integrated development environment) that allows you to create, build, and run your apps on iOS Simulator and even on real iOS devices. We will talk more about Xcode and its features as we go along, but for now let’s focus on creating and running a simple iOS app. I assume that you’ve already downloaded Xcode into your computer from the Mac App Store. Once that step is taken care of, please follow these steps to create and run a simple iOS app:

  1. Open Xcode if it’s not already open.

  2. From the File menu, choose New Project...

  3. In the New Project window that appears, on the lefthand side under the iOS category, choose Application and then on the righthand side choose Single View Application. Then press the Next button.

  4. On the next screen, for the Product Name, enter a name that makes sense for you. For instance, you can set the name of your product as My First iOS App. In the Organization Name section, enter your company’s name, or if you don’t have a company, enter anything else that makes sense to you. The organization name is quite an important piece of information that you can enter here, but for now, you don’t have to worry about it too much. For the Company Identifier field, enter com.mycompany. If you really do own a company or you are creating this app for a company that you work with, replace mycompany with the actual name of the company in question. If you are just experimenting with development on your own, invent a name. For the Devices section, choose Universal.

  5. Once you are done setting the aforementioned values, simply press the Next button.

  6. You are now being asked by Xcode to save your project to a suitable place. Choose a suitable folder for your project and press the Create button.

  7. As soon as your project is created, you are ready to build and run it. However, before you begin, make sure that you’ve unplugged all your iOS devices from your computer. The reason behind this is that once an iOS device is plugged in, by default, Xcode will attempt to build and run your project on the device, causing some issues with provisioning profiles (which we haven’t talked about yet). So unplug your iOS devices and then press the big Run button on the top-lefthand corner of Xcode. If you cannot find the Run button, go to the Product menu and select the Run menu item.

Voilà! Your first iOS app is running in iOS Simulator now. Even though the app is not exactly impressive, simply displaying a white screen in the simulator, this is just the first step toward our bigger goal of mastering the iOS SDK, so hold on tight as we embark on this journey together.

Defining and Understanding Variables

All modern programming languages, including Objective-C, have the concept of variables. Variables are simple aliases to locations in the memory. Every variable can have the following properties:

  1. A data type, which is either a primitive, such as an integer, or an object

  2. A name

  3. A value

You don’t always have to set a value for a variable, but you need to specify its type and its name. Here are a few data types that you will need to know about when writing any typical iOS app:

Mutable Versus Immutable

If a data type is mutable, you can change if after it is initialized. For instance, you can change one of the values in a mutable array, or add or remove values. In contrast, you must provide the values to an immutable data type when you initialize it, and cannot add to them, remove them, or change them later. Immutable types are useful because they are more efficient, and because they can prevent errors when the values are meant to stay the same throughout the life of the data.

NSInteger and NSUInteger

Variables of this type can hold integral values such as 10, 20, etc. The NSInteger type allows negative values as well as positive ones, but the NSUInteger data type is the Unsigned type, hence the U in its name. Remember, the phrase unsigned in programming languages in the context of numbers always means that the number must not be negative. Only a signed data type can hold negative numbers.

CGFloat

Holds floating point variables with decimal points, such as 1.31 or 2.40.

NSString

Allows you to store strings of characters. We will see examples of this later.

NSNumber

Allows you to store numbers as objects.

id

Variables of type id can point to any object of any type. These are called untyped objects. Whenever you want to pass an object from one place to another but do not wish to specify its type for whatever reason, you can take advantage of this data type.

NSDictionary and NSMutableDictionary

These are immutable and mutable variants of hash tables. A hash table allows you to store a key and to associate a value to that key, such as a key named phone_num that has the value 05552487700. Read the values by referring to the keys associated with them.

NSArray and NSMutableArray

Immutable and mutable arrays of objects. An array is an ordered collection of items. For instance, you may have 10 string objects that you want to store in memory. An array could be a good place for that.

NSSet, NSMutableSet, NSOrderedSet, NSMutableOrderedSet

Sets are like arrays in that they can hold series of objects, but they differ from arrays in that they contain only unique objects. Arrays can hold the same object multiple times, but a set can contain only one instance of an object. I encourage you to learn the difference between arrays and sets and use them properly.

NSData and NSMutableData

Immutable and mutable containers for any data. These data types are perfect when you want to read the contents of a file, for instance, into memory.

Some of the data types that we talked about are primitive, and some are classes. You’ll just have to memorize which is which. For instance, NSInteger is a primitive data type, but NSString is a class, so objects can be instantiated of it. Objective-C, like C and C++, has the concept of pointers. A pointer is a data type that stores the memory address where the real data is stored. You should know by now that pointers to classes are denoted using an asterisk sign:

NSString *myString = @"Objective-C is great!";

Thus, when you want to assign a string to a variable of type NSString in Objective-C, you simply have to store the data into a pointer of type NSString *. However, if you are about to store a floating point value into a variable, you wouldn’t specify it as a pointer since the data type for that variable is not a class:

/* Set the myFloat variable to PI */
CGFloat myFloat = M_PI;

If you wanted to have a pointer to that floating point variable, you could do so as follows:

/* Set the myFloat variable to PI */
CGFloat myFloat = M_PI;

/* Create a pointer variable that points to the myFloat variable */
CGFloat *pointerFloat = &myFloat;

Getting data from the original float is a simple dereference (myFloat), whereas getting the value of through the pointer requires the use of the asterisk (*pointerFloat). The pointer can be useful in some situations, such as when you call a function that sets the value of a floating-point argument and you want to retrieve the new value after the function returns.

Going back to classes, we probably have to talk a bit more about classes before things get lost in translation, so let’s do that next.

Creating and Taking Advantage of Classes

A class is a data structure that can have methods, instance variables, and properties, along with many other features, but for now we are just going to talk about the basics. Every class has to follow these rules:

  • The class has to be derived from a superclass, apart from a few exceptions such as NSObject and NSProxy classes, which are root classes. Root classes do not have a superclass.

  • It has to have a name that conforms to Cocoa’s naming convention for methods.

  • It has to have an interface file that defines the interface of the class.

  • It has to have an implementation where you implement the features that you have promised to deliver in the interface of the class.

NSObject is the root class from which almost every other class is inherited. For this example, we are going to add a class, named Person, to the project we created in Creating and Running Our First iOS App. We are going to then add two properties to this class, named firstName and lastName, of type NSString. Follow these steps to create and add the Person class to your project:

  1. In Xcode, while your project is open and in front of you, from the File menu, choose New → File...

  2. On the lefthand side, ensure that under the iOS main section you have chosen the Cocoa Touch category. Once done, select the Objective-C Class item and press the Next button.

  3. In the Class section, enter Person.

  4. In the “Subclass of” section, enter NSObject.

  5. Once done, press the Next button, at which point Xcode will ask where you would like to save this file. Simply save the new class into the folder where you have placed your project and its files. This is the default selection. Then press the Create button, and you are done.

You now have two files added to your project: Person.h and Person.m. The former is the interface and the latter is the implementation file for your Person class. In Objective-C, .h files are headers, where you define the interface of each class, and .m files are implementation files where you write the actual implementation of the class.

Now let’s go into the header file of our Person class and define two properties for the class, of type NSString:

@interface Person : NSObject

@property (nonatomic, copy) NSString *firstName;
@property (nonatomic, copy) NSString *lastName;

@end

Just like a variable, definition of properties has its own format, in this particular order:

  1. The definition of the property has to start with the @property keyword.

  2. You then need to specify the qualifiers of the property. nonatomic properties are not thread-safe. We will talk about thread safety in Chapter 16. You can also specify assign, copy, weak, strong, or unsafe_unretained as the property qualifiers. We will read more about these soon too.

  3. You then have to specify the data type of the property, such as NSInteger or NSString.

  4. Last but not least, you have to specify a name for the property. The name of the property has to follow the Apple guidelines.

We said that properties can have various qualifiers. Here are the important qualifiers that you need to know about:

strong

Properties of this type will be retained by the runtime. These can only be instances of classes. In other words, you cannot retain a value into a property of type strong if the value is a primitive. You can retain objects, but not primitives.

copy

The same as strong, but when you assign to properties of this type, the runtime will make a copy of the object on the right side of the assignment. The object on the righthand side of the assignment must conform to the NSCopying or NSMutableCopying protocol.

assign

Objects or primitive values that are set as the value of a property of type assign will not be copied or retained by that property. For primitive properties, this qualifier will create a memory address where you can put the primitive data. For objects, properties of this type will simply point to the object on the righthand side of the equation.

unsafe_unretained

The same as the assign qualifier.

weak

The same as the assign qualifier with one big difference. In the case of objects, when the object that is assigned to a property of this type is released from memory, the runtime will automatically set the value of this property to nil.

We now have a Person class with two properties: firstName and lastName. Let’s go back to our app delegate’s implementation (AppDelegate.m) file and instantiate an object of type Person:

#import "AppDelegate.h"
#import "Person.h"

@implementation AppDelegate

- (BOOL)            application:(UIApplication *)application
  didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{

    Person *person = [[Person alloc] init];

    person.firstName = @"Steve";
    person.lastName = @"Jobs";

    self.window = [[UIWindow alloc]
                   initWithFrame:[[UIScreen mainScreen] bounds]];
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}

We are allocating and initializing our instance of the Person class in this example. You may not know what that means yet, but continue to the Adding Functionality to Classes with Methods section and you will find out.

Adding Functionality to Classes with Methods

Methods are building blocks of classes. For instance, a class named Person can have logical functionalities such as walk, breathe, eat, and drink. These functionalities are usually encapsulated in methods.

A method can take parameters, which are variables that the caller passes when calling the method and are visible only to the method. For instance, in a simple world, we would have a walk method for our Person class. However, if you want, you can add a parameter or argument to the method and name it walkingSpeed of type CGFloat, so that when another programmer calls that method on your class, she can specify the speed at which the person has to walk. You, as the programmer of that class, would then write the appropriate code for your class to handle different speeds of walking. Don’t worry if this all sounds like too much, but have a look at the following example, where I have added a method to the implementation file we created in Creating and Taking Advantage of Classes for our Person class:

#import "Person.h"

@implementation Person

- (void) walkAtKilometersPerHour:(CGFloat)paramSpeedKilometersPerHour{
    /* Write the code for this method here */
}

- (void) runAt10KilometersPerHour{
    /* Call the walk method in our own class and pass the value of 10 */
    [self walkAtKilometersPerHour:10.0f];
}

@end

A typical method has the following qualities in Objective-C:

  1. A prefix to tell the compiler whether the method is an instance method (-) or a class method (+). An instance method can be accessed only after the programmer allocates and initializes an instance of your class. A class method can be accessed by calling it directly from the class itself. Don’t worry if this all sounds complicated. We will see examples of these methods in this book, so don’t get hung up on this for now.

  2. A data type for the method, if the method returns any value. In our example, we have specified void, telling the compiler that we are not returning anything.

  3. The first part of the method name followed by the first parameter. You don’t necessarily have to have any parameters for a method. You can have methods that take no parameters.

  4. The list of subsequent parameters following the first parameter.

Let me show you an example of a method with two parameters:

- (void) singSong:(NSData *)paramSongData loudly:(BOOL)paramLoudly{
    /* The parameters that we can access here in this method are:

     paramSongData (to access the song's data)
     paramLoudly will tell us if we have to sing the song loudly or not
     */
}

It’s important to bear in mind that every parameter in every method has an external and an internal name. The external name is part of the method, whereas the internal part is the actual name or alias of the parameter that can be used inside the method’s implementation. In the previous example, the external name of the first parameter is singSong, whereas its internal name is paramSongData. The external name of the second parameter is loudly, but its internal name is paramLoudly. The method’s name and the external names of its parameters combine to form what is known as the selector for the method. The selector for the aforementioned method in this case would be singSong:loudly:. A selector, as you will later see in this book, is the runtime identifier of every method. No two methods inside a single class can have the same selector.

In our example, we have defined three methods for our Person class, inside its implementation file (Person.m):

  • walkAtKilometersPerHour:

  • runAt10KilometersPerHour

  • singSong:loudly:

If we want to be able to use any of these methods from the outside world—for instance, from the app delegate—we should expose those methods in our interface file (Person.h):

#import <Foundation/Foundation.h>

@interface Person : NSObject

@property (nonatomic, copy) NSString *firstName;
@property (nonatomic, copy) NSString *lastName;

- (void) walkAtKilometersPerHour:(CGFloat)paramSpeedKilometersPerHour;
- (void) runAt10KilometersPerHour;

/* Do not expose the singSong:loudly: method to the outside world.
 That method is internal to our class. So why should we expose it? */

@end

Given this interface file, a programmer can call the walkAtKilometersPerHour: and the runAt10KilometersPerHour methods from outside the Person class, but not the singSong:loudly: method because it has not been exposed in the file. So let’s go ahead and try to call all three of these methods from our app delegate to see what happens!

- (BOOL)            application:(UIApplication *)application
  didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{

    Person *person = [[Person alloc] init];

    [person walkAtKilometersPerHour:3.0f];
    [person runAt10KilometersPerHour];

    /* If you uncomment this line of code, the compiler will give
     you an error telling you this method doesn't exist on the Person class */
    //[person singSong:nil loudly:YES];

    self.window = [[UIWindow alloc]
                   initWithFrame:[[UIScreen mainScreen] bounds]];
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}

Now we know how to define and call instance methods, but what about class methods? Let’s first find out what class methods are and how they differ from instance methods.

An instance method is a method that relates to an instance of a class. For instance, in our Person class, you can instantiate this class twice to create two distinct persons in a hypothetical game that you are working on and have one of those persons walk at the speed of 3 kilometers an hour while the other person walks at 2 kilometers an hour. Even though you have written the code for the walking instance method only once, when two separate instances of the Person class are created at runtime, the calls to the instance methods will be routed to the appropriate instance of this class.

In contrast, class methods work on the class itself. For instance, in a game where you have instances of a class named Light that light the scenery of your game, you may have a dimAllLights class method on this class that a programmer can call to dim all lights in the game, no matter where they are placed. Let’s have a look at an example of a class method on our Person class:

#import "Person.h"

@implementation Person

+ (CGFloat) maximumHeightInCentimeters{
    return 250.0f;
}

+ (CGFloat) minimumHeightInCentimeters{
    return 40.0f;
}

@end

The maximumHeightInCentimeters method is a class method that returns the hypothetical maximum height of any person in centimeters. The minimumHeightInCentimeters class method returns the minimum height of any person. Here is how we would then expose these methods in the interface of our class:

#import <Foundation/Foundation.h>

@interface Person : NSObject

@property (nonatomic, copy) NSString *firstName;
@property (nonatomic, copy) NSString *lastName;
@property (nonatomic, assign) CGFloat currentHeight;

+ (CGFloat) maximumHeightInCentimeters;
+ (CGFloat) minimumHeightInCentimeters;

@end

Note

We have also added a new floating point property to our Person class named currentHeight. This allows instances of this class to be able to store their height in memory for later reference, just like their first or last names.

And in our app delegate, we would proceed to use these new methods like so:

- (BOOL)            application:(UIApplication *)application
  didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{

    Person *steveJobs = [[Person alloc] init];
    steveJobs.firstName = @"Steve";
    steveJobs.lastName = @"Jobs";
    steveJobs.currentHeight = 175.0f; /* Centimeters */

    if (steveJobs.currentHeight >= [Person minimumHeightInCentimeters] &&
        steveJobs.currentHeight <= [Person maximumHeightInCentimeters]){
        /* The height of this particular person is in the acceptable range */
    } else {
        /* This person's height is not in the acceptable range */
    }

    self.window = [[UIWindow alloc]
                   initWithFrame:[[UIScreen mainScreen] bounds]];
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}

Conforming to Requirements of Other Classes with Protocols

Objective-C has the concept of a protocol. This is a concept found in many other languages (always under a different term, it seems); for instance, it is called an interface in Java. A protocol, as its name implies, is a set of rules that classes can abide by in order to be used in certain ways. A class that follows the rules is said to conform to the protocol. Protocols are different from actual classes in that they do not have an implementation. They are just rules. For instance, every car has wheels, doors, and a main body color, among many other things. Let’s define these properties in a protocol named Car. Simply follow these steps to create a header file that can contain our Car protocol:

  1. In Xcode, while your project is open, from the File menu, select New → File...

  2. In the new dialog, on the lefthand side, make sure that you’ve selected Cocoa Touch under the iOS main category. Once done, on the righthand side of the dialog, choose “Objective-C protocol” and then press the Next button.

  3. On the next screen, under the Protocol section, enter Car as the protocol’s name and then press the Next button.

  4. You will now be asked to save your protocol on disk. Simply choose a location, usually in your project’s folder, and press the Create button.

Xcode will now create a file for you named Car.h with content like this:

#import <Foundation/Foundation.h>

@protocol Car <NSObject>

@end

So let’s go ahead and define the properties for the Car protocol, as we discussed earlier in this section:

#import <Foundation/Foundation.h>

@protocol Car <NSObject>

@property (nonatomic, copy) NSArray *wheels;
@property (nonatomic, strong) UIColor *bodyColor;
@property (nonatomic, copy) NSArray *doors;

@end

Now that our protocol has been defined, let’s create a class for a car, such as Jaguar, and then make that class conform to our protocol. Simply follow the steps provided in Creating and Taking Advantage of Classes to create a class named Jaguar and then make it conform to the Car protocol like so:

#import <Foundation/Foundation.h>
#import "Car.h"

@interface Jaguar : NSObject <Car>

@end

If you build your project now, you will notice that the compiler will give you a few warnings such as this:

Auto property synthesis will not synthesize property declared in a protocol

This is simply telling you that your Jaguar class is attempting to conform to the Car protocol but is not really implementing the required properties and/or methods in that protocol. So you should now know that a protocol can have required or optional items, and that you denote them by the @optional or the @required keywords. The default qualifier is @required, and since in our Car protocol we didn’t specify the qualifier explicitly, the compiler has chosen @required for us implicitly. Therefore, the Jaguar class now has to implement everything that is required from it by the Car protocol, like so:

#import <Foundation/Foundation.h>
#import "Car.h"

@interface Jaguar : NSObject <Car>

@property (nonatomic, copy) NSArray *wheels;
@property (nonatomic, strong) UIColor *bodyColor;
@property (nonatomic, copy) NSArray *doors;

@end

Perfect. Now you have an understanding of the basics of protocols and how they work and how you can define them. We will read more about them later in this book, so what you know right now about protocols is quite sufficient.

Storing Items in and Retrieving Them from Collections

Collections are instances of objects and can hold other objects. One of the primary collections is an array, which instantiates either NSArray or NSMutableArray. You can store any object in an array, and an array can contain more than one instance of the same object. Here is an example where we create an array of three strings:

NSArray *stringsArray = @[
                          @"String 1",
                          @"String 2",
                          @"String 3"
                          ];

__unused NSString *firstString = stringsArray[0];
__unused NSString *secondString = stringsArray[1];
__unused NSString *thirdString = stringsArray[2];

Note

The __unused macro tells the compiler not to complain when a variable, such as the firstString variable in our example, is declared but never used. The default behavior of the compiler is that it throws a warning to the console saying a variable is not used. Our brief example has declared the variables but not used them, so adding the aforementioned macro to the beginning of the variable declaration keeps the compiler and ourselves happy.

A mutable array is an array that can be mutated and changed after it has been created. An immutable array, like we saw, cannot be tampered with after it is created. Here is an example of an immutable array:

NSString *string1 = @"String 1";
NSString *string2 = @"String 2";
NSString *string3 = @"String 3";

NSArray *immutableArray = @[string1, string2, string3];

NSMutableArray *mutableArray = [[NSMutableArray alloc]
                                initWithArray:immutableArray];

[mutableArray exchangeObjectAtIndex:0 withObjectAtIndex:1];
[mutableArray removeObjectAtIndex:1];
[mutableArray setObject:string1 atIndexedSubscript:0];

NSLog(@"Immutable array = %@", immutableArray);
NSLog(@"Mutable Array = %@", mutableArray);

The output of this program is as follows:

Immutable array = (
    "String 1",
    "String 2",
    "String 3"
)
Mutable Array = (
    "String 1",
    "String 3"
)

Another very common collection found throughout iOS programs is a dictionary. Dictionaries are like arrays, but every object in a dictionary is assigned to a key so that later you can retrieve the same object using the key. Here is an example:

NSDictionary *personInformation =
@{
  @"firstName" : @"Mark",
  @"lastName" : @"Tremonti",
  @"age" : @30,
  @"sex" : @"Male"
  };

NSString *firstName = personInformation[@"firstName"];
NSString *lastName = personInformation[@"lastName"];
NSNumber *age = personInformation[@"age"];
NSString *sex = personInformation[@"sex"];

NSLog(@"Full name = %@ %@", firstName, lastName);
NSLog(@"Age = %@, Sex = %@", age, sex);

The output of this program is:

Full name = Mark Tremonti
Age = 30, Sex = Male

You can also have mutable dictionaries, just as you can have mutable arrays. Mutable dictionaries’ contents can be changed after they are instantiated. Here is an example:

NSDictionary *personInformation =
@{
  @"firstName" : @"Mark",
  @"lastName" : @"Tremonti",
  @"age" : @30,
  @"sex" : @"Male"
  };

NSMutableDictionary *mutablePersonInformation =
    [[NSMutableDictionary alloc] initWithDictionary:personInformation];

mutablePersonInformation[@"age"] = @32;

NSLog(@"Information = %@", mutablePersonInformation);

The output of this program is:

Information = {
    age = 32;
    firstName = Mark;
    lastName = Tremonti;
    sex = Male;
}

You can also take advantage of sets. Sets are like arrays but must contain a unique set of objects. You cannot add the same instance of an object twice to the same set. Here is an example:

NSSet *shoppingList = [[NSSet alloc] initWithObjects:
                       @"Milk",
                       @"Bananas",
                       @"Bread",
                       @"Milk", nil];

NSLog(@"Shopping list = %@", shoppingList);

If you run this program, the output will be:

Shopping list = {(
    Milk,
    Bananas,
    Bread
)}

Note how Milk was mentioned twice in our program but added to the set only once. That’s the magic behind sets. You can also use mutable sets like so:

NSSet *shoppingList = [[NSSet alloc] initWithObjects:
                       @"Milk",
                       @"Bananas",
                       @"Bread",
                       @"Milk", nil];

NSMutableSet *mutableList = [NSMutableSet setWithSet:shoppingList];

[mutableList addObject:@"Yogurt"];
[mutableList removeObject:@"Bread"];

NSLog(@"Original list = %@", shoppingList);
NSLog(@"Mutable list = %@", mutableList);

And the output is:

Original list = {(
    Milk,
    Bananas,
    Bread
)}
Mutable list = {(
    Milk,
    Bananas,
    Yogurt
)}

There are two other important classes that you need to know about, now that we are talking about sets and collections:

NSOrderedSet

An immutable set that keeps the order in which objects were added to it

NSMutableOrderedSet

The mutable version of the ordered set

By default, sets do not keep the order in which objects were added to them. Take the following as an example:

NSSet *setOfNumbers = [NSSet setWithArray:@[@3, @4, @1, @5, @10]];
NSLog(@"Set of numbers = %@", setOfNumbers);

What gets printed to the screen after you run this program is:

Set of numbers = {(
    5,
    10,
    3,
    4,
    1
)}

But that is not the order in which we created the set. If you want to keep the order intact, simply use the NSOrderedSet class instead:

NSOrderedSet *setOfNumbers = [NSOrderedSet orderedSetWithArray
                              :@[@3, @4, @1, @5, @10]];

NSLog(@"Ordered set of numbers = %@", setOfNumbers);

And, of course, you can use the mutable version of an ordered set:

NSMutableOrderedSet *setOfNumbers =
    [NSMutableOrderedSet orderedSetWithArray:@[@3, @4, @1, @5, @10]];

[setOfNumbers removeObject:@5];
[setOfNumbers addObject:@0];
[setOfNumbers exchangeObjectAtIndex:1 withObjectAtIndex:2];

NSLog(@"Set of numbers = %@", setOfNumbers);

The results are shown here:

Set of numbers = {(
    3,
    1,
    4,
    10,
    0
)}

Before we move off the topic of sets, there is one other handy class that you may need to know about. The NSCountedSet class can hold a unique instance of an object multiple times. However, the way this is done is different from the way arrays perform the same task. In an array, the same object can appear multiple times. But in a counted set, the object will appear only once, but the set keeps a count of how many times the object was added to the set and will decrement that counter each time you remove an instance of the object. Here is an example:

NSCountedSet *setOfNumbers = [NSCountedSet setWithObjects:
                              @10, @20, @10, @10, @30, nil];

[setOfNumbers addObject:@20];
[setOfNumbers removeObject:@10];

NSLog(@"Count for object @10 = %lu",
      (unsigned long)[setOfNumbers countForObject:@10]);

NSLog(@"Count for object @20 = %lu",
      (unsigned long)[setOfNumbers countForObject:@20]);

The output is:

Count for object @10 = 2
Count for object @20 = 2

Note

The NSCountedSet class is mutable, despite what its name may lead you to think.

Adding Object Subscripting Support to Your Classes

Traditionally, when accessing objects in collections such as arrays and dictionaries, programmers had to access a method on the array or the dictionary to get or set that object. For instance, this is the traditional way of creating a mutable dictionary, adding two keys and values to it, and retrieving those values back:

NSString *const kFirstNameKey = @"firstName";
NSString *const kLastNameKey = @"lastName";

NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init];

[dictionary setValue:@"Tim" forKey:kFirstNameKey];
[dictionary setValue:@"Cook" forKey:kLastNameKey];

__unused NSString *firstName = [dictionary valueForKey:kFirstNameKey];
__unused NSString *lastName = [dictionary valueForKey:kLastNameKey];

But with all the advances in the LLVM compiler, this code can now be shortened to this:

NSString *const kFirstNameKey = @"firstName";
NSString *const kLastNameKey = @"lastName";

NSDictionary *dictionary = @{
                             kFirstNameKey : @"Tim",
                             kLastNameKey : @"Cook",
                             };

__unused NSString *firstName = dictionary[kFirstNameKey];
__unused NSString *lastName = dictionary[kLastNameKey];

You can see that we are initializing the dictionary by providing the keys in curly brackets. The same thing for arrays. Here is how we used to create and use arrays traditionally:

NSArray *array = [[NSArray alloc] initWithObjects:@"Tim", @"Cook", nil];
__unused NSString *firstItem = [array objectAtIndex:0];
__unused NSString *secondObject = [array objectAtIndex:1];

And now with object subscripting, we can shorten this code, as follows:

NSArray *array = @[@"Tim", @"Cook"];
__unused NSString *firstItem = array[0];
__unused NSString *secondObject = array[0];

LLVM didn’t even stop there. You can add subscripting to your own classes as well. There are two types of subscripting:

Subscripting by key

With this, you can set the value for a specific key inside an object, just like you would in a dictionary. You can also access/read-from values inside the object by providing the key.

Subscripting by index

As with arrays, you can set/get values inside the object by providing an index to that object. This makes sense for array-like classes where the elements lie in a natural order that can be represented by an index.

For the first example, we are going to look at subscripting by key. To do this, we are going to create a class named Person with a firstName and a lastName. Then we are going to allow the programmer to change the first and last names by simply providing the keys to those properties.

The reason you may want to add subscripting by key to a class like this is if your property names are volatile and you want to allow the programmer to set the value of those properties without having to worry about whether the names of those properties will change later; otherwise, the programmer is better off using the properties directly. The other reason for implementing subscripting by key is if you want to hide the exact implementation/declaration of your properties from the programmer and not let her access them directly.

In order to support subscripting by key on your own classes, you must implement the following two methods on your class and put the method signatures in your class’s header file; otherwise, the compiler won’t know that your class supports subscripting by key.

#import <Foundation/Foundation.h>

/* We will use these as the keys to our firstName and lastName
 properties so that if our firstName and lastName properties' names
 change in the future in the implementation, we won't break anything
 and our class will still work, as we can simply change the value of
 these constants inside our implementation file */
extern NSString *const kFirstNameKey;
extern NSString *const kLastNameKey;

@interface Person : NSObject

@property (nonatomic, copy) NSString *firstName;
@property (nonatomic, copy) NSString *lastName;

- (id) objectForKeyedSubscript:(id<NSCopying>)paramKey;
- (void) setObject:(id)paramObject forKeyedSubscript:(id<NSCopying>)paramKey;

@end

The objectForKeyedSubscript: method will be called on your class whenever the programmer provides a key and wants to read the value of that key in your class. The parameter that will be given to you will obviously be the key from which the programmer wants to read the value. To complement this method, the setObject:forKeyedSubscript: method will be called on your class whenever the programmer wants to set the value for a specified key. So in our implementation, we want to check whether the given keys are the first name and the last name keys, and if yes, we will set/get the values of the first name and last name in our class:

#import "Person.h"

NSString *const kFirstNameKey = @"firstName";
NSString *const kLastNameKey = @"lastName";

@implementation Person

- (id) objectForKeyedSubscript:(id<NSCopying>)paramKey{

    NSObject<NSCopying> *keyAsObject = (NSObject<NSCopying> *)paramKey;
    if ([keyAsObject isKindOfClass:[NSString class]]){
        NSString *keyAsString = (NSString *)keyAsObject;
        if ([keyAsString isEqualToString:kFirstNameKey] ||
            [keyAsString isEqualToString:kLastNameKey]){
            return [self valueForKey:keyAsString];
        }
    }

    return nil;
}

- (void) setObject:(id)paramObject forKeyedSubscript:(id<NSCopying>)paramKey{
    NSObject<NSCopying> *keyAsObject = (NSObject<NSCopying> *)paramKey;
    if ([keyAsObject isKindOfClass:[NSString class]]){
        NSString *keyAsString = (NSString *)keyAsObject;
        if ([keyAsString isEqualToString:kFirstNameKey] ||
            [keyAsString isEqualToString:kLastNameKey]){
            [self setValue:paramObject forKey:keyAsString];
        }
    }
}

@end

So in this code, in the objectForKeyedSubscript: method, we are given a key, and we are expected to return the object that is associated in our instance with that key. The key that is given to us is an object that conforms to the NSCopying protocol. In other words, it’s an object that we can make a copy of, if we want to. We expect the key to be a string so that we can compare it with the predefined keys that we have declared on top of our class, and if it matches, we will set the value of that property in our class. We will then use the NSObject method named valueForKey: to return the value associated with the given key. But obviously, before we do so, we ensure that the given key is one of the keys that we expect. In the setObject:forKeyedSubscript: method we do the exact opposite. We set the values for a given key instead of returning them.

Now, elsewhere in your app, you can instantiate an object of type Person and use the predefined keys of kFirstNameKey and kLastNameKey to change the value of the firstName and lastName properties like so:

Person *person = [Person new];
person[kFirstNameKey] = @"Tim";
person[kLastNameKey] = @"Cook";
__unused NSString *firstName = person[kFirstNameKey];
__unused NSString *lastName = person[kLastNameKey];

This code will achieve exactly the same effect as the more direct approach of setting the properties of a class:

Person *person = [Person new];
person.firstName = @"Tim";
person.lastName = @"Cook";
__unused NSString *firstName = person.firstName;
__unused NSString *lastName = person.lastName;

You can also support subscripting by index, the same way arrays do. This is useful, as mentioned before, to allow programmers to access objects that have a natural order inside a class. But there are not many data structures besides arrays where it makes sense to order and number elements, unlike subscripting by key, which applies to a wide range of data structures. So the example I’ll use to illustrate subscripting by index is a bit contrived. In our previous example, we had the Person class with a first and last name. Now if you want to allow programmers to be able to read the first name by providing the index of 0 and the last name by providing the index of 1, all you have to do is declare the objectAtIndexedSubscript: and the setObject:atIndexedSubscript: methods in the header file of your class, and then write the implementation. Here is how we declare these methods in our Person class’s header file:

- (id) objectAtIndexedSubscript:(NSUInteger)paramIndex;
- (void) setObject:(id)paramObject atIndexedSubscript:(NSUInteger)paramIndex;

The implementation is also quite simple. We take the index and act upon it in a way that makes sense to our class. We decided that the first name has to have the index of 0 and the last name the index of 1. So if we get the index of 0 for setting a value, we set the value of the first name to the incoming object, and so on:

- (id) objectAtIndexedSubscript:(NSUInteger)paramIndex{

    switch (paramIndex){
        case 0:{
            return self.firstName;
            break;
        }
        case 1:{
            return self.lastName;
            break;
        }
        default:{
            [NSException raise:@"Invalid index" format:nil];
        }
    }

    return nil;
}

- (void) setObject:(id)paramObject atIndexedSubscript:(NSUInteger)paramIndex{
    switch (paramIndex){
        case 0:{
            self.firstName = paramObject;
            break;
        }
        case 1:{
            self.lastName = paramObject;
            break;
        }
        default:{
            [NSException raise:@"Invalid index" format:nil];
        }
    }
}

Now we can test out what we’ve written so far, like so:

Person *person = [Person new];
person[kFirstNameKey] = @"Tim";
person[kLastNameKey] = @"Cook";
NSString *firstNameByKey = person[kFirstNameKey];
NSString *lastNameByKey = person[kLastNameKey];

NSString *firstNameByIndex = person[0];
NSString *lastNameByIndex = person[1];

if ([firstNameByKey isEqualToString:firstNameByIndex] &&
    [lastNameByKey isEqualToString:lastNameByIndex]){
    NSLog(@"Success");
} else {
    NSLog(@"Something is not right");
}

If you’ve followed all the steps in this recipe, you should see the value Success printed to the console now.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required