- •C and Objective-C
- •How this book works
- •How the life of a programmer works
- •Installing Apple’s developer tools
- •Getting started with Xcode
- •Where do I start writing code?
- •How do I run my program?
- •So what is a program?
- •Don’t stop
- •Types
- •A program with variables
- •Challenge
- •Boolean variables
- •When should I use a function?
- •How do I write and use a function?
- •How functions work together
- •Local variables, frames, and the stack
- •Recursion
- •Looking at the frames in the debugger
- •return
- •Global and static variables
- •Challenge
- •printf()
- •Integer operations
- •Integer division
- •Operator shorthand
- •Floating-point numbers
- •Tokens for displaying floating-point numbers
- •The while loop
- •The for loop
- •break
- •continue
- •The do-while loop
- •Challenge
- •Getting addresses
- •Storing addresses in pointers
- •Getting the data at an address
- •How many bytes?
- •NULL
- •Stylish pointer declarations
- •Challenges
- •Writing pass-by-reference functions
- •Avoid dereferencing NULL
- •Creating and using your first object
- •Message anatomy
- •Objects in memory
- •Challenge
- •Nesting message sends
- •Multiple arguments
- •Sending messages to nil
- •Challenge
- •Challenge
- •NSMutableArray
- •Reference pages
- •Quick Help
- •Other options and resources
- •Accessor methods
- •Dot notation
- •Properties
- •self
- •Multiple files
- •Challenge
- •Overriding methods
- •super
- •Challenge
- •Object ownership and ARC
- •Creating the Asset class
- •Adding a to-many relationship to Employee
- •Challenge
- •Retain cycles
- •Weak references
- •Zeroing of weak references
- •For the More Curious: Manual reference counting and ARC History
- •Retain count rules
- •NSArray/NSMutableArray
- •Immutable objects
- •Sorting
- •Filtering
- •NSSet/NSMutableSet
- •NSDictionary/NSMutableDictionary
- •Preprocessor directives
- •#include and #import
- •#define
- •Global variables
- •enum
- •#define vs global variables
- •Writing an NSString to a file
- •Reading files with NSString
- •Writing an NSData object to a file
- •Reading an NSData from a file
- •Target-action
- •Helper objects
- •Notifications
- •Which to use?
- •Callbacks and object ownership
- •Challenge
- •Getting started with iTahDoodle
- •BNRAppDelegate
- •Adding a C helper function
- •Objects in iTahDoodle
- •Model-View-Controller
- •The application delegate
- •Setting up views
- •Running on the iOS simulator
- •Wiring up the table view
- •Adding new tasks
- •Saving task data
- •For the More Curious: What about main()?
- •Edit BNRDocument.h
- •A look at Interface Builder
- •Edit BNRDocument.xib
- •Making connections
- •Revisiting MVC
- •Edit BNRDocument.m
- •Writing init methods
- •A basic init method
- •Using accessors
- •init methods that take arguments
- •Deadly init methods
- •Property attributes
- •Mutability
- •Lifetime specifiers
- •copy
- •More about copying
- •Advice on atomic vs. nonatomic
- •Key-value coding
- •Non-object types
- •Defining blocks
- •Using blocks
- •Declaring a block variable
- •Assigning a block
- •Passing in a block
- •typedef
- •Return values
- •Memory management
- •The block-based future
- •Challenges
- •Anonymous block
- •NSNotificationCenter
- •Bitwise-OR
- •Bitwise-AND
- •Other bitwise operators
- •Exclusive OR
- •Complement
- •Left-shift
- •Right-shift
- •Using enum to define bit masks
- •More bytes
- •Challenge
- •char
- •char *
- •String literals
- •Converting to and from NSString
- •Next Steps
- •Index
24
Callbacks
Thus far, your code has been the boss. It has been sending messages to standard Foundation objects, like instances of NSString and NSArray, and telling them what to do. Thus far, your programs have run and exited in milliseconds.
You have been living a sweet and simple existence. In the real world, applications run for hours and your objects act as slaves to the stream of events pouring in from the user, the clock, the network, etc.
In a real-world application, there needs to be an object that waits for events like mouse movements, touch events, timers, and network activity. On Mac OS X and iOS, this object is an instance of NSRunLoop. The run loop sits and waits, and when something happens, it sends a message to another object.
We say that when something happens, the run loop causes a callback to occur. For Objective-C programmers, there are three forms that a callback can take. (Because these are very general ideas, I am going to use x for “a specific something” that happens. I’ll fill in the details in the sections that follow.)
•Target-action: Before the wait begins, you say “When x happens, send this particular message to that particular object.” The object receiving the message is the target. The selector for the message is the action.
•Helper objects: Before the wait begins, you say “Here is a helper object that conforms to your protocol. Send it messages when things happen.” (More on protocols in Chapter 25.) Helper objects are often known as delegates or data sources.
•Notifications: There is an object called the notification center. Before the wait begins, you say to the notification center “This object is waiting for these sorts of notifications. When one of those notifications arrives, send the object this message.” When x happens, an object posts a notification to the notification center, and the center forwards it on to your object.
In this chapter, you will implement all three types of callbacks and learn which to employ in which circumstances.
Target-action
Timers use a target-action mechanism. You create a timer with a delay, a target, and an action. After that delay, the timer sends the action message to its target.
You are going to create a program with a run loop and timer. Every two seconds, the timer will send the action message to its target. You will create a class, and an instance of that class will be the target.
157
Chapter 24 Callbacks
Figure 24.1 Logger is the target of the NSTimer
In Xcode, create a new project: a Foundation Command Line Tool named Callbacks. As a goof, first you are just going to get a run loop and start it running. Edit main.m:
#import <Foundation/Foundation.h>
int main (int argc, const char * argv[])
{
@autoreleasepool {
[[NSRunLoop currentRunLoop] run];
}
return 0;
}
Build and run the program. Notice that the method run never returns. The run loop is in an infinite loop waiting for something to happen. You’ll need to terminate the program. (Choose Product → Stop.)
Now you are going to create a custom class to act as the target of the timer. Create a new file: an Objective-C class called Logger that is a subclass of NSObject. (Remember, to get to the class template, choose File → New → New File....) In Logger.h, declare the action method:
#import <Foundation/Foundation.h>
@interface Logger : NSObject
- (void)sayOuch:(NSTimer *)t;
@end
Notice that the action method takes one argument – the object that is sending the action message. In this case, it is the timer object.
Implement a simple sayOuch: method in Logger.m:
#import "Logger.h"
@implementation Logger
- (void)sayOuch:(NSTimer *)t
{
NSLog(@"Ouch!");
}
@end
At this point, we need to take a short detour and discuss selectors. Remember that when you send a message to an object, the object’s class is asked if it has a method with that name. The search goes up the inheritance hierarchy until a class responds with “Yeah, I’ve got a method with that name.”
158
Target-action
Figure 24.2 The search for a method with the right name
As you can imagine, this search has to happen very, very quickly. If we used the actual name of the method (which could be very long), method lookup would be really slow. To speed things up, the compiler assigns a unique number to each method name it encounters. At runtime, we use that number instead of the method name.
Figure 24.3 How it really works
That unique number that represents a particular method name is known as a selector. To create a timer that sends the message sayOuch: to our Logger, you must ask the compiler to look up its selector. This is done with the @selector compiler directive.
In main.m, create an instance of Logger and make it the target of an instance of NSTimer. Set the action to be the selector for sayOuch:.
159
Chapter 24 Callbacks
#import <Foundation/Foundation.h>
#import "Logger.h"
int main (int argc, const char * argv[])
{
@autoreleasepool {
Logger *logger = [[Logger alloc] init];
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:logger
selector:@selector(sayOuch:)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] run];
}
return 0;
}
Build and run the program. (You will get an unused variable warning. Ignore it for now.) You should see Ouch! appear in the console every 2 seconds.
Note that the colon is part of the selector. @selector(sayOuch) is not equal to @selector(sayOuch:).
Take a second look at the warning from the compiler. It is saying, “Hey, you created the variable timer, but didn’t use it.” In some settings, like this one, you need to flag a variable as purposefully unused to silence the warnings. This is done with the __unused modifier. Add that now.
__unused NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:logger
selector:@selector(sayOuch:)
userInfo:nil
repeats:YES];
Build it again, and the warning should be gone.
Helper objects
Timers are simple. They only do one thing: fire. Thus, target-action is a good fit. A lot of simple user interface controls, like buttons and sliders, also use the target-action mechanism. What about something more complex?
In Chapter 23, you used an NSURLConnection method to fetch data from a web server. It worked fine, but there are two problems with the method:
•It blocks the main thread while waiting for all the data to arrive. If you used this method in a real application, the user interface would become unresponsive while the data was fetched.
•It has no way to callback if, for example, the webserver demands a username and password.
For these reasons, we typically use an NSURLConnection asynchronously. That is, we start it fetching and then await callbacks as the data arrives or the web server demands credentials or the fetch fails.
How do you get these callbacks? You supply the NSURLConnection with a helper object. When things happen, the connection sends messages to the helper object. What messages? The guy who wrote
160
Helper objects
NSURLConnection made up a protocol – a list of method declarations – that the helper object can implement. Here are some of the methods in that protocol:
- (NSURLRequest *)connection:(NSURLConnection *)c willSendRequest:(NSURLRequest *)req redirectResponse:(NSURLResponse *)res;
-(void)connection:(NSURLConnection *)sender didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)ch;
-(void)connection:(NSURLConnection *)sender
didReceiveData:(NSData *)data;
-(void)connectionDidFinishLoading:(NSURLConnection *)sender;
-(void)connection:(NSURLConnection *)sender didFailWithError:(NSError *)error;
-(NSCachedURLResponse *)connection:(NSURLConnection *)sender
willCacheResponse:(NSCachedURLResponse *)cachedResponse;
As you can see, an NSURLConnection lives a much richer life than an NSTimer. Now you are going to create an object that implements some or all of these methods and then introduce that object to the NSURLConnection as its helper object. In particular, the NSURLConnection has a pointer called
delegate.
Figure 24.4 Logger is the delegate of the NSURLConnection
In main(), create an NSURLConnection and set the instance of Logger to be its delegate:
#import <Foundation/Foundation.h> #import "Logger.h"
int main (int argc, const char * argv[])
{
@autoreleasepool {
Logger *logger = [[Logger alloc] init];
NSURL *url = [NSURL URLWithString: @"http://www.gutenberg.org/cache/epub/205/pg205.txt"];
161
Chapter 24 Callbacks
NSURLRequest *request = [NSURLRequest requestWithURL:url];
__unused NSURLConnection *fetchConn
= [[NSURLConnection alloc] initWithRequest:request delegate:logger
startImmediately:YES];
__unused NSTimer *timer
= [NSTimer scheduledTimerWithTimeInterval:2.0 target:logger
selector:@selector(sayOuch:)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] run];
}
return 0;
}
The instance of Logger will need an instance of NSMutableData to hold onto the bytes as they arrive. Add an instance variable to Logger.h:
#import <Foundation/Foundation.h>
@interface Logger : NSObject {
NSMutableData *incomingData;
}
- (void)sayOuch:(NSTimer *)t; @end
Now implement some of the delegate methods in Logger.m:
#import "Logger.h"
@implementation Logger
- (void)sayOuch:(NSTimer *)t
{
NSLog(@"Ouch!");
}
// Called each time a chunk of data arrives
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
NSLog(@"received %lu bytes", [data length]);
// Create a mutable data if it doesn't already exist if (!incomingData) {
incomingData = [[NSMutableData alloc] init];
}
[incomingData appendData:data];
}
// Called when the last chunk has been processed
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSLog(@"Got it all!");
162