NSThread Timing Problem with GUI - objective-c

I have a simple thread that runs an infinite loop. Eventually, the loop will check for serial data at a USB port a few thousand times per second, but at the moment, it just writes something to a custom class of NSTextView once every second.
int i;
for (i=1; i>0; i++)
{
[lock lock];
[[self textStorage] replaceCharactersInRange:NSMakeRange([[self textStorage] length], 0) withString:#"test\n"];
[lock unlock];
sleep(1);
}
The issue is that it writes really sporadically. It will do one or two, then wait ten seconds and spit ten of them out at once. If I replace the writing line with an NSLog(#"test"), it logs at nice even intervals. I have another test method in the main thread that accepts input into a text field and puts it into the text view, and doing this seems to update the text view to include the child thread's most recent writes. There shouldn't be anything interfering with it at this point anyway, but I've locked everything everywhere just to be sure. Thanks in advance.

You should always perform operations that affect the UI from the main thread. You can have the child thread create a temporary object that holds the results, and then use performSelectorOnMainThread:withObject:waitUntilDone: to call another method that will do the necessary modifications on the main thread.
NSString * const MDResultKey = #"MDResult";
- (void)someMethod {
//
int i;
for (i=1; i>0; i++) {
// if necessary, create an object to hold results
NSDictionary *results = [NSDictionary
dictionaryWithObjectsAndKeys:#"test", MDResultKey, nil];
[self performSelectorOnMainThread:#selector(updateUIWithResults:)
withObject:results waitUntilDone:NO];
sleep(1);
}
}
- (void)updateUIWithResults:(NSDictionary *)results {
NSString *result = [results objectForKey:MDResultKey];
[lock lock]; // ?
[[self textStorage] replaceCharactersInRange:
NSMakeRange([[self textStorage] length], 0) withString:result];
[lock unlock]; // ?
}

I'd personally be pretty wary of calling anything on an NSTextStorage on a background thread. I think NSTextView reacts to any NSTextStorage changes, and any UI code on a non-main thread is going to have unpredictable problems.
I would just send the new string to the main thread and call -replaceCharactersInRange: there.

Related

objective c - for loop to fetch image from iCloud sequentially

on IOS, I need to get metadata for a selected set of images. But since the images are backed up to iCloud, sometimes it may immediately return (cached) and sometimes it may take a second or two.
The for loop runs through quickly, I am able to wait for all of the images to be processed before I move forward. But they still are being fetched in parallel. How do I make the for loop run sequentially by waiting for the block to finish before moving on to next image.
// Step 4: Fetch Details like Metadata for this batch
-(void) getDetailsForThisBatchOfNewAssets:(NSMutableArray*) mArrBatchOfNewAssets
withCompletionHandler:(blockReturnsMArrAndMArr) blockReturns{
NSLog(#"%s with arraySize of %lu",__PRETTY_FUNCTION__, (unsigned long)[mArrBatchOfNewAssets count] );
long assetCount = [mArrBatchOfNewAssets count];
NSMutableArray *mArrNewAssetsAndDetails = [[NSMutableArray alloc] init];
NSMutableArray *mArrNewAssetFailed = [[NSMutableArray alloc] init];
if(assetCount == 0){
NSLog(#" Looks like there are no NEW media files on the device.");
return;
}
else
NSLog(#"found %ld assets in all that need to be backed up", assetCount);
dispatch_group_t groupForLoopGetDetails = dispatch_group_create();
for(long i = 0 ; i < assetCount; i++){
PHAsset *currentAsset = [[mArrBatchOfNewAssets objectAtIndex:i] objectForKey:#"asset"];
NSString *mediaIdentifier = [[[currentAsset localIdentifier] componentsSeparatedByString:#"/"] firstObject];
[mArrIdentifiersInThisBatch addObject:mediaIdentifier];
dispatch_group_enter(groupForLoopGetDetails);
[mediaManager getDetailedRecordForAsset:currentAsset
withCompletionHandler:^(NSMutableDictionary *mDicDetailedRecord, NSMutableDictionary *mDicRecordForError)
{
if(mDicRecordForError[#"error"]){
[mArrNewAssetFailed addObject:mDicRecordForError];
NSLog(#"Position %ld - Failed to fetch Asset with LocalIdentifier: %#, adding it to Failed Table. Record: %#",i,[currentAsset localIdentifier], mDicRecordForError);
} else {
[mArrNewAssetsAndDetails addObject:mDicDetailedRecord ];
NSLog(#"Position %ld - added asset with LocalIdentifier to mArrNewAssetsAndDetails %#",i,[currentAsset localIdentifier]);
}
dispatch_group_leave(groupForLoopGetDetails);
}];
} // end of for loop that iterates through each asset.
dispatch_group_notify(groupForLoopGetDetails, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSLog(#"Completed gathering details for this batch of assets for backup. Count : %lu and failed Media count: %lu",(unsigned long)[mArrNewAssetsAndDetails count], (unsigned long)[mArrNewAssetFailed count]);
blockReturns(mArrNewAssetsAndDetails,mArrNewAssetFailed);
});
}
I have looked through several questions on SO on this topic but still have not figured out how to make this run sequentially.
I don't want to do a "self call" for this method, because I'm already doing "self call" at another place before I reach this method and my code is now growing into too many notifications and catches because of that.
Assuming the completion handler of getDetailedRecordForAsset is called on a different thread, you can use a semaphore to block execution (Note: DO NOT DO this on the main thread) inside the loop while waiting for the completion handler.
Remove the dispatch group stuff, then, inside the loop:
create a semaphore right before calling getDetailedRecordForAsset like so: dispatch_semaphore_t semaphore = dispatch_semaphore_create( 0);
as the last statement of the completion handler call dispatch_semaphore_signal( semaphore);
immediately after calling getDetailedRecordForAsset, wait for the end of the completion handler with dispatch_semaphore_wait( semaphore, DISPATCH_TIME_FOREVER);
So the structure of the loop will look like:
for (assets)
{
... // get current asset, media identifier as above
dispatch_semaphore_t semaphore = dispatch_semaphore_create( 0);
[mediaManager getDetailedRecordForAsset:currentAsset
withCompletionHandler:^(NSMutableDictionary *mDicDetailedRecord, NSMutableDictionary *mDicRecordForError)
{
... // handle error or add asset details as above
dispatch_semaphore_signal( semaphore);
}
dispatch_semaphore_wait( semaphore, DISPATCH_TIME_FOREVER);
}

Draw the contents of an array modified in the background

I have a background thread that runs and modifies the contents of a NSMutableArray within an object. This takes a long time to run (several hours) and I periodically want to draw the contents of an array within the drawRect of a NSView to check on progress and see the intermediate results.
My object has a protocol with a method called: didChange:
// How I start my background thread
[self performSelectorInBackground:#selector(startProcessing) withObject:nil];
- (void)startProcessing {
myObject.delegate = self;
[myObject start];
}
// My protocol implementation
- (void)myObjectDidChange:(myObjectClass *)sender {
[myView setNeedsDisplay:YES];
}
// My View's drawRect (pseudo code)
- (void)drawRect {
[myObject drawInContext:context];
}
All works, except that the NSMutableArray backing all this is being changed whilst the drawing takes place. How should I do this? Do I somehow pause the processing in the background thread whilst the update is taking place?
EDIT: This is the sort of display I am drawing (although much more complicated):
Any help appreciated.
If you are doing something in background thread and you want to update UI, its usually done on the main thread, so in your object did change you would do it, probably like this:
// My protocol implementation
- (void)myObjectDidChange:(myObjectClass *)sender {
dispatch_async(dispatch_get_main_queue(), ^{
[self drawRect]; //Or any drawing function you are trying to do
});
}
I have done it using NSLock to lock the outer loop of the start and the drawInContext methods. I am still not sure if this is the best approach and will not accept this answer for a few days in case there is a better answer out there.
- (void)start {
for(int i=0; i < MAX; i++) {
[self.updateLock lock];
....
[self.updateLock unlock];
}
}
- (void)drawInContext:(CGContextRef)context {
[self.updateLock lock];
...
[self.updateLock unlock];
}

Simple progress bar is not updating

I am implementing a Cocoa Application which is just a simple progress bar that starts when I press a button.
The situation is: I can see Animation is Start and Stop when I press the button, but the progress bar will not update the value.
I had also tried the solution mentioned here but it doesn't work:
How do I update a progress bar in Cocoa during a long running loop?
Can someone help to see where is the problem in my source code?
Here is my source.
SimpleProgressBar.m
#import "SimpleProgressBar.h"
#implementation SimpleProgressBar
#synthesize progressBar;
int flag=0;
-(IBAction)startProgressBar:(id)sender{
if(flag ==0){
[self.progressBar startAnimation:sender];
flag=1;
}else{
[self.progressBar stopAnimation:sender];
flag=0;
}
[self.progressBar displayIfNeeded];
[self.progressBar setDoubleValue:10.0];
int i=0;
for(i=0;i<100;i++){
NSLog(#"progr: %f",(double)i);
[self.progressBar setDoubleValue:(double)i];
[self.progressBar setNeedsDisplay:YES];
}
}
#end
SimpleProgressBar.h
#import < Foundation/Foundation.h >
#interface SimpleProgressBar : NSObject{
__weak NSProgressIndicator *progressBar;
}
#property (weak) IBOutlet NSProgressIndicator *progressBar;
-(IBAction)startProgressBar:(id)sender;
#end
Thank you very much for any helpful answer.
Update:
Here is my porting from the solution and it doesn't work:
SimpleProgressBar.m
#import "SimpleProgressBar.h"
#implementation SimpleProgressBar
#synthesize progressBar;
int flag=0;
-(IBAction)startProgressBar:(id)sender{
if(flag ==0){
[self.progressBar startAnimation:sender];
flag=1;
}else{
[self.progressBar stopAnimation:sender];
flag=0;
}
[self.progressBar displayIfNeeded];
[self.progressBar setDoubleValue:0.0];
void(^progressBlock)(void);
progressBlock = ^{
[self.progressBar setDoubleValue:0.0];
int i=0;
for(i=0;i<100;i++){
//double progr = (double) i / (double)100.0;
double progr = (double) i;
NSLog(#"progr: %f",progr);
dispatch_async(dispatch_get_main_queue(),^{
[self.progressBar setDoubleValue:progr];
[self.progressBar setNeedsDisplay:YES];
});
}
};
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue,progressBlock);
}
Update:
A couple of observations:
It strikes me that if you want to watch the NSProgressIndicator advance, you need to add a sleepForTimeInterval or else the for loop iterates so quickly that you won't see the progress indicator advance, but rather you'll just see it quickly end up in its final state. If you insert sleepForTimeInterval, you should see it progress:
self.progressIndicator.minValue = 0.0;
self.progressIndicator.maxValue = 5.0;
[self.progressIndicator setIndeterminate:NO];
self.progressIndicator.doubleValue = 0.001; // if you want to see it animate the first iteration, you need to start it at some small, non-zero value
for (NSInteger i = 1; i <= self.progressIndicator.maxValue; i++)
{
[NSThread sleepForTimeInterval:1.0];
[self.progressIndicator setDoubleValue:(double)i];
[self.progressIndicator displayIfNeeded];
}
Or, if you wanted to do the for loop on a background thread, and dispatch the updates back to the main queue:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (NSInteger i = 1; i <= self.progressIndicator.maxValue; i++)
{
[NSThread sleepForTimeInterval:1.0];
dispatch_async(dispatch_get_main_queue(), ^{
[self.progressIndicator setDoubleValue:(double)i];
[self.progressIndicator displayIfNeeded];
});
}
});
You are using startAnimation and stopAnimation, but according to the documentation each of these "does nothing for a determinate progress indicator," so these calls seem inappropriate for this situation.
My original answer, below, was predicated on the comment in the Threads and Your User Interface in the Threading Programming Guide, which says:
If your application has a graphical user interface, it is recommended that you receive user-related events and initiate interface updates from your application’s main thread. This approach helps avoid synchronization issues associated with handling user events and drawing window content. Some frameworks, such as Cocoa, generally require this behavior, but even for those that do not, keeping this behavior on the main thread has the advantage of simplifying the logic for managing your user interface.
But the answer below is (incorrectly) an iOS answer, so is not applicable.
Original answer:
Your for loop is running on the main thread, and thus UI updates won't appear until you yield back to the runloop. You're also going through that loop so quickly that even if you properly dispatched that to a background queue, you wouldn't experience the progress view changing as you iterate through your loop.
So, perform the loop on a secondary thread (e.g. via GCD or operation queue) and then dispatch UI updates back to the main thread, which is now free to do UI updates. So, using your theoretical example, you could do something like:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int i = 0; i < 100; i++)
{
[NSThread sleepForTimeInterval:0.1];
dispatch_async(dispatch_get_main_queue(), ^{
[self.progressView setProgress: (CGFloat) (i + 1.0) / 100.0 animated:YES];
});
}
});
Note, having a loop that updates the progress view only makes sense if you're doing something slow enough for you to see the progress view change. In your original example, you're just looping from 0 to 99, updating the progress view. But that happens so quickly, that there's no point in a progress view in that case. That's why my above example not only employs a background queue for the loop, but also added a slight delay (via sleepForTimeInterval).
Let's consider a more realistic application of the progress view. For example, let's say I had an array, urls, of NSURL objects that represent items to be downloaded from the server. Then I might do something like:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int i = 0; i < [urls count]; i++)
{
// perform synchronous network request (on main queue, you should only do asynchronous network requests, but on background queue, synchronous is fine, and in this case, needed)
NSError *error = nil;
NSURLResponse *response = nil;
NSURLRequest *request = [NSURLRequest requestWithURL:urls[i]];
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:response error:error];
// got it; now update my model and UI on the basis of what I just downloaded
dispatch_async(dispatch_get_main_queue(), ^{
[self.progressView setProgress: (CGFloat) (i + 1.0) / [array count] animated:YES];
// do additional UI/model updates here
});
}
});

Is it possible to check if an NSThread is blocked?

I've always been interested in how to write the following code to use it for unit testing:
Is it possible to extend NSThread with a method that would check if a particular thread is blocked?
Right now I'am working with NSCondition: Xcode shows me the chain which is called by -wait to block the thread:
[NSCondition wait]
pthread_cond_wait$UNIX2003
_pthread_cond_wait
__psynch_cvwait
Besides checking the locks done by NSCondition, if it is even possible, I would highly appreciate method working also for any other blocking capabilities (dispatch semaphores, condition locks, sleeping threads and so on, ) - I have no idea about Objective-C internals, if maybe they could be catched by one method or each needs its own.
Here is a simple example of what I would like to achieve. The mysterious method is called isBlocked.
// Some test case
// ...
__block NSThread *thread;
NSCondition *condition = [NSCondition alloc] init];
dispatch_async(someQueue(), ^{
thread = NSThread.currentThread;
[condition lock];
[condition wait];
[condition unlock];
});
while(1) {
NSLog(#"Thread is blocked: %d", thread.isBlocked);
}
Note: I am not good at C and all this low-level POSIX stuff, so, please, be verbose.
Note 2: I am interested in solutions working for dispatch queues as well: if someone can show me how to test the fact that someQueue() is blocked by -[NSCondition wait] (not the fact that it is going to be blocked (fx hacking some code before -[condition wait] is run and the block is set), but the fact that thread/queue is blocked), I will accept this as an answer as much like I would do with working -[NSThread isBlocked] method.
Note 3: Suspecting bad news like "it is not possible", I claim that any ideas about catching the fact that -[condition wait] was run and the thread was set blocked (see Note 2) are appreciated and can be also accepted as an answer!
UPDATE 1 in address to the nice answer by Richard J. Ross III. Unfortunately, his answer does not work in my original example, the version which is closer to my real work (though it does not differ much from the example I've initially provided - sorry that I didn't include it in the first edition of the question):
// Example
// Here I've bootstrapped Richard's isLocking categories for both NSThread and NSCondition
// ...
// somewhere in SenTesting test case...
__block NSThread *thread;
NSCondition *condition = [NSCondition alloc] init];
__block BOOL wePassedBlocking = NO;
dispatch_async(someQueue(), ^{
thread = NSThread.currentThread;
[condition lock];
[condition wait];
[condition unlock];
wePassedBlocking = YES; // (*) This line is occasionally never reached!
});
while(!thread.isWaitingOnCondition); // I want this loop to exit after the condition really locks someQueue() and _thread_ __.
// sleep(1);
[condition lock];
[condition broadcast]; // BUT SOMETIMES this line is called before -[condition wait] is called inside someQueue() so the entire test case becomes blocked!
[condition unlock];
while(!wePassedBlocking); // (*) And so this loop occasionally never ends!
If I uncomment sleep(1) test begins working very stable without any occasional locks!
This leads us to the problem, that Richard's category does set state exactly one line before the actual blocking is done meaning that sometimes test case's main thread catches this new state before we actually have someQueue/thread blocked because Richard's code does not contain any synchronization mechanisms: #synchronized, NSLock or something like that! I hope I am making a clear explanation of this tricky case. For anyone who has doubts about what I've posted here, I would say that I have been also experimenting with multiple queues and even more complex cases, and if needed I'm ready to provide more examples. Richard, thanks again for your effort, let's think more together, if you understand these my points!
UPDATE 2
I see the dead-end paradox: obviously, to really set the state of waitingOnCondition we need to wrap this state's change inside some synchronization closures, but the problem is that the closing one, unlocking the synchronization lock, should be called after -[condition wait], but it can't, because the thread is already blocked. Again, I hope I am describing it pretty clear.
Here you go! It won't detect threads being waited on by anything other than -[NSCondition wait], but it could easily be extended to detect other kinds of waiting.
It's probably not the best implementation out there, but it does in fact work, and will do what you need it to.
#import <objc/runtime.h>
#implementation NSThread(isLocking)
static int waiting_condition_key;
-(BOOL) isWaitingOnCondition {
// here, we sleep for a microsecond (1 millionth of a second) so that the
// other thread can catch up, and actually call 'wait'. This time
// interval is so small that you will never notice it in an actual
// application, it's just here because of how multithreaded
// applications work.
usleep(1);
BOOL val = [objc_getAssociatedObject(self, &waiting_condition_key) boolValue];
// sleep before and after so it works on both edges
usleep(1);
return val;
}
-(void) setIsWaitingOnCondition:(BOOL) value {
objc_setAssociatedObject(self, &waiting_condition_key, #(value), OBJC_ASSOCIATION_RETAIN);
}
#end
#implementation NSCondition(isLocking)
+(void) load {
Method old = class_getInstanceMethod(self, #selector(wait));
Method new = class_getInstanceMethod(self, #selector(_wait));
method_exchangeImplementations(old, new);
}
-(void) _wait {
// this is the replacement for the original wait method
[[NSThread currentThread] setIsWaitingOnCondition:YES];
// call the original implementation, which now resides in the same name as this method
[self _wait];
[[NSThread currentThread] setIsWaitingOnCondition:NO];
}
#end
int main()
{
__block NSCondition *condition = [NSCondition new];
NSThread *otherThread = [[NSThread alloc] initWithTarget:^{
NSLog(#"Thread started");
[condition lock];
[condition wait];
[condition unlock];
NSLog(#"Thread ended");
} selector:#selector(invoke) object:nil];
[otherThread start];
while (![otherThread isWaitingOnCondition]);
[condition lock];
[condition signal];
[condition unlock];
NSLog(#"%i", [otherThread isWaitingOnCondition]);
}
Output:
2013-03-20 10:43:01.422 TestProj[11354:1803] Thread started
2013-03-20 10:43:01.424 TestProj[11354:1803] Thread ended
2013-03-20 10:43:01.425 TestProj[11354:303] 0
Here is a solution using dispatch_semaphore_t
PGFoo.h
#import <Foundation/Foundation.h>
#interface PGFoo : NSObject
- (void)longRunningAsynchronousMethod:(void (^)(NSInteger result))completion;
#end
PGFoo.m
#import "PGFoo.h"
#implementation PGFoo
- (void)longRunningAsynchronousMethod:(void (^)(NSInteger))completion {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(5);
completion(1);
});
}
#end
Test Methods
- (void)testThatFailsBecauseItIsImpatient {
PGFoo *foo = [[PGFoo alloc] init];
__block NSInteger theResult = 0;
[foo longRunningAsynchronousMethod:^(NSInteger result) {
theResult = result;
}];
STAssertEquals(theResult, 1, nil);
}
- (void)testThatPassesBecauseItIsPatient {
PGFoo *foo = [[PGFoo alloc] init];
__block NSInteger theResult = 0;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[foo longRunningAsynchronousMethod:^(NSInteger result) {
theResult = result;
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
STAssertEquals(theResult, 1, nil);
}
By using a dispatch_semaphore_t you can "track" whether a thread that is waiting on that semaphore is blocked. For every call of dispatch_semaphore_wait the semaphore's count is decremented and the thread waits until a call of dispatch_semaphore_signal is made, when dispatch_semaphore_signal is called the semaphore's count is incremented, if the count is incremented to a value greater than -1 the thread continues.
This solution fails to answer your question about checking whether an NSThread is "blocked" but I think it provides what you are reaching for, assuming you're not reaching to check on NSThread instances that are maintained within an existing framework.

How can I make a method stall for a fixed amount of time?

I have an app that calls a sometimes-fast, sometimes-slow method. I know an upper bound for how long it will take (2 seconds). I'd like to set a timer to start when the method is called, run the code, but then not produce the output until 2 seconds has passed, no matter how long it actually takes. That way the user perceives the action as always taking the same amount of time. How can I implement this?
What I would like is something along the lines of this:
-(IBAction)doStuff {
// START A TIMER, LOOK BUSY
[activityIndicator startAnimating];
... real work happens here ...
... NSString *coolString gets assigned ...
// WHEN TIMER == 2 SECONDS, REVEAL COOLNESS
[activityIndicator stopAnimating];
[textField setText:coolString];
}
There are a couple of ways to delay an action in Cocoa. The easiest may be to use performSelector:withObject:afterDelay:. This method sets up a timer for you and calls the specified method when the time comes. It's an NSObject method, so your objects all get it for free.
The tricky part here is that the first method will block the main thread, so you need get it onto a background thread, and then get back to the main thread in order to update the UI. Here's a stab at it:
// Put the method which will take a while onto another thread
[self performSelectorInBackground:#selector(doWorkForUnknownTime)
withObject:nil];
// Delay the display for exactly two seconds, on the main thread
[self performSelector:#selector(displayResults)
withObject:nil
afterDelay:2.0];
- (void)doWorkForUnknownTime {
// results is an ivar
results = ...; // Perform calculations
}
- (void)displayResults {
if( !results ){
// Make sure that we really got results
[self performSelector:#selector(displayResults:)
withObject:nil
afterDelay:0.5];
return;
}
// Do the display!
}
The only other thing I can think of is to store the time that the "work" method is called in an NSDate, and check how long it took when you get the results. If it isn't two seconds yet, sleep the background thread, then call back to the main thread when you're done.
[self performSelectorInBackground:#selector(doWorkForUnknownTime:)
withObject:[NSDate date]];
- (void)doWorkForUnknownTime:(NSDate *)startTime {
// All threads must have an autorelease pool in place for Cocoa.
#autoreleasepool{
// This will take some time
NSString * results = ...; // Perform calculations
NSTimeInterval elapsedTime = [[NSDate date] timeIntervalSinceDate:startTime];
if( elapsedTime < 2.0 ){
// Okay to do this to wait since we're on a background thread,
// although not ideal; sleeping threads are kind of wasteful.
// Try not to do this a lot.
sleep(2.0 - elapsedTime);
}
// Don't forget to retain results on the main thread!
[self performSelectorOnMainThread:#selector(displayResults:)
withObject:results
waitUntilDone:YES];
// [results release]; // if necessary
}
}
[self performSelector:#selector(myfunc) withObject: afterDelay:];
should help.
-(IBAction)doStuff {
// START A TIMER, LOOK BUSY
[activityIndicator startAnimating];
... real work happens here ...
... NSString *coolString gets assigned ...
// WHEN TIMER == 2 SECONDS, REVEAL COOLNESS
[self performSelector:#selector(revealCoolnessWithString:) withObject:coolString afterDelay:2];
}
- (void)revealCoolnessWithString:(NSString *)coolString
{
[activityIndicator stopAnimating];
[textField setText:coolString];
}
Hope this helps