As a courtesy, this is a full free rendering of my book, Programming iOS 6, by Matt Neuburg. Copyright 2013 Matt Neuburg. Please note that this edition is outdated; the current books are iOS 13 Programming Fundamentals with Swift and Programming iOS 13. If my work has been of help to you, please consider purchasing one or both of them, or you can reward me through PayPal at http://www.paypal.me/mattneub. Thank you!
Using the Cocoa frameworks requires an understanding of how those frameworks organize their classes. Cocoa class organization depends upon certain Objective-C language features that are introduced in this chapter. The chapter also surveys some commonly used Cocoa utility classes, along with a discussion of the Cocoa root class.
Cocoa effectively hands you a large repertory of objects that already know how to behave in certain desirable ways. A UIButton, for example, knows how to draw itself and how to respond when the user taps it; a UITextField knows how to display editable text, how to summon the keyboard when the user taps in it, and how to accept keyboard input.
Often, the default behavior or appearance of an object supplied by Cocoa won’t be quite what you’re after, and you’ll want to customize it. Cocoa classes are heavily endowed with methods that you can call and properties that you can set for precisely this purpose, and these will be your first resort. Always study the documentation for a Cocoa class to see whether instances can already be made to do what you want. For example, the class documentation for UILabel (Chapter 27) shows that you can set the font, size, color, line-breaking behavior, and horizontal alignment of its text, among other things.
Nevertheless, sometimes setting properties and calling methods won’t suffice to customize an instance the way you want to. In such cases, Cocoa may provide methods that are called internally as an instance does its thing, and whose behavior you can customize by subclassing and overriding (Chapter 4). You don’t have the code to any of Cocoa’s built-in classes, but you can still subclass them, creating a new class that acts just like a built-in class except for the modifications you provide.
Oddly enough, however (and you might be particularly surprised by this if you’ve used another object-oriented application framework), subclassing is probably one of the less important ways in which your code will relate to Cocoa. Knowing or deciding when to subclass can be somewhat tricky, but the general rule is that you probably shouldn’t subclass unless you’re invited to.
A common case is a UIView that is to be drawn in some custom manner. You don’t actually draw a UIView; rather, when a UIView needs drawing, its drawRect:
method is called so that the view can draw itself. So the way to draw a UIView in some completely custom manner is to subclass UIView and implement drawRect:
in the subclass. As the documentation says, “Subclasses override this method if they actually draw their views.” That’s a pretty strong hint that you need to subclass UIView in this situation.
For example, suppose we want our window to contain a horizontal line. There is no horizontal line interface widget built into Cocoa, so we’ll just have to roll our own — a UIView that draws itself as a horizontal line. Let’s try it:
In MyHorizLine.m, replace the contents of the implementation section with this (without further explanation; you’ll know all about this after you read Chapter 15):
- (id)initWithCoder:(NSCoder *)decoder { self = [super initWithCoder:decoder]; if (self) { self.backgroundColor = [UIColor clearColor]; } return self; } - (void)drawRect:(CGRect)rect { CGContextRef c = UIGraphicsGetCurrentContext(); CGContextMoveToPoint(c, 0, 0); CGContextAddLineToPoint(c, self.bounds.size.width, 0); CGContextStrokePath(c); }
Build and run the app in the Simulator. You’ll see a horizontal line corresponding to the location of the top of the MyHorizLine instance in the window. Our view has drawn itself as a horizontal line, because we subclassed it to do so.
In that example, we started with a bare UIView that had no drawing functionality of its own. That’s why there was no need to call super
; the default implementation of UIView’s drawRect:
does nothing. But you might also be able to subclass a built-in UIView subclass to modify the way it already draws itself. Again using UILabel as an example, the documentation shows that two methods are present for exactly this purpose. Both drawTextInRect:
and textRectForBounds:limitedToNumberOfLines:
explicitly tell us: “You should not call this method directly. This method should only be overridden by subclasses.” The implication is that these are methods that will be called for us, automatically, by Cocoa, as a label draws itself; thus, we can subclass UILabel and implement them in our subclass to modify how a particular type of label draws itself.
Here’s an example from one of my own apps, in which I subclass UILabel to make a label that draws its own rectangular border and has its content inset somewhat from that border, by overriding drawTextInRect:
. As the documentation tells us: “In your overridden method, you can configure the current [graphics] context further and then invoke super
to do the actual drawing [of the text].” Let’s try it:
In MyBoundedLabel.m, insert this code into the implementation section:
- (void)drawTextInRect:(CGRect)rect { CGContextRef context = UIGraphicsGetCurrentContext(); CGContextStrokeRect(context, CGRectInset(self.bounds, 1.0, 1.0)); [super drawTextInRect:CGRectInset(rect, 5.0, 5.0)]; }
Build and run the app, and you’ll see how the rectangle is drawn and the label’s text is inset within it.
Similarly, in a table view (a UITableView) you might very well be able to avoid subclassing the table view cell (UITableViewCell), because it provides so many properties through which you can customize its appearance. If you want text to appear in the cell using a certain font, the built-in cell styles and the ability to access and modify the cell’s labels might be quite sufficient. You can directly replace a cell’s background or put a checkmark at the right end of the cell. All of that is simply a matter of setting the cell’s built-in properties. But if you want a table view cell that doesn’t look or behave like any of the built-in cell styles, then you’ll probably subclass UITableViewCell. (We’ll go deeply into this in Chapter 21.)
You wouldn’t subclass UIApplication (the class of the singleton shared application instance) just in order to respond when the application has finished launching, because the delegate mechanism (Chapter 11) provides a way to do that (application:didFinishLaunchingWithOptions:
). On the other hand, if you need to perform certain tricky customizations of your app’s fundamental event messaging behavior, you might subclass UIApplication in order to override sendEvent:
. The documentation does tell you this, and it also tells you, rightly, that needing to do this would be fairly rare (though I have had occasion to do it).
If you do subclass UIApplication, you’ll need to change the third argument in the call to UIApplicationMain
in main.m from nil to the NSString name of your subclass. Otherwise your UIApplication subclass won’t be instantiated as the shared application instance.
Another class that’s commonly subclassed is UIViewController (Chapter 19). And any class you write will naturally need to be a subclass of NSObject, if nothing else. You definitely want your class to inherit all of NSObject’s yummy goodness, including alloc
and init
, which make it possible to instantiate your class in the first place (see The Secret Life of NSObject).
A category is an Objective-C language feature that allows you to reach right into an existing class and define additional methods. You can do this even if you don’t have the code for the class, as with Cocoa’s classes. Your instance methods can refer to self
, and this will mean the instance to which the message was originally sent, as usual. A category, unlike a subclass, cannot define additional instance variables; it can override methods, but you should probably not take advantage of this ability.
Defining a category is just like defining the class on which the category is being defined: you need an interface section and an implementation section, and you’ll typically distribute them into the standard .h and .m class file pair. At the start of both the interface section and the implementation section, where you give the class’s name, you add a category name in parentheses. The .h file will probably need to import the header for the original class (or the header of the framework that defines it), and the .m file will, as usual, import the .h file, as will any .m file that wants to call one of your additional methods.
The easiest way to set up your .h and .m class file pair as a category is to ask Xcode to do it for you. Choose File → New → File, and in the “Choose a template” dialog, among the iOS Cocoa Touch file types, pick “Objective-C category”. You’ll be asked to give a name for the category and the class on which you’re defining this category.
For example, in one of my apps I found myself performing a bunch of string transformations in order to derive the path to various resource files inside the app bundle based on the resource’s name and purpose. I ended up with half a dozen utility methods. Given that these methods all operated on an NSString, it was appropriate to implement them as a category of NSString, thus allowing any NSString, anywhere in my code, to respond to them.
The code was structured like this (I’ll show just one of the methods):
// StringCategories.h: #import <Foundation/Foundation.h> @interface NSString (MyStringCategories) - (NSString*) basePictureName; @end // StringCategories.m: #import "StringCategories.h" @implementation NSString (MyStringCategories) - (NSString*) basePictureName { return [self stringByAppendingString:@"IO"]; } @end
If basePictureName
had been implemented as a utility method within some other class, it would need to take a parameter — we’d have to pass an NSString to it — and, it were an instance method, we’d need to go to the extra trouble of obtaining a reference to an instance of that class. A category is neater and more compact. We’ve extended NSString itself to have basePictureName
as an instance method, so, in any .m file that imports StringCategories.h, we can send the basePictureName
message directly to any NSString we want to transform:
NSString* aName = [someString basePictureName];
A category is particularly appropriate in the case of a class like NSString, because the documentation warns us that subclassing NSString is a bad idea. That’s because NSString is part of a complex of classes called a class cluster, which means that an NSString object’s real class might actually be some other class. A category is a much better way to modify a class within a class cluster than subclassing.
A method defined through a category can equally be a class method. Thus you can inject utility methods into any appropriate class and call those methods without the overhead of instantiating anything at all. Classes are globally available, so your method becomes, in effect, a global method (see Chapter 13).
A category can be used to split a class over multiple .h/.m file pairs. If a class threatens to become long and unwieldy, yet it clearly needs to be a single class, you can define the basic part of it (including instance variables) in one file pair, and then add another file pair defining a category on your own class to contain further methods.
Cocoa itself does this. A good example is NSString. NSString is defined as part of the Foundation framework, and its basic methods are declared in NSString.h. Here we find that NSString itself, with no category, has just two methods, length
and characterAtIndex:
, because these are regarded as the minimum that a string needs to do in order to be a string. Additional methods (those that create a string, deal with a string’s encoding, split a string, search in a string, and so on) are clumped into categories. The interface for many of these categories appears in this same file, NSString.h. But a string may serve as a file pathname, so we also find a category on NSString in NSPathUtilities.h, where methods are declared for splitting a pathname string into its constituents and the like. Then, in NSURL.h, there’s another NSString category, declaring a couple of methods for dealing with percent-escaping in a URL string. Finally, off in a completely different framework (UIKit), UIStringDrawing.h adds yet another NSString category, with methods for drawing a string in a graphics context.
This organization won’t matter to you as a programmer, because an NSString is an NSString, no matter how it acquires its methods, but it can matter when you consult the documentation. The NSString methods declared in NSString.h, NSPathUtilities.h, and NSURL.h are documented in the NSString class documentation page, but the NSString methods declared in UIStringDrawing.h are not, presumably because they originate in a different framework. Instead, they appear in a separate document, NSString UIKit Additions Reference. As a result, the string drawing methods can be difficult to discover, especially as the NSString class documentation doesn’t link to the other document. I regard this as a major flaw in the structure of the Cocoa documentation. A third-party utility such as AppKiDo can be helpful here.
A class extension is a nameless category that exists solely as an interface section, like this:
@interface MyClass () // stuff goes here @end
Typically, the only classes that will be permitted to “see” a class extension will be the class that’s being extended or a subclass of that class. If only the former is the case, the class extension will usually appear directly in the class’s implementation file, like this:
// MyClass.m: @interface MyClass () // stuff goes here @end @implementation MyClass { // ivars } // methods @end
That’s such a common arrangement that Xcode’s project template files actually give you a class extension in certain classes. For example, our Empty Window project comes with a class extension at the start of ViewController.m — take a look and see!
I’m sure you’re leaping out of your chair with eagerness to know what on earth sort of “stuff” could possibly go into a class extension to make it so useful that it appears in a template file. Unfortunately, in modern Objective-C the answer has mostly to do with property declarations, which I don’t discuss until Chapter 12. So your curiosity will have to remain unsatisfied until then.
A class extension used to be the standard solution to the problem of method definition order. One method in an implementation section couldn’t call another method in that same implementation section unless either the definition or a method declaration for that other method preceded it. It’s finicky work trying to arrange all the method definitions in the right order, so the obvious solution is a method declaration. A method declaration can go only into an interface section. But to put a method declaration into the interface section in this class’s header file is annoying — it means we must switch to another file — and, even worse, it makes that method public; any class that imports that header file can now see and call this method. That’s fine if this method is supposed to be public; but what if we wanted to keep it private? The solution: a class extension at the start of this class’s implementation file. Put the method declarations into that class extension; all the methods in the implementation section can now see those method declarations and can call one another, but no other class can see them.
However, since LLVM compiler version 3.1 (Xcode 4.3) that trick has been unnecessary: methods (and functions) in a class implementation can see and call one another regardless of order.
Every reasonably sophisticated object-oriented language must face the fact that the hierarchy of subclasses and superclasses is insufficient to express the desired relationships between classes. For example, a Bee object and a Bird object might need to have certain features in common by virtue of the fact that both a bee and a bird can fly. But Bee might inherit from Insect, and not every insect can fly, so how can Bee acquire the aspects of a Flier in a way that isn’t completely independent of how Bird acquires them?
Some object-oriented languages solve this problem through mixin classes. For example, in Ruby you could define a Flier module, complete with method definitions, and incorporate it into both Bee and Bird. Objective-C uses a simpler, lighter-weight approach — the protocol. Cocoa makes heavy use of protocols.
A protocol is just a named list of method declarations, with no implementation. A class may formally declare that it conforms to (or adopts) a protocol; such conformance is inherited by subclasses. This declaration satisfies the compiler when you try to send a corresponding message. If a protocol declares an instance method myCoolMethod
, and if MyClass declares conformance to that protocol, then you can send the myCoolMethod
message to a MyClass instance and the compiler won’t complain.
Actually implementing the methods declared in a protocol is up to the class that conforms to it. A protocol method may be required or optional. If a protocol method is required, then if a class conforms to that protocol, the compiler will warn if that class fails to implement that method. Implementing optional methods, on the other hand, is optional. (Of course, that’s just the compiler’s point of view; at runtime, if a message is sent to an object with no implementation for the corresponding method, a crash can result; see Chapter 3.)
Here’s an example of how Cocoa uses a protocol. Some objects can be copied; some can’t. This has nothing to do with an object’s class heritage. Yet we would like a uniform method to which any object that can be copied will respond. So Cocoa defines a protocol named NSCopying, which declares just one method, copyWithZone:
(required). A class that explicitly conforms to NSCopying is promising that it implements copyWithZone:
.
Here’s how the NSCopying protocol is defined (in NSObject.h, where your code can see it):
@protocol NSCopying - (id)copyWithZone:(NSZone *)zone; @end
That’s all there is to defining a protocol.
The definition uses the @protocol
compiler directive; it states the name of the protocol; it consists entirely of method declarations; and it is terminated by the @end
compiler directive. A protocol definition will typically appear in a header file, so that classes that need to know about it, in order to adopt it or call its methods, can import it. A protocol section of a header file is not inside any other section (such as an interface section). Any optional methods must be preceded by the @optional
directive. A protocol definition may state that the protocol incorporates other protocols; these constitute a comma-separated list in angle brackets after the protocol’s name, like this example from Apple’s own code (UIAlertView.h):
@protocol UIAlertViewDelegate <NSObject> @optional - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex; // ... more optional method declarations ... @end
The NSCopying protocol definition in NSObject.h is just a definition; it is not a statement that NSObject conforms to NSCopying. Indeed, NSObject does not conform to NSCopying. To see this, try sending the copyWithZone:
method to your own subclass of NSObject:
MyClass* mc = [MyClass new]; MyClass* mc2 = [mc copyWithZone: [mc zone]];
The compiler warns that a MyClass instance may not respond to copyWithZone:
; under ARC, this code won’t compile at all.
To conform formally to a protocol, a class’s interface section appends the name of the protocol, in angle brackets, after the name of the superclass (or, if this is a category declaration, after the parentheses). This will necessitate importing the header file that declares the protocol (or some header file that imports that header file). To state that a class conforms to multiple protocols, put multiple protocol names in the angle brackets, separated by comma.
Let’s see what happens if you conform formally to the NSCopying protocol. Modify the first line of the interface section of your class as follows:
@interface MyClass : NSObject <NSCopying>
Now the compiler warns that MyClass fails to implement copyWithZone:
and thus does not fully implement the NSCopying protocol (because copyWithZone:
is a required method of the NSCopying protocol).
The name of a protocol may also be used when specifying an object type. Most often, the object will be typed as an id
, but with the accompanying proviso that it conforms to a protocol, whose name appears in angle brackets.
To illustrate, let’s look at another typical example of how Cocoa uses protocols, namely in connection with a table (UITableView). A UITableView has a dataSource
property, declared like this:
@property (nonatomic, assign) id<UITableViewDataSource> dataSource
This property represents an instance variable whose type is id <UITableViewDataSource>
. This means “I don’t care what class my data source belongs to, but whatever it is, it should conform to the UITableViewDataSource protocol.” Such conformance constitutes a promise that the data source will implement at least the required instance methods tableView:numberOfRowsInSection:
and tableView:cellForRowAtIndexPath:
, which the table view will call when it needs to know what data to display.
If you attempt to set a table view’s dataSource
property to an object that does not conform to UITableViewDataSource, you’ll get a warning from the compiler. So, for example:
MyClass* mc = [MyClass new]; UITableView* tv = [UITableView new]; tv.dataSource = mc; // compiler warns
Under ARC, this warning is couched in rather confusing terms, along these lines: “Passing ‘MyClass *const __strong’ to parameter of incompatible type ‘id<UITableViewDataSource>’.”
To quiet the compiler, MyClass’s declaration should state that it conforms to UITableViewDataSource. Once it does so, MyClass is an id <UITableViewDataSource>
, and the third line no longer generates a warning. Of course, you must also supply implementations of tableView:numberOfRowsInSection:
and tableView:cellForRowAtIndexPath:
in MyClass to avoid the other warning, namely that you’re not fully implementing a protocol you’ve claimed to conform to.
In a very large percentage of cases, the object that you want to assign where conformity to a protocol is expected is self
. In this situation, you can declare this class’s conformity to the protocol in the implementation file as part of a class extension, like this:
// MyClass.m: @interface MyClass () <UITableViewDataSource> @end @implementation MyClass - (void) someMethod { UITableView* tv = [UITableView new]; tv.dataSource = self; } @end
I prefer this architecture, because it means that the declaration of conformity to the protocol is right there in the same implementation file that uses the protocol, and because the header defining the protocol can be imported in the implementation file, rather than in the class’s header file where it will be unnecessarily shared with any other classes that import the same header file.
A prevalent use of protocols in Cocoa is in connection with delegate objects. We’ll talk in detail about delegates in Chapter 11, but you can readily see that many classes have a delegate
property and that the class of this property is often id <SomeProtocol>
. For example, in our Empty Window project, the AppDelegate class provided by the project template is declared like this:
@interface AppDelegate : UIResponder <UIApplicationDelegate>
The reason is that AppDelegate’s purpose on earth is to serve as the shared application’s delegate. The shared application object is a UIApplication, and a UIApplication’s delegate
property is typed as an id <UIApplicationDelegate>
. So AppDelegate announces its role by explicitly conforming to UIApplicationDelegate.
As a programmer, Cocoa’s use of protocols will matter to you in two ways:
id <SomeProtocol>
, you must make sure that that object’s class does indeed conform to SomeProtocol (and implements any methods required by that protocol).
A protocol has its own documentation page. When the UIApplication class documentation tells you that a UIApplication’s delegate
property is typed as an id <UIApplicationDelegate>
, it’s implicitly telling you that if you want to know what messages a UIApplication’s delegate might receive, you need to look in the UIApplicationDelegate protocol documentation.
Similarly, when a class’s documentation mentions that the class conforms to a protocol, don’t forget to examine that protocol’s documentation, because the latter might contain important information about how the class behaves. To learn what messages can be sent to an object, you need to look upward through the superclass inheritance chain (Chapter 8); you also need to look at any protocols that this object’s class conforms to.
You will probably not have frequent cause to define a protocol yourself. But protocols effectively allow one class to declare a method that another class is to implement, and there are certain situations where that’s exactly the problem you need to solve; so a protocol will be the solution.
For example, in one of my apps I present a view where the user can move three sliders to choose a color. Appropriately, its code is in a class called ColorPickerController. When the user taps Done or Cancel, the view should be dismissed; but first, the code that presented this view needs to hear about what color the user chose. So I need to send a message from the ColorPickerController instance back to the instance that presented it. Here is the declaration for that message:
- (void) colorPicker:(ColorPickerController*)picker didSetColorNamed:(NSString*)theName toColor:(UIColor*)theColor;
The question is: where should this declaration go?
Now, it happens that in my app I know the class of the instance that will present the ColorPickerController’s view: it is a SettingsController. So I could simply declare this method in the interface section of SettingsController’s header file. But this feels wrong:
Therefore we want ColorPickerController itself to declare the method that it itself is going to send; and we want it to send the message blindly to some receiver, without regard to the class of that receiver. Thus there needs to be a linkage, as it were, between the declaration of this method in ColorPickerController and the implementation of this method in the receiver. That linkage is precisely what a protocol creates! The solution, then, is for ColorPickerController to define a protocol in its header file, with this method as part of that protocol, and for the class that presents a ColorPickerController’s view to conform to that protocol.
If you create a project from Xcode’s own Utility Application template, you will see that this is exactly the architecture it exemplifies.
We start with a MainViewController. It eventually creates a FlipsideViewController. When the FlipsideViewController is ready to go out of existence, it is going to want to send the flipsideViewControllerDidFinish:
message back to whoever created it. So FlipsideViewController defines a FlipsideViewControllerDelegate protocol requiring the flipsideViewControllerDidFinish:
method, along with a delegate
property typed as id <FlipsideViewControllerDelegate>
. When a MainViewController instance creates a FlipsideViewController instance, it specifies that it itself, the MainViewController instance, is the FlipsideViewController’s delegate
; and it can do this, because MainViewController does in fact adopt the FlipsideViewControllerDelegate protocol! Problem solved, mission accomplished.
A protocol can explicitly designate some or all of its methods as optional. The question thus arises: How, in practice, is such an optional method feasible? We know that if a message is sent to an object and the object can’t handle that message, an exception is raised (and your app will likely crash). But a method declaration is a contract suggesting that the object can handle that message. If we subvert that contract by declaring a method that might or might not be implemented, aren’t we inviting crashes?
The answer is that Objective-C is not only dynamic but also introspective. You can ask an object whether it can deal with a message without actually sending it that message. This makes optional methods quite safe, provided you know that a method is optional.
The key method here is NSObject’s respondsToSelector:
, which takes a selector parameter and returns a BOOL. With it, you can send a message to an object only if it would be safe to do so:
MyClass* mc = [MyClass new]; if ([mc respondsToSelector:@selector(woohoo)]) { [mc woohoo]; }
You wouldn’t want to do this before sending just any old message, because it isn’t necessary except for optional methods, and it slows things down a little. But Cocoa does in fact call respondsToSelector:
on your objects as a matter of course. To see that this is true, implement respondsToSelector:
on AppDelegate in our Empty Window project and instrument it with logging:
- (BOOL) respondsToSelector: (SEL) sel { NSLog(@"%@", NSStringFromSelector(sel)); return [super respondsToSelector:(sel)]; }
The output on my machine, as the Empty Window app launches, includes the following (I’m omitting private methods and multiple calls to the same method):
application:handleOpenURL: application:openURL:sourceApplication:annotation: applicationDidReceiveMemoryWarning: applicationWillTerminate: applicationSignificantTimeChange: application:willChangeStatusBarOrientation:duration: application:didChangeStatusBarOrientation: application:willChangeStatusBarFrame: application:didChangeStatusBarFrame: application:deviceAccelerated: application:deviceChangedOrientation: applicationDidBecomeActive: applicationWillResignActive: applicationDidEnterBackground: applicationWillEnterForeground: applicationWillSuspend: application:didResumeWithOptions: application:shouldSaveApplicationState: application:supportedInterfaceOrientationsForWindow: application:willFinishLaunchingWithOptions: application:didFinishLaunchingWithOptions:
That’s Cocoa, checking to see which of the optional UIApplicationDelegate protocol methods (including a couple of undocumented methods) are actually implemented by our AppDelegate instance — which, because it is the UIApplication object’s delegate and formally conforms to the UIApplicationDelegate protocol, has explicitly agreed that it might be willing to respond to any of those messages. The entire delegate pattern (Chapter 11) depends upon this technique. Observe the policy followed here by Cocoa: it checks all the optional protocol methods once, when it first meets the object in question, and presumably stores the results; thus, the app is slowed a tiny bit by this one-time initial bombardment of respondsToSelector:
calls, but now Cocoa knows all the answers and won’t have to perform any of these same checks on the same object later on.
The Foundation classes of Cocoa provide basic data types and utilities that will form the basis of much that you do in Cocoa. Obviously I can’t list all of them, let alone describe them fully, but I can survey a few that I use frequently and that you’ll probably want to be aware of before writing even the simplest Cocoa program. For more information, start with Apple’s list of the Foundation classes in the Foundation Framework Reference.
NSRange is a struct of importance in dealing with some of the classes I’m about to discuss. Its components are integers (NSUInteger), location
and length
. For example, a range whose location
is 1 starts at the second element of something (because element counting is always zero-based), and if its length
is 2 it designates this element and the next. Cocoa also supplies various convenience methods for dealing with a range; you’ll use NSMakeRange
frequently. (Note that the name, NSMakeRange
, is backward compared to names like CGPointMake
and CGRectMake
.)
NSNotFound
is a constant integer indicating that some requested element was not found. For example, if you ask for the index of a certain object in an NSArray and the object isn’t present in the array, the result is NSNotFound
. The result could not be 0
to indicate the absence of the object, because 0
would indicate the first element of the array. Nor could it be nil, because nil is 0
(and in any case is not appropriate when an integer is expected). The true numeric value of NSNotFound
is of no concern to you; always compare against NSNotFound
itself, to learn whether a result is a meaningful index.
If a search returns a range and the thing sought is not present, the location
component of the resulting NSRange will be NSNotFound
.
NSString, which has already been used rather liberally in examples earlier in this book, is the Cocoa object version of a string. You can create an NSString through a number of class methods and initializers, or by using the NSString literal notation @"..."
, which is really a compiler directive. Particularly important is stringWithFormat:
, which lets you convert numbers to strings and combine strings; see Chapter 9, where I discussed format strings in connection with NSLog. Here are some strings in action:
int x = 5; NSString* s = @"widgets"; NSString* s2 = [NSString stringWithFormat:@"You have %i %@.", x, s];
NSString has a modern, Unicode-based idea of what a string can consist of. A string’s “elements” are its characters, whose count is its length
. These are not bytes, because the numeric representation of a Unicode character could be multiple bytes, depending on the encoding. Nor are they glyphs, because a composed character sequence that prints as a single “letter” can consist of multiple characters. Thus the length of an NSRange indicating a single “character” might be greater than 1.
An NSString can be searched using various rangeOf...
methods, which return an NSRange. In addition, NSScanner lets you walk through a string looking for pieces that fit certain criteria; for example, with NSScanner (and NSCharacterSet) you can skip past everything in a string that precedes a number and then extract the number. The rangeOfString:
family of search methods lets you look for a substring; it can specify an option NSRegularExpressionSearch
, which lets you search using a regular expression, and regular expressions are also supported as a separate class, NSRegularExpression (which uses NSTextCheckingResult to describe match results).
In this example from one of my apps, the user has tapped a button whose title is something like “5 by 4” or “4 by 3”. I want to know both numbers; one tells me how many rows the layout is to have, the other how many columns. I use an NSScanner to locate the two numbers in the title:
NSString* s = [as buttonTitleAtIndex:ix]; NSScanner* sc = [NSScanner scannerWithString:s]; int rows, cols; [sc scanInt:&rows]; [sc scanUpToCharactersFromSet:[NSCharacterSet decimalDigitCharacterSet] intoString:nil]; [sc scanInt:&cols];
Here’s how I might do the same thing using a regular expression:
NSString* s = [as buttonTitleAtIndex:ix]; int rowcol[2]; int* prowcol = rowcol; NSError* err = nil; NSRegularExpression* r = [NSRegularExpression regularExpressionWithPattern:@"\\d" options:0 error:&err]; // error-checking omitted for (NSTextCheckingResult* match in [r matchesInString:s options:0 range:NSMakeRange(0, [s length])]) *prowcol++ = [[s substringWithRange: [match range]] intValue];
The syntax seems oddly tortured, though, because we must convert each match from an NSTextCheckingResult to a range, then to a substring of our original string, and finally to an integer.
More sophisticated automated textual analysis is supported by some additional classes, such as NSDataDetector, an NSRegularExpression subclass that efficiently finds certain types of string expression such as a URL or a phone number, and NSLinguisticTagger, which actually attempts to analyze text into its grammatical parts of speech.
An NSString object’s string is immutable. You can use a string to generate another string in various ways, such as by appending another string or by extracting a substring, but you can’t alter the string itself. For that, you need NSString’s subclass, NSMutableString.
NSString has convenience utilities for working with a file path string, and is often used in conjunction with NSURL, which is another Foundation class worth looking into. NSString and some other classes discussed in this section provide methods for writing out to a file’s contents or reading in a file’s contents; when they do, the file can be specified either as an NSString file path or as an NSURL (Chapter 36).
An NSString carries no font and size information. Interface objects that display strings (such as UILabel) have a font
property that is a UIFont; but this determines the single font and size in which the string will display. Before iOS 6, display of styled text — where different runs of text have different style attributes (size, font, color, and so forth) — was quite challenging. The NSAttributedString class, embodying a string along with style runs, required the use of Core Text (Chapter 23), and you had to lay out the styled text by drawing it yourself; you couldn’t display styled text in any standard interface object.
Starting in iOS 6, however, NSAttributedString is a full-fledged Objective-C class, with methods and supporting classes that allow you to style text and paragraphs easily in sophisticated ways — and the built-in interface objects that display text can display styled text. This is a major improvement.
String drawing in a graphics context can be performed with methods provided through the UIStringDrawing category on NSString (see the String UIKit Additions Reference) and the NSStringDrawing category on NSAttributedString (see the NSAttributedString UIKit Additions Reference, new in iOS 6).
NSAttributedString, along with its supporting classes (NSMutableAttributedString, NSParagraphStyle, NSMutableParagraphStyle) and its drawing commands, is discussed in Chapter 23.
An NSDate is a date and time, represented internally as a number of seconds (NSTimeInterval) since some reference date. Calling [NSDate date]
gives you a date object for the current date and time; other date operations may involve NSDateComponents and NSCalendar and can be a bit tricky because calendars are complicated (see the Date and Time Programming Guide; NSDateComponents examples appear in Chapter 25 and Chapter 32).
You will also likely be concerned with dates represented as strings. Creation and parsing of date strings involves NSDateFormatter, which uses a format string similar to NSString’s stringWithFormat
. A complication is added by the fact that the exact string representation of a date component or format can depend upon the user’s locale, consisting of language, region format, and calendar settings. (Actually, locale considerations can also play a role in NSString format strings.)
In this example from one of my apps, I prepare the content of a UILabel reporting the date and time when our data was last updated. The app is not localized — the word “at” appearing in the string is always going to be in English — so I want complete control of the presentation of the date and time components as well. To get it, I have to insist upon a particular locale:
NSDateFormatter *df = [NSDateFormatter new]; if ([[NSLocale availableLocaleIdentifiers] indexOfObject:@"en_US"] != NSNotFound) { NSLocale* loc = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]; [df setLocale:loc]; // English month name and time zone name if possible } [df setDateFormat:@"'Updated' d MMMM yyyy 'at' h:mm a z"]; [self setRefreshControlTitle:[df stringFromDate: [NSDate date]]]; // just now
Locales are an interesting and complicated topic; to learn more, consult in your browser the documentation for ICU (International Components for Unicode), from which the iOS support for creating and parsing date strings is derived. To study what locales exist, use the locale explorer at http://demo.icu-project.org/icu-bin/locexp.
An NSNumber is an object that wraps a numeric value (including BOOL). Thus, you can use it to store and pass a number where an object is expected. An NSNumber is formed from an actual number with a method that specifies the numeric type; for example, you can call numberWithInt:
to form a number from an int:
[[NSUserDefaults standardUserDefaults] registerDefaults: [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInt: 4], @"cardMatrixRows", [NSNumber numberWithInt: 3], @"cardMatrixColumns", nil]];
As I mentioned in Chapter 5, LLVM compiler version 4.0 (Xcode 4.4) brought with it a new syntax for forming a new NSNumber instance:
@
. To specify further the numeric type, follow the literal number with U
(unsigned integer), L
(long integer), LL
(long long integer), or F
(float). (There is no NSNumber wrapper for an unsigned long.) For example, @3.1415
is equivalent to [NSNumber numberWithDouble:3.1415]
; @YES
is equivalent to [NSNumber numberWithBool:YES]
.
@
. For example, if height
and width
are floats, @(height/width)
is equivalent to [NSNumber numberWithFloat: height/width]
.
Thus, the preceding example can be rewritten like this:
[[NSUserDefaults standardUserDefaults] registerDefaults: [NSDictionary dictionaryWithObjectsAndKeys: @4, @"cardMatrixRows", @3, @"cardMatrixColumns", nil]];
(There is also a new NSDictionary literal syntax, so it will turn out, a few pages from now, that we can rewrite that code even more compactly.)
An NSNumber is not itself a number, however, so you still can’t use it in calculations or where an actual number is expected. Instead, you must explicitly extract the number from its NSNumber wrapper using the inverse of the method that wrapped the number to begin with. Knowing what that method was is up to you. So, for example, if an NSNumber wraps an int, you can call intValue
to extract the int:
NSUserDefaults* ud = [NSUserDefaults standardUserDefaults]; int therows = [[ud objectForKey:@"cardMatrixRows"] intValue]; int thecols = [[ud objectForKey:@"cardMatrixColumns"] intValue];
Actually, this is such a common transformation when communicating with NSUserDefaults that it provides convenience methods. So I could have written the same thing this way:
NSUserDefaults* ud = [NSUserDefaults standardUserDefaults]; int therows = [ud integerForKey:@"cardMatrixRows"]; int thecols = [ud integerForKey:@"cardMatrixColumns"];
An NSNumber subclass, NSDecimalNumber, can be used in calculations, thanks to a bunch of arithmetic methods (or their C equivalent functions, which are faster). This is useful particularly for rounding, because there’s a handy way to specify exactly the desired rounding behavior.
NSValue is NSNumber’s superclass. Use it for wrapping nonnumeric C values such as structs. Convenience methods provided through the NSValueUIGeometryExtensions category on NSValue (see the NSValue UIKit Additions Reference) allow easy wrapping and unwrapping of CGPoint, CGSize, CGRect, CGAffineTransform, UIEdgeInsets, and UIOffset; additional categories allow easy wrapping and unwrapping of CATransform3D, CMTime, CMTimeMapping, CMTimeRange, MKCoordinate, and MKCoordinateSpan.
You are unlikely to need to store any other kind of C value in an NSValue, but you can if you need to.
NSData is a general sequence of bytes; basically, it’s just a buffer, a chunk of memory. It is immutable; the mutable version is its subclass NSMutableData.
In practice, NSData tends to arise in two main ways:
When storing an object as a file or in user preferences. For example, you can’t store a UIColor value directly into user preferences. So if the user has made a color choice and you need to save it, you transform the UIColor into an NSData (using NSKeyedArchiver) and save that:
[[NSUserDefaults standardUserDefaults] registerDefaults: [NSDictionary dictionaryWithObjectsAndKeys: [NSKeyedArchiver archivedDataWithRootObject:[UIColor blueColor]], @"myColor", nil]];
The use of NSKeyedArchiver, and its reversal with NSKeyedUnarchiver, is discussed further in Chapter 19 and Chapter 36.
The foregoing types will quickly come to seem to you like basic data types, but of course they are actually object types — which means that they are pointers (Chapter 3) Therefore you cannot compare them using the C operators for testing equality as you would with actual numbers. That’s because, in the case of object types, the C operators compare the pointers, not the object content of the instances. For example:
NSString* s1 = [NSString stringWithFormat:@"%@, %@", @"Hello", @"world"]; NSString* s2 = [NSString stringWithFormat:@"%@, %@", @"Hello", @"world"]; if (s1 == s2) // false // ...
The two strings are equivalent (@"Hello, world"
) but are not the same object. (The example is deliberately elaborate because Cocoa’s efficient management of string literals sees to it that two strings initialized directly as @"Hello, world"
are the same object, which wouldn’t illustrate the point I’m making.) It is up to individual classes to implement a test for equality. The general test, isEqual:
, is inherited from NSObject and overridden, but some classes also define more specific and efficient tests. Thus, the correct way to perform the above test is like this:
if ([s1 isEqualToString: s2])
Similarly, it is up to individual classes to supply ordered comparison methods. The standard method is called compare:
, and returns one of three constants: NSOrderedAscending
(the receiver is less than the argument), NSOrderedSame
(the receiver is equal to the argument), or NSOrderedDescending
(the receiver is greater than the argument); for an example, see Example 3.2.
NSIndexSet expresses a collection of unique whole numbers; its purpose is to express element numbers of an ordered collection, such as an NSArray. Thus, for instance, to retrieve multiple objects simultaneously from an array, you specify the desired indexes as an NSIndexSet. It is also used with other things that are array-like; for example, you pass an NSIndexSet to a UITableView to indicate what sections to insert or delete.
To take a specific example, let’s say you want to speak of elements 1, 2, 3, 4, 8, 9, and 10 of an NSArray. NSIndexSet expresses this notion in some compact implementation that can be readily queried. The actual implementation is opaque, but you can imagine that in this case the set might consist of two NSRange structs, {1,4}
and {8,3}
, and NSIndexSet’s methods actually invite you to think of an NSIndexSet as composed of ranges.
An NSIndexSet is immutable; its mutable subclass is NSMutableIndexSet. You can form a simple NSIndexSet consisting of just one contiguous range directly, by passing an NSRange to indexSetWithIndexesInRange:
; but to form a more complex index set you’ll need to use NSMutableIndexSet so that you can append additional ranges.
To walk through (enumerate) the index values or ranges specified by an NSIndexSet, call enumerateIndexesUsingBlock:
or enumerateRangesUsingBlock:
or their variants.
An NSArray is an ordered collection of objects. Its length is its count
, and a particular object can be obtained by index number using objectAtIndex:
. The index of the first object is zero, so the index of the last object is count
minus one.
Starting with LLVM compiler version 4.0 (Xcode 4.5), it is no longer necessary to call objectAtIndex:
; instead, you can use a notation reminiscent of C and other languages that have arrays, namely, append square brackets containing the index number to the array reference (subscripting).
So, for example, if pep
contains @"Manny"
, @"Moe"
, and @"Jack"
, then pep[2]
yields @"Jack"
; it is equivalent to [pep objectAtIndex:2]
. Okay, I lied; actually, pep[2]
is equivalent to [pep objectAtIndexedSubscript:2]
. That’s because the subscripting notation causes any reference to which the subscript is appended to be sent objectAtIndexedSubscript:
. This in turn means that any class can implement objectAtIndexedSubscript:
and become eligible for subscripting notation — including your classes. Note that this method must be publicly declared for the compiler to permit the subscripting notation.
You can form an NSArray in various ways, but typically you’ll start by supplying a list of the objects it is to contain. As I mentioned in Chapter 3, a new Objective-C literal syntax (starting with LLVM compiler version 4.0, Xcode 4.4 or later) lets you wrap this list in @[...]
as a way of generating the NSArray:
NSArray* pep = @[@"Manny", @"Moe", @"Jack"];
An NSArray is immutable. This doesn’t mean you can’t mutate any of the objects it contains; it means that once the NSArray is formed you can’t remove an object from it, insert an object into it, or replace an object at a given index. To do those things, you can derive a new array consisting of the original array plus or minus some objects, or use NSArray’s subclass, NSMutableArray.
NSMutableArray’s addObject:
and replaceObjectAtIndex:withObject:
are supplemented by the same subscripting notation that applies to NSArray. In this case, though, the subscripted reference is an lvalue — you’re assigning to it:
pep[3] = @"Zelda";
That causes the NSMutableArray to be sent setObject:atIndexedSubscript:
. The way NSMutableArray implements this, if pep
has three elements, pep[3] = @"Zelda"
is equivalent to addObject:
(you’re appending to the end of the array); if pep
has more than three elements, it’s equivalent to replaceObjectAtIndex:withObject:
. (If pep
has fewer than three elements, an exception is thrown.)
You can walk through (enumerate) every object in an array with the for...in
construct described in Chapter 1. (You’ll get an exception if you try to mutate an array while enumerating it.)
You can seek an object within an array with indexOfObject:
or indexOfObjectIdenticalTo:
; the former’s idea of equality is to call isEqual:
, whereas the latter uses pointer equality.
Those familiar with other languages may miss such utility array functions as map
, which builds a new array of the results of calling a method on each object in the array. (makeObjectsPerformSelector:
requires a selector that returns no value, and enumerateObjectsUsingBlock:
requires a block function that returns no value.) The usual workaround is to make an empty mutable array and then enumerate the original array, calling a method and appending each result to the mutable array (Example 10.1). It is also sometimes possible to use key–value coding as a map
substitute (see Chapter 12).
Example 10.1. Building an array by enumerating another array
NSMutableArray* marr = [NSMutableArray array]; for (id obj in myArray) { id result = [obj doSomething]; [marr addObject: result]; }
You can filter an array to produce a new array consisting of just those objects meeting a test that can be described as an NSPredicate:
NSArray* pep = @[@"Manny", @"Moe", @"Jack"]; NSPredicate* p = [NSPredicate predicateWithFormat:@"self BEGINSWITH[cd] 'm'"]; NSArray* ems = [pep filteredArrayUsingPredicate:p];
To search or filter an array on a more customized test, you can walk through the array applying the test and adding those that meet it to an NSMutableArray (similar to Example 10.1). And there are many methods that give you the ability to search or filter an array using a block:
NSArray* pep = @[@"Manny", @"Moe", @"Jack"]; NSArray* ems = [pep objectsAtIndexes: [pep indexesOfObjectsPassingTest: ^BOOL(id obj, NSUInteger idx, BOOL *stop) { return ([(NSString*)obj rangeOfString:@"m" options:NSCaseInsensitiveSearch].location == 0); }]];
You can derive a sorted version of the array, supplying the sorting rules in various ways, or if it’s a mutable array, you can sort it directly; see Example 3.1 and Example 3.2.
Forming a new array from some or all of the elements of an existing array is not an expensive operation. The objects constituting the elements of the first array are not copied; the new array consists merely of a new set of pointers to the already existing objects. The same is true for the other collection types I’m about to discuss.
An NSSet is an unordered collection of distinct objects. “Distinct” means that no two objects in a set can return YES when they are compared using isEqual:
. Learning whether an object is present in a set is much more efficient than seeking it in an array, and you can ask whether one set is a subset of, or intersects, another set. You can walk through (enumerate) a set with the for...in
construct, though the order is of course undefined. You can filter a set, as you can an array. Indeed, much of what you can do with a set is parallel to what you can do with an array, except that of course you can’t do anything with a set that involves the notion of ordering.
To escape even that restriction, you can use an ordered set. An ordered set (NSOrderedSet) is very like an array, and the methods for working with it are very similar to the methods for working with an array — NSOrderedSet even implements objectAtIndexedSubscript:
, so you can fetch an element by subscripting.
But an ordered set’s elements must be distinct. Handing an array over to an ordered set uniques the array, meaning that order is maintained but only the first occurrence of an equal object is moved to the set. An ordered set provides many of the advantages of sets: for example, as with an NSSet, learning whether an object is present in an ordered set is much more efficient than for an array, and you can readily take the union, intersection, or difference with another set. Since the distinctness restriction will often prove no restriction at all (because the elements were going to be distinct anyway), it is likely that programmers will want to get into the habit of using NSOrderedSet instead of NSArray wherever possible.
An NSSet is immutable. You can derive one NSSet from another by adding or removing elements, or you can use its subclass, NSMutableSet. Similarly, NSOrderedSet has its mutable counterpart, NSMutableOrderedSet (which implements setObject:atIndexedSubscript:
). There is no penalty for adding to, or inserting into, a mutable set an object that the set already contains; nothing is added (and so the distinctness rule is enforced), but there’s no error.
NSCountedSet, a subclass of NSMutableSet, is a mutable unordered collection of objects that are not necessarily distinct (this concept is usually referred to as a bag). It is implemented as a set plus a count of how many times each element has been added.
An NSDictionary is an unordered collection of key–value pairs. The key is usually an NSString, though it doesn’t have to be. The value can be any object. An NSDictionary is immutable; its mutable subclass is NSMutableDictionary.
The keys of a dictionary are distinct (using isEqual:
for comparison). If you add a key–value pair to an NSMutableDictionary, then if that key is not already present, the pair is simply added, but if the key is already present, then the corresponding value is replaced.
The fundamental use of an NSDictionary is to request an entry’s value by key (using objectForKey:
); if no such key exists, the result is nil, so this is also the way to find out whether a key is present. A dictionary is thus an easy, flexible data storage device, an object-based analogue to a struct. Cocoa often uses a dictionary to provide you with an extra packet of named values, as in the userInfo
of an NSNotification, the options
parameter of application:didFinishLaunchingWithOptions:
, and so on.
The same Objective-C modernizations that brought us array literals and subscripting have brought us dictionary literals and subscripting. In addition to forming a dictionary from an array of objects and an array of keys (dictionaryWithObjects:forKeys:
) or as a nil-terminated list of alternating objects and keys (dictionaryWithObjectsAndKeys:
), a dictionary may be formed literally as a comma-separated list of key–value pairs, each key followed by a colon and the value, and wrapped in @{...}
. Thus, recall our earlier NSUserDefaults example:
[[NSUserDefaults standardUserDefaults] registerDefaults: [NSDictionary dictionaryWithObjectsAndKeys: @4, @"cardMatrixRows", @3, @"cardMatrixColumns", nil]];
That can be rewritten like this:
[[NSUserDefaults standardUserDefaults] registerDefaults: @{@"cardMatrixRows":@4, @"cardMatrixColumns":@3}];
To fetch a value from a dictionary by its key, instead of calling objectForKey:
, you can now subscript the key in square brackets to the dictionary reference: dict[key]
. Similarly, to add a key–value pair to an NSMutableDictionary, instead of calling setObject:forKey:
, you can assign to the subscripted dictionary reference. Parallel to NSArray, this is accomplished behind the scenes by calling objectForKeyedSubscript:
and setObject:forKeyedSubscript:
, and your own classes can declare these methods and be eligible for keyed subscripting notation.
Data structures such as an array of dictionaries, a dictionary of dictionaries, and so forth, are extremely common, and will often lie at the heart of an app’s functionality. Here’s an example from one of my own apps. The app bundle contains a text file laid out like this:
chapterNumber [tab] pictureName [return] chapterNumber [tab] pictureName [return]
As the app launches, I load this text file and parse it into a dictionary, each entry of which has the following structure:
key: (chapterNumber, as an NSNumber) value: [Mutable Array] (pictureName) (pictureName) ...
Thus, as we walk the text file, we end up with all pictures for a chapter collected under the number of that chapter. This makes it easy for me later to present all the pictures for a given chapter. For each line of the text file, if the dictionary entry for that chapter number doesn’t exist, we create it, with an empty mutable array as its value. Whether that dictionary entry existed or not, it does now, and its value is a mutable array, so we append the picture name to that mutable array. Observe how this single typical example (Example 10.2) brings together many of the Foundation classes discussed in this section.
Example 10.2. Parsing a file with Foundation classes
NSString* f = [[NSBundle mainBundle] pathForResource:@"index" ofType:@"txt"]; NSError* err = nil; NSString* s = [NSString stringWithContentsOfFile:f encoding:NSUTF8StringEncoding error:&err]; // error-checking omitted NSMutableDictionary* d = [NSMutableDictionary dictionary]; for (NSString* line in [s componentsSeparatedByString:@"\n"]) { NSArray* items = [line componentsSeparatedByString:@"\t"]; NSInteger chnum = [items[0] integerValue]; NSNumber* key = @(chnum); NSMutableArray* marr = d[key]; if (!marr) { // no such key, create key–value pair marr = [NSMutableArray array]; d[key] = marr; } // marr is now a mutable array, empty or otherwise NSString* picname = items[1]; [marr addObject: picname]; }
You can get from an NSDictionary a list of keys, a sorted list of keys, or a list of values. You can walk through (enumerate) a dictionary by its keys with the for...in
construct, though the order is of course undefined. A dictionary also supplies an objectEnumerator
, which you can use with the for...in
construct to walk through just the values. You can also walk through the key–value pairs together using a block, and you can even filter an NSDictionary by some test against its values.
If you have old code that you’d like to convert to use the modern NSNumber, NSArray, and NSDictionary literal Objective-C syntax, and to use array and dictionary subscripting, you can do so with no work whatever. Simply choose Edit → Refactor → Convert to Modern Objective-C Syntax.
NSNull does nothing but supply a pointer to a singleton object, [NSNull null]
. Use this singleton object to stand for nil in situations where an actual object is required and nil is not permitted. For example, you can’t use nil as the value of an element of a collection (such as NSArray, NSSet, or NSDictionary), so you’d use [NSNull null]
instead.
Despite what I said earlier about equality, you can test an object against [NSNull null]
using the C equality operator, because this is a singleton instance and therefore pointer comparison works.
Beginners sometimes have difficulty with the Foundation’s immutable/mutable class pairs, so here are some hints.
The documentation may not make it completely obvious that the mutable classes obey and, if appropriate, override the methods of the immutable classes. Thus, for example, [NSArray array]
generates an immutable array, but [NSMutableArray array]
generates a mutable array. (You will look in vain for the expected [NSMutableArray mutableArray]
.) The same is true of all the initializers and convenience class methods for instantiation: they may all have “array” in their name, but when sent to NSMutableArray, they yield a mutable array.
That fact also answers the question of how to make an immutable array mutable, and vice versa. If arrayWithArray:
, sent to the NSArray class, yields a new immutable array containing the same objects in the same order as the original array, then the same method, arrayWithArray:
, sent to the NSMutableArray class, yields a mutable array containing the same objects in the same order as the original. Thus this single method can transform an array between immutable and mutable in either direction. You can also use copy
(produces an immutable copy) and mutableCopy
(produces a mutable copy).
All of the above applies equally, of course, to the other immutable/mutable class pairs. You will often want to work internally and temporarily with a mutable instance but then store (and possibly vend, as an instance variable) an immutable instance, thus protecting the value from being changed accidentally or behind your own back. What matters is not a variable’s declared class but what class the instance really is (polymorphism; see Chapter 5), so it’s good that you can easily switch between an immutable and a mutable version of the same data.
To test whether an instance is mutable or immutable, do not ask for its class
. These immutable/mutable class pairs are all implemented as class clusters, which means that Cocoa uses a secret class, different from the documented class you work with. This secret class is subject to change without notice, because it’s none of your business and you should never have looked at it in the first place. Thus, code of this form is subject to breakage:
if ([NSStringFromClass([n class]) isEqualToString: @"NSCFArray"]) // wrong!
Instead, to learn whether an object is mutable, ask it whether it responds to a mutability method:
if ([n respondsToSelector:@selector(addObject:)]) // right
Here’s a reminder: just because a collection class is immutable doesn’t mean that the objects it collects are immutable. They are still objects and do not lose any of their normal behavior merely because they are pointed to by an immutable collection.
A property list is a string (XML) representation of data. The Foundation classes NSString, NSData, NSArray, and NSDictionary are the only classes that can be converted into a property list. Moreover, an NSArray or NSDictionary can be converted into a property list only if the only classes it collects are these classes, along with NSDate and NSNumber. (This is why, as mentioned earlier, you must convert a UIColor into an NSData in order to store it in user defaults; the user defaults is a property list.)
The primary use of a property list is to store data as a file. NSArray and NSDictionary provide convenience methods writeToFile:atomically:
and writeToURL:atomically:
that generate property list files given a pathname or file URL, respectively; they also provide inverse convenience methods that initialize an NSArray object or an NSDictionary object based on the property list contents of a given file. For this very reason, you are likely to start with one of these classes when you want to create a property list. (NSString and NSData, with their methods writeToFile:...
and writeToURL:...
, just write the data out as a file directly, not as a property list.)
When you initialize an NSArray or NSDictionary from a property list file in this way, the objects in the collection are all immutable. If you want them to be mutable, or if you want to convert an instance of one of the other property list classes to a property list, you’ll use the NSPropertyListSerialization class (see the Property List Programming Guide).
Because every class inherits from NSObject, it’s worth taking some time to investigate and understand NSObject. NSObject is constructed in a rather elaborate way:
copy
instance method, so you can call copy
on any instance, but you’ll crash unless the instance’s class adopts the NSCopying protocol and implements copyWithZone:
.
awakeFromNib
(see Chapter 7) comes from the UINibLoadingAdditions category on NSObject, declared in UINibLoading.h. And performSelector:withObject:afterDelay:
, discussed in Chapter 11, comes from the NSDelayedPerforming category on NSObject, declared in NSRunLoop.h.
respondsToSelector:
is defined as an instance method by NSObject, but it can (therefore) be treated also as a class method and sent to a class object.
The problem for the programmer is that Apple’s documentation is rather rigid about classification. When you’re trying to work out what you can say to an object, you don’t care where that object’s methods come from; you just care what you can say. But Apple differentiates methods by where they come from. Even though NSObject is the root class, the most important class, from which all other classes inherit, no single page of the documentation provides a conspectus of all its methods. Instead, you have to look at both the NSObject Class Reference and the NSObject Protocol Reference simultaneously, plus the pages documenting the NSCopying, NSMutableCopying, and NSCoding protocols (in order to understand how they interact with methods defined by NSObject), plus you have to supply mentally a class method version of every NSObject instance method!
Of the methods injected into NSObject by categories, many are delegate methods used in restricted situations (so that these are really informal protocols), and do not need centralized documentation; for example, animationDidStart:
is documented under the CAAnimation class, quite rightly, because you need to know about it only and exactly when you’re working with CAAnimation. Others that are general in nature are documented on the NSObject class documentation page itself; for example, cancelPreviousPerformRequestsWithTarget:
comes from a category declared in NSRunLoop.h, but it is documented under NSObject, quite rightly, since this is a class method, and therefore effectively a global method, that you might want to send at any time. However, every object responds to awakeFromNib
, and it’s likely to be crucial to every app you write; yet you must learn about it outside of the NSObject documentation, sitting all by itself in the NSObject UIKit Additions Reference page, where you’re extremely unlikely to discover it! The same goes, it might be argued, for all the key–value coding methods (Chapter 12) and key–value observing methods (Chapter 13).
Once you’ve collected, by hook or by crook, all the NSObject methods, you can see that they fall into a certain natural classification, much as outlined in Apple’s documentation (see also “The Root Class” in the “Cocoa Objects” section of the Cocoa Fundamentals Guide):
alloc
and copy
, along with methods that you might override in order to learn when something is happening in the lifetime of an object, such as initialize
(see Chapter 11) and dealloc
(see Chapter 12), plus methods that manage memory (see Chapter 12).
Methods for learning an object’s class and inheritance, such as class
, superclass
, isKindOfClass:
, and isMemberOfClass:
.
To check the class of an instance (or class), use methods such as isKindOfClass:
and isMemberOfClass:
. Direct comparison of two class objects, as in [someObject class] == [otherObject class]
, is rarely advisable, especially because a Cocoa instance’s class might be a private, undocumented subclass of the class you expect. I mentioned this already in connection with class clusters, and it can happen in other cases.
respondsToSelector:
; for representing an object as a string (description
, used in debugging; see Chapter 9); and for comparing objects (isEqual:
).
doesNotRecognizeSelector:
. If you’re curious, see the Objective-C Runtime Programming Guide. An example appears in Chapter 25.
performSelector:
takes a selector as parameter, and sending it to an object tells that object to perform that selector. This might seem identical to just sending that message to that object, but what if you don’t know what message to send until runtime? Moreover, variants on performSelector:
allow you send a message on a specified thread (Chapter 38), or send a message after a certain amount of time has passed (performSelector:withObject:afterDelay:
and similar).