The Foundation framework is the bread and butter in the toolbox of an iOS developer. It provides the NSObject
root class and a large number of fundamental building blocks for iOS development, from classes for numbers and strings, to arrays and dictionaries. The Foundation framework might seem a bit dull at first, but it harnesses a lot of power and is indispensable for developing iOS applications.
A Word About Core Foundation
In the previous article, I briefly mentioned Core Foundation and its relation to Foundation. Even though we won't explicitly use the Core Foundation framework in the rest of this series, it's useful to be familiar with the framework and to know how it differs from its object-oriented sibling, Foundation, which you'll use extensively.
While the Foundation framework is implemented in Objective-C, the Core Foundation framework is C-based. Despite this difference, the Core Foundation framework does implement a limited object model. This object model allows for the definition of a collection of opaque types that are often referred to as objects—despite the fact that they are, strictly speaking, not objects.
The primary goal of both frameworks is similar, enabling sharing of data and code between the various libraries and frameworks. Core Foundation also includes support for internationalization. A key component of this support is provided through the CFString
opaque type, which efficiently manages an array of Unicode characters.
As I mentioned previously, toll-free bridging literally bridges the gap between both frameworks by enabling the substitution of Cocoa objects for Core Foundation objects in function parameters and vice versa.
It's important to note that Automatic Reference Counting (ARC) doesn't manage Core Foundation "objects". This means that you are responsible for managing memory when working with Core Foundation. Mike Ash wrote a great article about Automatic Reference Counting and how to use ARC with Core Foundation and toll-free bridging.
Visit Apple's Core Foundation framework reference if you want to learn more about the framework and for a complete list of the framework's opaque types.
Practice, Practice, Practice
Learning a new skill is best done through practice. Let's get started by creating a new playground in Xcode. Name the playground Foundation and set Platform to iOS. Click Next to continue. Tell Xcode where you want to save the playground and click Create.
This is what the contents of the playground should look like.
//: Playground - noun: a place where people can play import UIKit var str = "Hello, playground"
The playground contains a comment at the top, an import statement for the UIKit framework, and a variable declaration. On the right, you can see the output of the playground.
Since we'll be working with the Foundation framework, we need to replace the import statement for UIKit with an import statement for Foundation. Remove the comment at the top, update the import statement, and remove the variable declaration.
import Foundation
Foundation
The Foundation framework is much more than a collection of classes for working with numbers, strings, and collections (arrays, dictionaries, and sets). It also defines dozens of protocols, functions, data types, and constants.
In the rest of this article, I will primarily focus on the classes that you'll use most often when developing iOS applications. However, I will also briefly talk about three key protocols defined by the Foundation framework, NSObject
, NSCoding
, and NSCopying
.
Protocols
Several languages, such as Perl, Python, and C++, provide support for multiple inheritance. This means that a class can descend from—be a subclass of—more than one class.
Even though Swift and Objective-C don't provide support for multiple inheritance, they do support multiple inheritance through specification in the form of protocols. We already covered protocols earlier in this series, but there is an important detail we haven't discussed yet.
Optional and Required Methods
Remember that every method of a Swift protocol is required. If a type conforming to a Swift protocol doesn't implement every property and method defined by the protocol, the compiler will throw an error. This isn't true for Objective-C protocols. In Objective-C, methods of a protocol can be marked as optional. If a protocol method is optional, the type conforming to the protocol isn't required to implement the method.
Because the vast majority of the frameworks of the iOS SDK are written in Objective-C, we will meet a number of Objective-C protocols that define optional methods. What's important to remember is that every property and method of a Swift protocol is required.
Benefits
The benefits of protocols are manifold. When a class adopts or conforms to a protocol, the class is expected to implement the methods declared by the protocol. For some of you, protocols may be reminiscent of interfaces in Java. This means that a protocol can be used to declare the interface to an object without revealing the type of the object.
Multiple inheritance has its benefits, but it most certainly has its downsides. The advantage of protocols is that unrelated types, enums, classes, and structures, can still share similar behavior through the use of protocols.
NSObject
In addition to the NSObject
root class, the Foundation framework also defines the NSObject
protocol. In Swift, however, classes and protocols cannot have the same name. As a result, the NSObject
protocol is known as the NSObjectProtocol
in Swift.
Objects conforming to the NSObjectProtocol
protocol can be asked about their class and superclass, can be compared with other objects, and respond to self
. This is only a small subset of the behavior added to objects conforming to the NSObjectProtocol
protocol.
NSCoding
Objects conforming to the NSCoding
protocol can be encoded and decoded. This is necessary for objects that need to be archived or distributed. Object archival, for example, takes place when an object or object graph is stored to disk.
NSCopying
The NSCopying
protocol declares only one method, copyWithZone(_:)
. If a class is to support copying objects, it needs to conform to the NSCopying
protocol. Copying an object is done by sending it a message of copy()
or copyWithZone(_:)
.
Classes
NSObject
The NSObject
class is the root class of the vast majority of the Objective-C class hierarchies. Why is that important if we're interested in Swift? Remember that most frameworks of the iOS SDK are powered by Objective-C. Even if you decide to develop an application in Swift, you'll still be using Objective-C under the hood.
By inheriting from the NSObject
root class, objects know how to behave as Objective-C objects and how to interface with the Objective-C runtime. It shouldn't be a surprise that NSObject
conforms to the NSObject/NSObjectProtocol
protocol, which we discussed a few moments ago.
Let's see what we get for free if a class inherits from the NSObject
class. Add the following code snippet to your playground. This should look familiar if you've read the previous articles of this series. We declare a class, Book
, that inherits from NSObject
. The Book
class implements two instance methods, chapters()
and pages()
.
class Book: NSObject { func chapters() { } func pages() { } }
Because Book
inherits from NSObject
and NSObject
conforms to the NSObjectProtocol
protocol, we can ask the Book
class about its behavior and inheritance tree. Take a look at the following example.
We first ask the Book
class about itself by invoking classForCoder()
on the Book
class. The classForCoder()
method is a class method, which means we can invoke this method on the Book
class itself. Next, we ask the Book
class about its superclass or parent class, the class it directly inherits from. This returns an optional since not every class has a parent class. Unsurprisingly, the superclass of Book
is NSObject
.
The next line tells us that Book
conforms to the NSObjectProtocol
protocol, which isn't unexpected since it inherits this trait from the NSObject
class. We also learn that Book
doesn't conform to the NSCoding
protocol.
It appears that the Book
class doesn't respond to the chapters()
method. This isn't that odd since chapters()
is an instance method, not a class method. This is proven by the last two lines of our example in which we instantiate a Book
instance and ask the instance the same question.
In the above example we use the respondsToSelector(_:)
method, but you may be wondering what a selector is? What does "responding to a selector" mean? A selector is a fancy word for the name a function or method. A selector is used by the Objective-C runtime to send messages to objects. Long story short, if you read the word selector, then think function or method. The finer details are beyond the scope of this tutorial.
NSNumber
The NSNumber
class is a utility class that manages any of the basic numeric data types. It is a subclass of the NSValue
class, which provides an object-oriented wrapper for scalar types, pointers, structures, and object ids. The NSNumber
class defines methods for retrieving the value it stores, for comparing values, and also for returning a string representation of the stored value.
Keep in mind that the value retrieved from an NSNumber
instance needs to be consistent with the value that is being stored in it. The NSNumber
class will attempt to dynamically convert the stored value to the requested type, but it goes without saying that there are limitations inherent to the data types that NSNumber
can manage.
Let me illustrate this with an example. Add the following code snippet to your playground.
We create a new NSNumber
instance by passing a Double
value to init(double:)
. Next, we request the stored value using three different NSNumber
methods. As you can see, the results don't always reflect the value stored in myNumber
.
The lesson is simple, be consistent when using NSNumber
by keeping track of the type that is stored in the NSNumber
instance.
NSString
Instances of the NSString
class manage an array of unichar
characters forming a string of text. The subtle but important difference with a regular C string, which manages char
characters, is that a unichar
character is a multibyte character. This is similar to Swift's String
type.
As its name implies, a unichar
character is ideally suited for handling Unicode characters. Due to this implementation, the NSString
class provides out-of-the-box support for internationalization.
I want to emphasize that the string managed by an NSString
instance is immutable. This means that, once the string is created, it cannot be modified. Developers coming from other languages, such as PHP, Ruby, or JavaScript, might be confused by this behavior.
The Foundation framework also defines a mutable subclass of NSString
, NSMutableString
, that can be modified after initialization.
There are various ways to create string objects. The simplest way to create a string object is by calling the string()
method on the NSString
class. This returns an empty string object. Take a look at the class reference of NSString
for a complete list of initializers.
Another common path for creating string objects is through string literals. In the next example, a string literal is assigned to stringA
. At compile time, the compiler will replace the string literal with an instance of NSString
.
Notice that we don't rely on Swift's type inference to determine the type of stringA
. The second example, stringB
, shows why that is. If we don't explicitly specify the type of stringA
as NSString
, Swift thinks we want to create a String
instance. The example also illustrates that NSString
inherits from NSObject
while String
doesn't. Remember that the String
type in Swift is a structure and structures don't support inheritance.
The NSString
class has a wide range of instance and class methods for creating and manipulating strings and you will rarely, if ever, feel the need to subclass NSString
.
Let's explore NSString
and its mutable subclass, NSMutableString
, by adding the following snippet to your playground.
let stringA: NSString = "This is " let stringB: NSString = NSString(string: "a mutable string.") var mutableString = NSMutableString(string: stringA) mutableString.appendString(stringB as String)
We start by creating two NSString
instances, one using literal syntax and one using the init(string:)
initializer. A mutable string is then created by passing the first string as an argument. To illustrate that mutable strings can be modified after creation, stringB
is appended to the mutable string and its value printed.
NSArray
and NSSet
The NSArray
class manages an immutable, ordered list of objects. The Foundation framework also defines a mutable subclass of NSArray
, NSMutableArray
. The NSArray
class behaves very much like a C or Swift array with the difference that an instance of NSArray
manages objects. The NSArray
class declares a wide range of methods that facilitate working with arrays, such as methods for finding and sorting objects in the array.
It's important to understand that instances of NSArray
, NSSet
, and NSDictionary
can only store objects. This means that it's not possible to store scalar types, pointers, or structures in any of these collection classes—or their subclasses—the compiler will throw an error if you do. The solution is to wrap scalar types, pointers, and structures in an instance of NSValue
or NSNumber
like we saw earlier in this article.
Add the following code snippet to your playground to explore NSArray
and its mutable counterpart, NSMutableArray
.
let myArray = NSArray(objects: "Bread", "Butter", "Milk", "Eggs") print(myArray.count) print(myArray.objectAtIndex(2)) var myMutableArray = NSMutableArray(object: NSNumber(int: 265)) myMutableArray.addObject(NSNumber(int: 45))
We create an array by using the init(objects:)
initializer. This method accepts a variable number of arguments. Next, we print the number of objects in the array and ask the array for the object at index 2
.
Because NSMutableArray
inherits from NSArray
, it behaves in much the same way as NSArray
. The main difference is that objects can be added and removed from the array after initialization.
Before moving on, I want to say a few words about NSSet
. This class is similar to NSArray
, but the key differences are that the collection of objects that a set manages is unordered and that a set cannot contain duplicates.
The advantage of NSSet
is that querying its objects is faster if you only need to know if an object is contained in the set. The Foundation framework also defines NSOrderedSet
. Instances of this class have the benefits of NSSet
, but also keep track of the position of each object.
NSDictionary
Like arrays, dictionaries are a common concept in most programming languages. In Ruby, for example, they are referred to as hashes. The basic concept is easy, a dictionary manages a static collection of key-value pairs or entries.
As in Ruby hashes, the key of an entry doesn't need to be a string object per se. It can be any type of object that conforms to the NSCopying
protocol as long as the key is unique in the dictionary. In most cases, though, it's recommended to use string objects as keys.
Like arrays, dictionaries cannot store a null value. If you want to represent a null value, then you can use NSNull
. The NSNull
class defines a singleton object that is used to symbolize null values in arrays, dictionaries, and sets.
The singleton pattern is an important pattern in many programming languages. It limits the instantiation of a class to one object. You will deal with singleton objects frequently when developing iOS applications.
Like NSArray
, the Foundation framework defines a mutable subclass of NSDictionary
, NSMutableDictionary
. There are various ways to instantiate a dictionary. Take a look at the following code snippet.
let keyA: NSString = "myKey" let keyB: NSString = "myKey" let myDictionary = NSDictionary(object: "This is a string literal", forKey: keyA) print(myDictionary.objectForKey(keyB))
We declare two separate NSString
objects containing the same string. We then instantiate a dictionary by invoking init(object:forKey:)
. Next, we ask the dictionary for the object associated with the contents of keyB
and print its value.
It's important to pay attention to the details. Even though we used keyA
as the key of the key-value pair and keyB
as the key to fetch the value or object of the key-value pair, the dictionary gave us the correct object (as an optional). The NSDictionary
class is smart enough to know that we want the object associated with string "myKey"
. What does this mean? Even though the objects keyA
and keyB
are different objects, the string that they contain is the same and that is precisely what the NSDictionary
class uses to reference the object.
Before we wrap up this tutorial, I'd like to show you an example of nested dictionaries and arrays and an example of NSMutableDictionary
. The following code fragment shows that a dictionary can contain another dictionary—or array—and it also shows how to work with mutable dictionaries.
let myMutableDictionary = NSMutableDictionary() myMutableDictionary.setObject(myDictionary, forKey: "myDictionary")
Conclusion
Even though we covered a lot of ground in this article, we barely scratched the surface of what the Foundation framework has to offer. It isn't necessary to know the details of every class or function defined in the Foundation framework to get started with iOS development, though. You'll learn more about the Foundation framework as you explore the iOS SDK.
In the next article, we explore the UIKit framework and I also discuss the ins and outs of an iOS application.
If you have any questions or comments, you can leave them in the comments below or reach out to me on Twitter.
Comments