In short, I would like to, in Objective-C cocoa, program something that functions the same way as the following Java pseudocode:
public class MainClass
{
public void mainmethod() //Gets called at start of program
{
UILabel label = CreateAButton();
new DaemonClass(label).start();
//Do things without being interrupted by the Daemon class sleeping or lagging
}
}
public class DaemonClass extends Thread
{
public UILabel label;
public DaemonClass(UILabel lbl)
{
setDaemon(true);
label = lbl;
}
public void run()
{
int i = 0;
while(true)
{
i++;
i = i%2;
UILabel.setText("" + i);
Thread.sleep(1000);
}
}
}
In other words... I'd like to spawn a daemon thread that can be as slow as it likes, without interrupting the progress or speed of any other threads, INCLUDING, the main one.
I have tried using things like the Dispatch Queue, as well as NSThread.
When using either of these, I tried to create a simple label-changer thread that toggled the label's text from 1 to 0, indefinitely. It appeared to me, the user, to constantly be locked either at 1, or 0, randomly chosen at startup.
When using either of these, and attempting to use [NSThread sleepForTimeInterval:1];, the thread would stop executing all together after the sleepForTimeInterval call.
Furthermore, having skimmed the docs, I picked up on the fact that the run loop is not called while [NSThread sleep... is sleeping!
If it is any help, I was invoking my threads from the - (void)viewDidLoad; method.
My question for you is:
How do I stop [NSThread sleepForTimeInterval:1]; from crashing my thread, OR:
How do I start a daemon thread that invokes a method or code block (preferably a code block!)
P.S. if it makes any difference, this is for iOS
The reason for the problems you've seen is most likely that UIKit isn't thread-safe, i.e. you can only use a UILabel from the main thread. The easiest way to do that is to enqueue a block on the main queue (which is associated with the main thread) using GCD:
dispatch_async(dispatch_get_main_queue(), ^{
myLabel.text = #"whatever";
});
First of all you cannot manage UIKit in the background threads. In order to set the text to UILabel you need to use main thread.
Judging by the type of task you want to achieve, you should use NSTimer. You can set the time interval it should be called and stop and resume it anytime.
#property (strong, nonatomic) NSTimer *timer; //in your .h file
- (void)startChangingLabelText {
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(checkTime:)
userInfo:nil
repeats:YES];
}
- (void)stopChangingLabelText {
[timer invalidate], self.timer = nil;
}
- (void)checkTime:(NSTimer *)timer {
int rand = arc4random() % 2;
if (rand)
label.text = #"true";
else
label.text = #"false";
}
Related
I'm not sure how to correctly use GCD in a run loop situation where the thread might need to be stopped. The problem starts from the outset, and how or where to use CGEventCallback (which is absent from my code). The stop button won't stop the loop, and I don't think my dispatch queue is setup properly -- along with the while loop creating a huge lag.
I've read top question-answers from the search, like this and this, but one is for iOS and the other isn't relevant. Could someone show me how to properly do this?
my code:
// .h
#import <Cocoa/Cocoa.h>
#interface AppDelegate : NSObject <NSApplicationDelegate> {
IBOutlet NSTextField *textFieldBox;
IBOutlet NSButton *stop;
}
#property (assign) IBOutlet NSWindow *window;
- (void)stop;
#end
// .m
#import "AppDelegate.h"
#implementation AppDelegate
BOOL isActive = FALSE;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
[self mainMethod];
}
- (void)mainMethod {
NSLog(#"loop started");
isActive = TRUE;
[self theArbitraryNonCompliantLoop];
NSLog(#"doing other stuff");
}
- (void)stop {
isActive = FALSE;
return;
}
- (void)theArbitraryNonCompliantLoop {
dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(backgroundQueue, ^{
while (isActive) {
for (NSUInteger i = 0; i < 1000000; i++) {
[textFieldBox setStringValue:[NSString stringWithFormat:#"%lu",(unsigned long)i]];
}
}
});
}
#end
Ignoring the name, the for loop needs to test isActive as well. That will solve the latency issue.
The UI update needs to be done on the main thread which is easy because you can just schedule a block on the main queue to do it.
- (void)theArbitraryNonCompliantLoop {
dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(backgroundQueue, ^{
while (isActive)
{
for (NSUInteger i = 0; isActive && i < 1000000; i++)
{
dispatch_async(dispatch_get_main_queue(),
^{ [textFieldBox setStringValue:[NSString stringWithFormat:#"%lu",(unsigned long)i]] };
}
}
});
}
There are still some issues here. I think, as it stands it will flood the main thread's run loop with events, so some throttling will be required. You might also consider some synchronisation for the inActive instance variable in case the compiler optimises it by pulling it into a register at the beginning of the method. Also, it will be subject to race conditions thanks to caching etc.
Big mistake: You are changing a UI element on a background thread. That will cause all kinds of problems. Don't do that.
You seem to be quite confused what a runloop is. You are also trying to confuse people by calling something "theRunLoop" that just does stuff on a background thread. Your code has nothing to do with the runloop, and until you understand what a runloop is, better keep away from it.
Why would you call an arbitrary method theRunLoop?
Either way, quoting Run Loops (Threading Programming Guide):
Both Cocoa and Core Foundation provide run loop objects to help you
configure and manage your thread’s run loop. Your application does not
need to create these objects explicitly; each thread, including the
application’s main thread, has an associated run loop object. Only
secondary threads need to run their run loop explicitly, however. The
app frameworks automatically set up and run the run loop on the main
thread as part of the application startup process.
My guess would be that your while loop is still on its first run. The 1000000 for loop is probably taking too long which is why it still seems like the loop is still running. To test it out put an NSLog after your for loop to see if it has exited it after you changed isActive to false.
I want to manipulate views while in a for loop. I manipulate a view in the for loop, then the operations for the view are done at once after the for loop has ended. I tried to use other threads like GCD, but I noticed that a view is in the main thread. The operations are back to the main thread and they are put off after the for loop finishes.
What I want to do is update UITextView's text while in the for loop. If I can't operate the for loop in another thread, how can I do that? Are there other ways to do that?
Solution 1: Use a timer
In order to progressively add text to a textview, you can use an NSTimer.
Requirements
in your interface - the following ivars or properties:
UITextView *textView;
NSNumber *currentIndex;
NSTimer *timer;
NSString *stringForTextView;
Assuming the string is created and the textview is set up, you can create a function to create the timer and kick it off:
- (void) updateTextViewButtonPressed
{
timer = [NSTimer scheduledTimerWithTimeInterval:.5
target:self
selector:#selector(addTextToTextView)
userInfo:nil
repeats:YES];
}
- (void) addTextToTextView
{
textView.text = [string substringToIndex:currentIndex.integerValue];
currentIndex = [NSNumber numberWithInt:currentIndex.integerValue + 1];
if(currentIndex.integerValue == string.length)
{
[_timer invalidate];
timer = nil;
}
}
This is a basic working implementation, and you can vary it to pass in the string as userInfo for the timer, if it is not present at the class level. Then you could access it in your addTextToTextView selector with sender.userInfo. You can also adjust the timer interval and how exactly the text is added. I used half a second and character by character concatenation as an example.
Solution 2: Use a loop
Requirements
NSString *string
UITextview *textView
- (void) updateTextViewButtonPressed
{
// perform the actual loop on a background thread, so UI isn't blocked
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^()
{
for (int i = 0; i < string.length; i++)
{
// use main thread to update view
dispatch_async(dispatch_get_main_queue(), ^()
{
textView.text = [string substringToIndex:i];
});
// delay
[NSThread sleepForTimeInterval:.5];
}
});
}
I need to do a possibly long series of calls that must occur on the main thread (because otherwise UIKit will balk). By "long" I mean 10,000 operations lasting .1 second each on an iPad 3.
Obviously, It's probably not the best idea to just loop through all of them at once.
I don't know how to execute all these on the main thread while leaving enough breathing room to keep UIKit responsive and the watchdog asleep (ie. not get terminated for hogging the run loop).
Does anybody have an idea? I will be targeting iOS 5.
Specifically what I'm trying to do is cache UITextPositions, because a UITextView is apparently taking a non-cached, iterative approach at getting UITextPositions, which means it is very, very slow at doing positionFromPosition:textview.beginningOfDocument offset:600011, but much faster at getting positionFromPosition:aPositionAt600000 offset:11. In fact, in my test case, the former takes over 100 seconds (on the main thread!), while the latter is virtually instantaneous.
Why do you want to do it on the main thread? The typical answer is to do these operations on a background thread, and send UI updates back to the main thread. For example, you could use Grand Central Dispatch:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// do my time consuming task and everytime it wants to update the UI,
// it should dispatch that back to the main queue, e.g.
for (NSInteger i = 0; i < 10000; i++)
{
// do my background work
// now update the UI
dispatch_async(dispatch_get_main_queue(), ^{
// update the UI accordingly
});
}
});
Update:
It sounds like you have to do this in the foreground, so perhaps using a NSTimer might be better. I'm not a big NSTimer guy, but it might look something like the following.
First, make sure you have a class instance variable for it:
NSTimer *_timer;
Next, you can initialize it with:
- (void)startTimer
{
_timer = [NSTimer timerWithTimeInterval:0.0 target:self selector:#selector(timerCallback:) userInfo:nil repeats:YES];
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
[runloop addTimer:_timer forMode:NSDefaultRunLoopMode];
}
This will then invoke the timerCallback, perhaps processing a single UITextPosition on each invocation:
- (void)timerCallback:(NSTimer*)theTimer
{
BOOL moreTextPositionsToCalculate = ...;
if (moreTextPositionsToCalculate)
{
// calculate the next UITextPosition
}
else
{
[self stopTimer];
}
}
and when you're done, you could stop your timer like so:
- (void)stopTimer
{
[_timer invalidate];
_timer = nil;
}
In other words, if I have a process that continuously runs, but users can change parameters on the GUI that effect the process operation characteristics, where is a better place to put the process, in a NSThread or NSTimer?
While NSThread and NSTimer are two separate things for different needs, lets compare the two functions:
Using NSThread:
-(void) doSomethingEverySecond {
__block int cumValue = 0; // cumulative value
__block void(^execBlock)() = ^{
while (1)
{
#try
{
// some code here that might either A: call continue to continue the loop,
// or B: throw an exception.
cumValue++;
NSLog(#"Cumulative Value is: %i", cumValue);
if (cumValue == 5)
return;
}
#finally
{
[NSThread sleepForTimeInterval:1];
}
}
};
[NSThread detachNewThreadSelector:#selector(invoke) toTarget:[execBlock copy] withObject:nil];
}
Using NSTimer:
-(void) doSomethingEverySecond {
__block NSTimer *timer = nil;
__block int cumValue = 0;
__block void (^execBlock)() = ^{
cumValue++;
NSLog(#"Cumulative Value is: %i", cumValue);
if (cumValue == 5)
[timer invalidate];
};
timer = [NSTimer scheduledTimerWithTimeInterval:1 target:[execBlock copy] selector:#selector(invoke) userInfo:nil repeats:YES];
}
Now, if we want to something only once, NSThread is the way to go, as shown in the following:
-(void) doSomethingOnce {
__block void (^execBlock)() = ^{
NSLog(#"Doing something that could take a LONG time!");
};
[NSThread detachNewThreadSelector:#selector(invoke) toTarget:[execBlock copy] withObject:nil];
}
Now, for the NSTimer variant:
-(void) doSomethingOnce {
__block void (^execBlock)() = ^{
NSLog(#"Doing something that could take a LONG time!");
};
[NSTimer scheduledTimerWithTimeInterval:0 target:[execBlock copy] selector:#selector(invoke) userInfo:nil repeats:NO];
}
The reason for this is that we have complete control over the thread while using a NSThread, but if using a NSTimer, than we are executing inside a NSRunLoop which may freeze the UI if any heavy lifting is done inside. THAT is the advantage of a NSThread over a NSTimer.
You are also guaranteed that a NSThread that is detached is executed immediately, with a NSTimer, which is based on NSRunLoop, cannot, as it may or may not be able to execute immediately.
There is a 3rd alternative (well technically a fourth too, pthreads, but I will ignore that for now), GCD, but I would suggest you RTFM on that, as it's too broad of a topic to cover in this post.
NSThread and NSTimer are not mutually exclusive or replacements for one another. NSThread allows you to control a thread of execution and NSTimer is just that, a timer.
I assume you mean running an NSTimer on a background thread rather than on the main thread? That is generally a good idea so that the timer has less potential to be delayed by things occurring on your main thread (such as user interaction with the application).
You should read Apple's Threading Programming Guide.
I've been searching for and attempting to program for myself, an answer to this question.
I've got a secondary thread running inside my mainView controller which is then running a timer which counts down to 0.
Whilst this timer is running the secondary thread which initiated the timer should be paused/blocked whatever.
When the timer reaches 0 the secondary thread should continue.
I've Experimented with both NSCondition and NSConditionLock with no avail, so id ideally like solutions that solve my problem with code, or point me to a guide on how to solve this. Not ones that simply state "Use X".
- (void)bettingInit {
bettingThread = [[NSThread alloc] initWithTarget:self selector:#selector(betting) object:nil];
[bettingThread start];
}
- (void)betting {
NSLog(#"betting Started");
for (int x = 0; x < [dealerNormalise count]; x++){
NSNumber *currSeat = [dealerNormalise objectAtIndex:x];
int currSeatint = [currSeat intValue];
NSString *currPlayerAction = [self getSeatInfo:currSeatint objectName:#"PlayerAction"];
if (currPlayerAction != #"FOLD"){
if (currPlayerAction == #"NULL"){
[inactivitySeconds removeAllObjects];
NSNumber *inactivitySecondsNumber = [NSNumber numberWithInt:10];
runLoop = [NSRunLoop currentRunLoop];
betLooper = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(betLoop) userInfo:nil repeats:YES];
[runLoop addTimer:[betLooper retain] forMode:NSDefaultRunLoopMode];
[runLoop run];
// This Thread needs to pause here, and wait for some input from the other thread, then continue on through the for loop
NSLog(#"Test");
}
}
}
}
- (void)threadKiller {
[betLooper invalidate];
//The input telling the thread to continue can alternatively come from here
return;
}
- (void)betLoop {
NSLog(#"BetLoop Started");
NSNumber *currentSeconds = [inactivitySeconds objectAtIndex:0];
int currentSecondsint = [currentSeconds intValue];
int newSecondsint = currentSecondsint - 1;
NSNumber *newSeconds = [NSNumber numberWithInt:newSecondsint];
[inactivitySeconds replaceObjectAtIndex:0 withObject:newSeconds];
inacTimer.text = [NSString stringWithFormat:#"Time: %d",newSecondsint];
if (newSecondsint == 0){
[self performSelector:#selector(threadKiller) onThread:bettingThread withObject:nil waitUntilDone:NO];
// The input going to the thread to continue should ideally come from here, or within the threadKiller void above
}
}
You can't run a timer on a thread and sleep the thread at the same time. You may want to reconsider whether you need a thread at all.
There's a few things that need to be pointed out here. First, when you schedule your timer:
betLooper = [NSTimer scheduledTimerWithTimeInterval:1
target:self
selector:#selector(betLoop:)
userInfo:nil
repeats:YES];
it's added to and retained by the current run loop by that method, so you don't need to do that manually. Just [myRunLoop run]. Your timer's selector argument is also invalid -- a timer's "target method" needs to look like this:
- (void)timerFireMethod:(NSTimer *)tim;
This also means that you don't need to retain the timer if all you want to do is invalidate it, since you will have a reference to it from inside that method.
Second, it's not clear what you mean by "this thread needs to sleep to wait for input". When you schedule that timer, the method (betLoop) is called on the same thread. If you were to sleep the thread, the timer would stop too.
You seem to be a little mixed up regarding methods/threads. The method betting is running on your thread. It is not itself a thread, and it's possible to call other methods from betting that will also be on that thread. If you want a method to wait until another method has completed, you simply call the second method inside the first:
- (void)doSomethingThenWaitForAnotherMethodBeforeDoingOtherStuff {
// Do stuff...
[self methodWhichINeedToWaitFor];
// Continue...
}
I think you just want to let betting return; the run loop will keep the thread running, and as I said, the other methods you call from methods on the thread are also on the thread. Then, when you've done the countdown, call another method to do whatever work needs to be done (you can also invalidate the timer inside betLoop:), and finalize the thread:
- (void)takeCareOfBusiness {
// Do the things you were going to do in `betting`
// Make sure the run loop stops; invalidating the timer doesn't guarantee this
CFRunLoopStop(CFRunLoopGetCurrent());
return; // Thread ends now because it's not doing anything.
}
Finally, since the timer's method is on the same thread, you don't need to use performSelector:onThread:...; just call the method normally.
You should take a look at the Threading Programming Guide.
Also, don't forget to release the bettingThread object that you created.
NSThread has a class method + (void)sleepForTimeInterval:(NSTimeInterval)ti. Have a look at this :).
NSThread Class Reference