Back in the days of coding Atari ST I was fairly familiar with what was really going on in the processor after I compiled and ran my code. I knew that it was running sequentially. And if I jumped or branched off to a subroutine, I knew that it was run that and then return. It was an event based system with a menu bar… one of the first.. but still it was a main program loop that waited for events and then acted on them, running the code to act on them, and then return to the main loop and wait for the next event.
But today with multi-code processors and multi-threaded code, I have to admit that half the time I don’t really know much about what’s really happening in the compiled code from a program flow standpoint. Does it sometimes run down two threads of code at the same time? I sort of assume that it does at times. But I don’t really know.
For example, early on I found that I needed to ‘put off’ initializing some of my game variables rather than doing it inside the loadView because otherwise the OS would think my app was locked up and crash it out. The loadView needed to end as quickly as possible so the game title screen would flip off. So I used:
[self performSelector:@selector(initGame) withObject:nil afterDelay:.1];
This moved the initialization out of the loadView so loadView could return quicker. But almost immediately (.1 seconds later) run my initialization method.
But again.. I really don’t know what’s happening here. I assume that there is a main control loop that starts a timer and then executes the initGame selector when the timer runs out. But at the same time I do have an animation timer that triggers a drawView every 30th of a second. So.. what happens when one timer triggers before another one is completed and has returned? Does one method cease to run until the other one returns, does one timer have to wait in queue until the other one returns, or do both methods run along side each other? I know that I’ve seen my animations stop mid stream when something else happens, and then continue after. So maybe there is no multi-threading going on here. Maybe some know it all can leave a comment and tell me the answer.
But narrowing in on the point of THIS post.. in my table view I need to place certain functionality inside the iphoneViewController so that it will have access to properties (variables) of my game method… but I need to call it from inside one of the tableView controllers when someone taps the appropriate tableview cell. The way I’ve been handling this so far has been via messages and notifications. Once I find something that works I exploit it as much as possible. But I want to know what’s really going on so I don’t have some accidental conflict.
So to my experiment… Using NSLog is an interesting way to track the program flow and see what’s going on. I’m about to do that now and publish the results.
In my iPhoneViewController I set up the following notification:
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(testControl) name:@"testControl" object:nil];
Then:
-(void)testControl { NSLog(@"inside test control"); }
Then in my table view, inside didSelectRowAtIndexPath I have the following:
NSLog(@"test - sending a message to testControl"); NSNotification* notification = [NSNotification notificationWithName:@"testControl" object:self]; [[NSNotificationCenter defaultCenter] postNotification:notification]; NSLog(@"test - after the message is sent");
The point is to see if the ‘after message’ prints out before or after the ‘inside test control’. Is it a linear path, just like I called a subroutine (in old coding terms), or is it a sequential series of events? Here are the NSLog results:
2011-05-25 04:40:37.809 X[15647:207] test – sending a message to testControl
2011-05-25 04:40:37.809 X[15647:207] inside test control
2011-05-25 04:40:37.810 X[15647:207] test – after the message is sent
In this test it appears to be a sequential series of events in the flow. At least it looks like the message was picked up immediately, even before the clock had time to tick a 100th of a second, and the testControl method ran, and then after that the program flow returned. But with only that one NSLog in testControl maybe the timing just worked out that way. To do a better test I added a long delay in testControl via counting to 10000 in a for loop.
-(void)testControl { NSLog(@"inside test control"); for (int x=0; x<10000; x++) { // do nothing.. blow some time here freeze the app a bit } NSLog(@"exiting test control"); }
Ok so counting to 10000 happens in a blink. I had to up that to x<100000000 in order to have a barely noticeable delay.. and here are those results:
2011-05-25 04:47:54.642 X[15782:207] test – sending a message to testControl
2011-05-25 04:47:54.642 X[15782:207] inside test control
2011-05-25 04:47:54.898 X[15782:207] exiting test control
2011-05-25 04:47:54.898 X[15782:207] test – after the message is sent
I think this is a more reliable test. Sending the message did stop the program flow in that method, run all of the code in testControl and then return and continue. This is a successful test for me because it helped clear the fog a little bit.
Another note is that the [15782:207] does apparently indicate the process and thread. Above those lines in the output was:
[Switching to process 15782 thread 0x207]
Therefore we know that everything happening in those NSLogs was all sequential code in one program flow. The postNotification message sender had to wait to see if anyone received the message and run that code before returning and continuing the flow.
To answer your question: your intuition was correct. The performSelector:withObject:afterDelay: selector actually creates an NSTimer on the current NSRunloop that has a delay as is given with the object / selector as an invocation target. Your app finishes whatever it’s doing, falls all the way back to the runloop and then either sleeps in mach_msg_recv (waiting for something to happen, like a timer to fire) or handles some other event immediately if appropriate. One thing that’s added to the current runloop on your behalf is an NSTimer that pings the UI Server (on iOS this is SpringBoard). If SpringBoard doesn’t get this ping every so often, your app is considered to be hanging, and must be killed. This timeout may be a bit more harsh at app launch time, I don’t know for sure.
I want to say that you should be initializing your variables as lazily as possible: when you actually need them in order to do something. Typically, this sort of thing is done your viewController’s awakeFromNib handler. Perhaps you’re already doing this.