Let’s try something. Make a new project named AppLoops and put this into the main.m file.
Now run it. I think you can imagine what’s happening. It’s an infinite loop and it won’t stop. Don’t worry we can stop it anytime we want by pressing the Stop button that’s next to the Run button.
Launch the Mac application named Activity Monitor. It’s in /Applications/Utilities/Activity Monitor.app.
Launch Activity Monitor and select the View→All Processes menu item.
In the Activity Monitor window select the CPU tab and click on the % CPU column header to sort by CPU.
If your infinite loop app is still running Activity Monitor will look something like this.
The apps we have been creating start running, do some work, then quit. They don’t just keep running until we quit them. But, all the apps we normally use get launched and they are running and waiting for input. How do they do that?
They run in an infinite loop. However, it’s not like the one you just made, that takes too much of your CPU! It’s a special infinite loop called a Run Loop. We can make one of our own using an Objective-C class named NSRunLoop. I bet you could have guessed that.
Stop your infinite loop by pressing the Stop button. You can also press ⌘-. to stop it.
Change your code to this.
Run the app and look at Activity Monitor.
Nothing is using the CPU like with the first infinite loop. An NSRunLoop is different. It runs in a way that is very light weight. It can stay running all the time and not use much of the processing power of your computer.
What is the NSRunLoop doing?
It’s waiting for us to do something and then it will send messages in response.
This is called an Event-Driven Application. That’s how all iOS and most Mac, Windows, and Linux apps with a graphical interface work.
Now let’s give our app some events to work with.
The Callbacks project
Let’s start doing the example in the book. It’s required anyway.
Create a new project named Callbacks.
Add the NSRunLoop code from above to the main.m file.
Now create a new class named BNRLogger. (You should use your personal prefix.)
The BNRLogger.h file should look like this.
Here are the implementations of the methods in BNRLogger.m. First, the lastTimeString method.
The static keyword makes this variable one that will be shared by all instances of the BNRLogger class.
Here’s the updateLastTime method.
Here’s the full main.m file. Type it all in.
The __unused flag tells the compiler that you know you aren’t using the variable and it’s OK.
Don’t forget the colon in @selector(updateLastTime:), it won’t work without it.
Run it and you will get output like this:
What’s going on in that app? Callbacks.
It’s like you are going on a trip and I give you my phone number. I ask you to call me and let me know when something interesting happens.
We each go on with whatever we are doing. Every once in a while you call me with an update.
This happens all the time in modern programming.
It’s easy to see how it works with a phone. You just call my number. How does it work in programming?
We need these elements.
There has to be some code that is running that we can ask to do some work for us.
We need a way to ask that code to contact us.
We have to tell the code how to contact us.
We need to do something when the call comes through.
In our example, the other code that is going to work for us is an instance of NSTimer. Once you start one of these it runs in the background and then at the prescribed interval it wakes up and calls us. We set that interval to be 2 seconds.
Next, we have to tell the timer who to call. In this case we need to have an Objective-C object. We give the timer the pointer to our BNRLogger object. The term target means the object that gets targeted by the call.
Now we have to tell the timer what message to send to our object. This can be a little confusing. Here’s the line.
A selector is a method that will be called by our worker code. Objective-C method names are converted into numbers so they can be found faster. The numbers that represent the methods are called selectors. The @selector() compiler directive is telling the compiler to find the selector number that represents the method in the parentheses. That number is being sent to the method so the method can be found quickly when it needs to be called.
Notice that updateLastTime: is not in quotes. The @selector() needs the method name as it appears in your code, including the colon!
Next, if the userInfo parameter. We won’t be using this very often so we just set it to nil.
Finally, we tell the timer to repeat at the rate of the interval time. This timer will wake up every 2 seconds.
And what does it do every time it wakes up? It calls us back by running the updateLastTime: method in our BNRLogger object. Sweet!
The previous example demonstrates the common pattern in Cocoa called Target-Action. There are many classes in the Cocoa framework that use this pattern. It is common when there is exactly one method that needs to be run when some event happens. The method will have no parameters or just one parameter.
The basic concepts happens in other ways too.
What if, in our analogy, we want the person to call a different number when they encounter different interesting events. And then when they are arrive at their destination they should call another different number?
The Target-Action pattern doesn’t work in this situation.
In this case we use a class that implements a list of methods that are known by the worker object.
With each different event, the worker calls a different method.
Here’s a list of some of the methods that the NSURLConnection knows about.
Let’s code this up and watch it run. First, open BNRLogger.h and add the protocols to the class.
Next, add a property to the BNRLogger class. This will hold the data that is being downloaded from the server.
Because we never know how much data will be returned by a connection, we need to have one method that is called repeatedly until all the data is retrieved.
This method appends each chunk of data to our NSMutableData object.
Once all the data is returned this method is called just once.
If something went wrong this method gets called.
Now for the code in the main function.
Let’s look at this in more detail.
Create our BNRLogger instance.
Create the NSURL and NSURLRequest instances.
Start downloading the file in the background on a different thread. Set our BNRLogger instance as the helper object. It’s called the delegate in the method call.
The file is now downloading and calling the helper methods in our BNRLogger instance. Our app is still processing other things, too, like the updateLastTime: message. We can see this better if we change the timer to fire every 0.1 seconds. We can even go to 0.01666666 seconds, which is 60 times per second.
The NSNotificationCenter is a way for many objects to get callbacks when certain events happen.
Add this method declaration to your BNRLogger.h file.
Then add the implementation to your BNRLogger.m file.
Add this to the main function. Then go change the timezone in your System Preferences.
The zoneChange: method will get called when our computer’s time zone is changed.
Which to use?
Target-Action: Use when one object needs to get one message.
Helper: Use when one object needs to get many different messages.
Notifications: Use when many objects need to get the same message.