Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.

Domenachi

macrumors newbie
Original poster
Jul 26, 2013
6
0
I've read most of a couple of good books over the past couple years on iOS programming and went out of practice for a bit but coming back into now and there's a simple fundamental thing I can't wrap my head around when it comes to Objective-C and it's how events happen. Specifically how do I have a series of events happen rather than not all at one time. Here's a basic example I made to show you what I mean - I have a feeling I'm just approaching this all wrong and so my "work-around" is a terrible idea or whatever - I don't know.

Let's say I have a button called "goButton" and a label called "label". When I click the button I want the label to wait 2 seconds and read "Red", and then wait 2 more seconds and read "Yellow" and then 2 more seconds and read "Green". So when I looked up how to use "afterDelay" and did it the way I thought it would be done, it basically just waited 2 seconds and then changed to "Green" - which leads me to believe that all 3 of those delays happened at one time and we just arrived at the end - "Green". So I changed it to this to "work-around" but I feel like there must be a better way to approach this and that I just don't really understand how things happen when going through this code. In my mind if you have a line that has a delay, it should have to wait that delay out before going to the next line, but maybe that would be true if these were separate actions and after the delay you have to call the next action too? But that seems even messier. Here's my work-around that works but just doesn't seem right nor very flexible - obviously the work-around is me adding the extra delays to the successive events (so the delays are 2, 4, and then 6):

Code:
-(IBAction)goButton:(id)sender {
    [self performSelector:@selector(red) withObject:nil afterDelay:2];
    [self performSelector:@selector(yellow) withObject:nil afterDelay:4];
    [self performSelector:@selector(green) withObject:nil afterDelay:6];
}

-(void)red {
    label.text = @"Red";
}

-(void)yellow {
    label.text = @"Yellow";
}

-(void)green {
    label.text = @"Green";
}

Thanks for any help with this. I think I'm missing a fundamental part of Objective C and it probably turns this into a really stupid question but I really appreciate even making me feel dumb at this point. thanks again
 

chown33

Moderator
Staff member
Aug 9, 2009
10,750
8,421
A sea of green
Post the code that didn't work. Specifically, you wrote:
So when I looked up how to use "afterDelay" and did it the way I thought it would be done, it basically just waited 2 seconds and then changed to "Green" - which leads me to believe that all 3 of those delays happened at one time and we just arrived at the end - "Green".
So post that code.


The code you posted, with a 2-sec, 4-sec, and 6-sec delay, is one way that occurs to me. It even seems to me like a fairly obvious approach. Clearly, it wasn't an obvious approach for you, but since you didn't describe your algorithm or post any code for it, no one knows what that non-working approach was.

I can think of other ways of doing a series of delays, but rather than describing each of them, it's simpler if you post the one you expected to work but didn't. Someone could then explain why it didn't work.

EDIT
And I see the root of the problem right here:
In my mind if you have a line that has a delay, it should have to wait that delay out before going to the next line,
This is wrong.

Reread the docs for the method being called. It should explain that it schedules the target method for execution after a delay. It does not say that the delay is immediately performed, and the method won't return until after the delay. If the docs say it uses a timer, then you should refer to the NSTimer docs for more info.

There are functions that do a "hard delay", i.e. they don't return until after the delay. These won't work in this situation, because during the delay, nothing else can happen: no user interaction, etc. For an example of such a function, look up nanosleep().
 
Last edited:

ArtOfWarfare

macrumors G3
Nov 26, 2007
9,560
6,059
If you want a 2 second delay between red and yellow, then red should be the function where yellow gets scheduled.

The methods return immediately.

Alternatively, you could just do:

Code:
[self red];
sleep(2000);
[self yellow];
sleep(2000);
[self green];

That code should be placed in its own thread, though, to ensure it doesn't block the main one where user interactions take place.
 

ChristianVirtual

macrumors 601
May 10, 2010
4,122
282
日本
What I'm missing is the request to redraw the label in each color-methods ...

Code:
[label setNeedsDisplay];

Even better this way:

Code:
[label performSelectorOnMainThread:@selector(setNeedsDisplay) withObject:nil waitUntilDone:NO];
 

ArtOfWarfare

macrumors G3
Nov 26, 2007
9,560
6,059
What I'm missing is the request to redraw the label in each color-methods ...

Code:
[label setNeedsDisplay];

Even better this way:

Code:
[label performSelectorOnMainThread:@selector(setNeedsDisplay) withObject:nil waitUntilDone:NO];

Label's perform setNeedsDisplay themselves as you change their properties - you don't need to set it for them.
 

Sonnestah

macrumors regular
Mar 2, 2013
152
0
If you want a 2 second delay between red and yellow, then red should be the function where yellow gets scheduled.

The methods return immediately.

Alternatively, you could just do:

Code:
[self red];
sleep(2000);
[self yellow];
sleep(2000);
[self green];

That code should be placed in its own thread, though, to ensure it doesn't block the main one where user interactions take place.

Careful, he is updating UI, and that must be done from main thread

He is going to have to mix a background thread with the main thread
 

Domenachi

macrumors newbie
Original poster
Jul 26, 2013
6
0
If I add another layer to this and I think you'll see where the way I'm doing it starts making trouble.

Let's add a label to the ui - countDownLabel
And then let's say instead of just doing a delay, each of those actions causes countDownLabel to read "3" and then "2" and then "1", then changes the main label's text. So you hit "Go", and then countDownLabel reads 3, 2, and then 1 and then the main label changes to red, and then the countDownLabel count downs again and then changes main label to "yellow", etc.

I could have an action for each count and a delay of 1 second between each one, but now I'll have 9 actions and no flexibility. Because what if instead of a constant starting point for the countdown I want variables for the countdowns.

As in, you hit "Go" and 3 random numbers are selected as the starting points for the countdowns. randRedCount, randYellowCount, randGreedCount. Then it starts counting down from randRedCount, gets to zero and changes the main label to read "Red", then does the same for yellow and then green.

thanks again
 

chown33

Moderator
Staff member
Aug 9, 2009
10,750
8,421
A sea of green
Make a class. An instance of that class is responsible for everything dealing with whatever labels, delays, etc. you need. (Look up "encapsulation".)

That class has one method that is the target method. You then set it up so an initialized instance of the class has the right random numbers, the right counts between yellow, red, and greem or whatever.

All the delay intervals are encapsulated in the target method, too. So each time this target method runs, i.e. each time a delay interval expires, the method runs, does whatever is appropriate for that specific expiration, and then reschedules itself to be called again after whatever delay interval is appropriate.

When the object reaches the end of the actions on labels, countdowns, whatever, it does the past action and then DOESN'T reschedule a new delay.

If the version with all the delays, random numbers, whatever is too complex for you to write, then start by making a class that only handles your original red yellow green problem. Solve the simple problem first. Make sure it works. If you want comments, post its code. Then with a working simple version, start making small changes that add one feature at a time. Test again to make sure it works. Repeat.
 
Last edited:

firewood

macrumors G3
Jul 29, 2003
8,108
1,345
Silicon Valley
Cocoa is event driven. Thus your app should never wait. It should always quit running ASAP (completely return from functions and methods). And then wait for the OS to call it back (after telling the OS what to call on some event or timer or completion). The goal to run your app is to have it not run (unless it's computationally intensive or something) most of the time.

Learn to use the return statement a lot. (And blocks as well.)

After the first label change, just return. (After setting up what you what to do later in another callback or block.)
 
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.