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.
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:
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.
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, replacemycompany
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.Once you are done setting the aforementioned values, simply press the Next button.
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.
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.
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:
A data type, which is either a primitive, such as an integer, or an object
A name
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 phraseunsigned
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
- 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 value05552487700
. 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.
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:
In Xcode, while your project is open and in front of you, from the File menu, choose New → File...
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.
In the Class section, enter
Person
.In the “Subclass of” section, enter
NSObject
.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:
The definition of the property has to start with the
@property
keyword.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 specifyassign
,copy
,weak
,strong
, orunsafe_unretained
as the property qualifiers. We will read more about these soon too.You then have to specify the data type of the property, such as
NSInteger
orNSString
.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 theNSCopying
orNSMutableCopying
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
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 tonil
.
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.
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:
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.
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.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.
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
;
}
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:
In Xcode, while your project is open, from the File menu, select New → File...
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.
On the next screen, under the Protocol section, enter
Car
as the protocol’s name and then press the Next button.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.
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:
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
]);
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.
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.
Get iOS 7 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.