PDA

View Full Version : Running a loop Cocoa style




bahlquist
Dec 6, 2010, 11:51 AM
I wrote a program that used an NSTimer to send a message repeatedly to the same target. Now I want to remove the timer and just have the message sent at the start of each run loop. What is the best way to do this? If I was programming in C, I would just use a while loop.



robbieduncan
Dec 6, 2010, 12:00 PM
Why do you want to remove the NSTimer? Any equivalent method (say calling performSelector:target:argument:order:modes: (http://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSRunLoop_Class/Reference/Reference.html#//apple_ref/occ/instm/NSRunLoop/performSelector:target:argument:order:modes:)) uses a timer on the NSRunLoop anyway...

gnasher729
Dec 6, 2010, 12:32 PM
I wrote a program that used an NSTimer to send a message repeatedly to the same target. Now I want to remove the timer and just have the message sent at the start of each run loop. What is the best way to do this? If I was programming in C, I would just use a while loop.

This doesn't make sense. The run loop is executed exactly as often as it needs to, no more, and no less. There would be no guarantee how often your code would get executed. So the best way to do this is - don't. But why don't you tell us what you actually want to achieve?

GorillaPaws
Dec 6, 2010, 12:37 PM
I suspect you might want to create some form of delegation or notification so your method is called when a certain periodic action occurs. As gnasher729 has said, let us know what you're trying to accomplish.

bahlquist
Dec 6, 2010, 12:45 PM
I just want the same method called repeatedly. In C:

while(1){myFunction();}

chown33
Dec 6, 2010, 01:02 PM
I just want the same method called repeatedly. In C:

while(1){myFunction();}

That's how you do it in Objective-C, too.

If you're asking how to call your function every time through a run-loop, or as fast as possible with a run-loop, then you'll have to clarify by explaining which of those you want.

Either way, it will not be identical to the while(1) loop. The reason it won't be identical is that a while(1) never pauses or breaks or does anything else. A run-loop may. If that doesn't make sense, then you should first study run-loops more. Then come up with a more precise and more detailed explanation of exactly what should happen, in what order, and at what rate. As given, your description is too vague to determine exactly what you want to happen. In particular, "at the start of each run loop" is unclear.

In studying run-loops, you should also read about CFRunLoop, not just NSRunLoop. You may also find CFRunLoopObserver to be useful.
http://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html#//apple_ref/doc/uid/10000057i-CH16-SW22
http://developer.apple.com/library/mac/#documentation/CoreFoundation/Reference/CFRunLoopObserverRef/Reference/reference.html%23//apple_ref/doc/uid/20001442

Catfish_Man
Dec 6, 2010, 01:12 PM
I just want the same method called repeatedly. In C:

while(1){myFunction();}


All C code is also valid Objective-C code.

bahlquist
Dec 6, 2010, 01:55 PM
I am animating, in an OpenGL context, objects that bounce off each other. The method that I want called repeatedly is the method that updates the location of each object. At the end of the update method is [self setNeedsDisplay:YES];. I want the update method called once every time through the run loop. Now, according to my understanding of run loops, if I stick the code while(1){update();} inside a method that will be called, like, say, "awakeFromNib", then the run loop will not complete because of the infinite loop. I do not want this to happen because I want "drawRect" to be called. In any case, it seems that I should work within the structure run loop.

These are the details. In short: I want a method called once each time through the run loop. If this request doesn't make sense (and I really think it does), please tell me explicitly what my error is.

chown33
Dec 6, 2010, 01:59 PM
You're animating. The periodic timer was already correct. Go back to it.

I don't see what you were hoping to gain by eliminating a timer. It's animation; it's supposed to be periodic.


EDITED TO ADD:
In short: I want a method called once each time through the run loop. If this request doesn't make sense (and I really think it does), please tell me explicitly what my error is.
The error is in specifying "once each time through the run loop".

Go read "The Run Loop Sequence of Events":
http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html%23//apple_ref/doc/uid/10000057i-CH16-SW22

Note Step 7 is "Put the thread to sleep until...". Without a periodic timer, how would your animation be regular? Yet if you have a periodic timer, why do you need anything else other than that?

Also note that the loop may repeat several times very rapidly, if there are multiple pending tasks after a wakeup and expiration isn't reached (Step 9). If your update function is really being called once each time through the run loop, it will be called several times in rapid succession. This will occur at an irregular (untimed) rate. What will that do to the animation?

HiRez
Dec 6, 2010, 02:21 PM
Also, you should probably be using CADisplayLink (http://developer.apple.com/library/ios/#documentation/QuartzCore/Reference/CADisplayLink_ClassRef/Reference/Reference.html) instead of NSTimer to trigger your animation updates as it is more accurate and synchronized to the display's refresh interval.

kainjow
Dec 6, 2010, 03:00 PM
Also, you should probably be using CADisplayLink (http://developer.apple.com/library/ios/#documentation/QuartzCore/Reference/CADisplayLink_ClassRef/Reference/Reference.html) instead of NSTimer to trigger your animation updates as it is more accurate and synchronized to the display's refresh interval.

but then you introduce thread safety issues, which may be a bit much at this point :)

EDIT: oops, I'm thinking CGDisplayLink.

bahlquist
Dec 6, 2010, 03:53 PM
chown33, I would appreciate it if you refrained from responding to my posts (seriously). Although you have helped me out at least once, on average you cause the discussion in my posts to degenerate. In particular, I don't want to have to explain why I don't want to use a timer and then argue about it.

Please, no one has addressed my original question yet.

gnasher729
Dec 6, 2010, 04:02 PM
chown33, I would appreciate it if you refrained from responding to my posts (seriously). Although you have helped me out at least once, on average you cause the discussion in my posts to degenerate. In particular, I don't want to have to explain why I don't want to use a timer and then argue about it.

Please, no one has addressed my original question yet.

It's not a problem to know nothing, but it is a problem to refuse to learn. You were given the best possible advice, and you ask the person giving it not to respond to your posts?

Catfish_Man
Dec 6, 2010, 04:18 PM
chown33, I would appreciate it if you refrained from responding to my posts (seriously). Although you have helped me out at least once, on average you cause the discussion in my posts to degenerate. In particular, I don't want to have to explain why I don't want to use a timer and then argue about it.

Please, no one has addressed my original question yet.

Actually they have, you just don't understand the answer.

To answer the question very directly: use a runloop source. An example of a runloop source is a timer. You could also write your own custom CFRunLoopSource that signaled readiness as soon as it fired. That would probably screw up your animation though.

Probably the simplest way to do this is to use the -performSelector:afterDelay: method and pass 0 for the delay.

However, that will use a timer internally, which is apparently bad?

lee1210
Dec 6, 2010, 04:19 PM
chown33, I would appreciate it if you refrained from responding to my posts (seriously). Although you have helped me out at least once, on average you cause the discussion in my posts to degenerate. In particular, I don't want to have to explain why I don't want to use a timer and then argue about it.

Please, no one has addressed my original question yet.

It makes me sad to reply to someone who is so willing to reject help. chown33 linked to a document that explains very specifically how one would get something to occur at various points in the run loop. Specifically, you could add a run loop observer to the entrance to the run loop (for example). The document chown33 linked to provides a link to:
http://developer.apple.com/library/mac/#documentation/CoreFoundation/Reference/CFRunLoopObserverRef/Reference/reference.html%23//apple_ref/doc/uid/20001442

This is the reference for setting up a run loop observer that will fire when you have requested. Of course, as others have explained, even if you read that and set it up you are not going to get the desired behavior because of the nature of the run loop. It's not doing what you're expecting. People are trying to warn you that the timer is a valid approach and there's no reason to abandon it and you are expressing that you do not want good advice, you want to know how to do the wrong thing. Those documents will tell you, but when you discover that tying into the run loop is not what you want in this case and come back looking for more help, you're going to have a harder time getting any when you are actively discouraging it.

In any case, good luck with this project and in getting assistance in the future.

-Lee

vocaro
Dec 6, 2010, 04:54 PM
chown33, I would appreciate it if you refrained from responding to my posts (seriously). Although you have helped me out at least once, on average you cause the discussion in my posts to degenerate.

What you perceive as degeneration is simply mass confusion caused by your own posts. You seem unable to fully explain your situation and are too quick to reject the sound advice offered to you.

chown33
Dec 6, 2010, 05:29 PM
chown33, I would appreciate it if you refrained from responding to my posts (seriously). Although you have helped me out at least once, on average you cause the discussion in my posts to degenerate. In particular, I don't want to have to explain why I don't want to use a timer and then argue about it.

You asked for an explanation of what your error was in this:
If this request doesn't make sense (and I really think it does), please tell me explicitly what my error is.

I gave an explanation of the error. If you wish to challenge my explanation, or refute it by pointing out errors in my explanation, then please do so. I did double-check the reference docs before writing the explanation, but I could have made a mistake. Otherwise all I can do is refer you to Apple's documentation, and note that the questions I asked are not merely rhetorical devices. They are simple logical questions that arise directly from how a run-loop works. Without a timer of some kind, how do you propose to have regular periodic animated frames? That seems like a fundamental question to me.

Perhaps you should explain why you don't want to use a timer, given the context of your error. If a run-loop cannot be made to do what you want without using a timer of some kind, then it seems logical to me that the reasons for not using a timer be explained.

BadWolf13
Dec 6, 2010, 05:57 PM
While I know nothing about animation in Obj-C or Cocoa, I have used a do...while loop in my Obj-C programs, the same way I used them in C++. They work just fine, and the context is exactly the same.

GorillaPaws
Dec 6, 2010, 06:21 PM
chown33, I would appreciate it if you refrained from responding to my posts (seriously). Although you have helped me out at least once, on average you cause the discussion in my posts to degenerate. In particular, I don't want to have to explain why I don't want to use a timer and then argue about it.

Please, no one has addressed my original question yet.

The arrogance of that response is appaling. Read this article (http://www.mikeash.com/getting_answers.html)--at least twice.

bahlquist
Dec 6, 2010, 09:30 PM
Animating something by updating across irregular time intervals is not a problem: just calculate how much time has elapsed since the last update, and update using that time factor. If I am animating objects that interact with each other by colliding (in a physically realistic way), I may run into trouble if too many interactions occur in a short period of time, for the time needed to calculate the collisions may be longer than the ideal update interval length. In this case I want to compute the time of the collisions and update the locations over the whole sequence of collisions - but draw nothing until everything in the model is current. I will know when everything is current when I check for the next collision and find that it occurs in the (relatively) distant future. It is then that I will do the drawing (after one final update that coordinates the model with system time). I wouldn't want to wait to draw as I may have already waited to draw while sorting out the collisions.

It seems my answer does lie with CFRunLoopObserver, which is described in a link that was posted earlier:

http://developer.apple.com/library/mac/#documentation/CoreFoundation/Reference/CFRunLoopObserverRef/Reference/reference.html%23//apple_ref/doc/uid/20001442

In particular, the "The Run Loop Sequence of Events" is helpful (I tried to look for this earlier, but didn't know what to search for):

1. Notify observers that the run loop has been entered.
2. Notify observers that any ready timers are about to fire.
3. Notify observers that any input sources that are not port based are about to fire.
4. Fire any non-port-based input sources that are ready to fire.
5. If a port-based input source is ready and waiting to fire, process the event immediately. Go to step 9.
6. Notify observers that the thread is about to sleep.
7. Put the thread to sleep until one of the following events occurs:
* An event arrives for a port-based input source.
* A timer fires.
* The timeout value set for the run loop expires.
* The run loop is explicitly woken up.
8. Notify observers that the thread just woke up.
9. Process the pending event.
* If a user-defined timer fired, process the timer event and restart the loop. Go to step 2.
* If an input source fired, deliver the event.
* If the run loop was explicitly woken up but has not yet timed out, restart the loop. Go to step 2.
10. Notify observers that the run loop has exited.

lloyddean
Dec 6, 2010, 09:59 PM
So if I'm reading correctly an apology of some sort seems in order. Are you capable of issuing one?

jared_kipe
Dec 6, 2010, 10:06 PM
Animating something by updating across irregular time intervals is not a problem: just calculate how much time has elapsed since the last update, and update using that time factor. If I am animating objects that interact with each other by colliding (in a physically realistic way), I may run into trouble if too many interactions occur in a short period of time, for the time needed to calculate the collisions may be longer than the ideal update interval length. In this case I want to compute the time of the collisions and update the locations over the whole sequence of collisions - but draw nothing until everything in the model is current. I will know when everything is current when I check for the next collision and find that it occurs in the (relatively) distant future. It is then that I will do the drawing (after one final update that coordinates the model with system time). I wouldn't want to wait to draw as I may have already waited to draw while sorting out the collisions.


Do you think this is the first time someone has ever tried to animate and calculate collisions? Technologies like CADisplayLink were created for just this kind of situation. Everything I've ever read about OpenGL uses a "time since last frame" approach to animation, often breaking up the modeling function from the displaying function so you can iterate over the model multiple times if too much time has passed since the last display.

Seems like maybe a book is more called for than a forum post right now.

chown33
Dec 6, 2010, 10:29 PM
Animating something by updating across irregular time intervals is not a problem: just calculate how much time has elapsed since the last update, and update using that time factor. If I am animating objects that interact with each other by colliding (in a physically realistic way), I may run into trouble if too many interactions occur in a short period of time, for the time needed to calculate the collisions may be longer than the ideal update interval length. In this case I want to compute the time of the collisions and update the locations over the whole sequence of collisions - but draw nothing until everything in the model is current. I will know when everything is current when I check for the next collision and find that it occurs in the (relatively) distant future. It is then that I will do the drawing (after one final update that coordinates the model with system time). I wouldn't want to wait to draw as I may have already waited to draw while sorting out the collisions.
If you had posted that explanation to begin with, we all would have known why you removed the timer. Then I wouldn't have told you to go back to it. And I still would have pointed you to the CFRunLoop and CFRunLoopObserver reference docs.

It seems my answer does lie with CFRunLoopObserver, which is described in a link that was posted earlier:

http://developer.apple.com/library/mac/#documentation/CoreFoundation/Reference/CFRunLoopObserverRef/Reference/reference.html%23//apple_ref/doc/uid/20001442

In particular, the "The Run Loop Sequence of Events" is helpful (I tried to look for this earlier, but didn't know what to search for):

You're welcome.

dejo
Dec 7, 2010, 09:23 AM
So if I'm reading correctly an apology of some sort seems in order. Are you capable of issuing one?
Starting to look doubtful...

lloyddean
Dec 7, 2010, 10:49 AM
Starting to look doubtful...

Character flaws are hard to overcome.