1

I have both a UILabel and UIProgressView that I'm giving multiple updates to, but nothing is rendered until my communication object is finished.

I've got this ViewController:

@interface UpdateServerViewController : UIViewController <TaskController> {
    AgentAssetManager * agentAssetManager;
}

@property (strong, nonatomic) IBOutlet UIButton * updateNowButton;
@property (strong, nonatomic) IBOutlet UILabel * statusLabel;
@property (strong, nonatomic) IBOutlet UILabel * lastUpdateSuccessTimeLabel;
@property (strong, nonatomic) IBOutlet UILabel * lastUpdateAttemptTimeLabel;
@property (strong, nonatomic) IBOutlet UILabel * lastUpdateResultLabel;
@property (strong, nonatomic) IBOutlet UIProgressView * progressView;

- (IBAction) updateNow:(id) sender;
- (void) updateLabels;
- (void) scheduleInNextRunloopSelector:(SEL)selector;
- (void) taskSetStatus:(NSString *)status;
- (void) taskFinishedSuccessfully;
- (void) taskFinishedUnSuccessfully;

@end

The updateNow method calls AgentAssetManager:

[self scheduleInNextRunloopSelector:@selector(updateTime:)];

 McDbg(m, d+1, @"Calling AgentAssetManager sendToServer");
//    [statusLabel setText:@"Contacting Server"];
[agentAssetManager sendToServer:true taskController:self];

The AgentAssetManager sendToServer is performs a series of steps and sends notifications back to UpdateServerViewController via the TaskController protocol:

- (void) sendToServer:(Boolean) notifyUser 
   taskController:(id<TaskController>) taskCtlr
{
char *          m = "AgentAssetManager.sendToServer";
int             d = 0;
int             steps = 6; // Total number of steps

McDbg(m, d, @"Send Asset data to server");
McDbg(m, d+1, @"Notify User about status/errors=%s",
      (notifyUser) ? "YES" : "NO");

if (taskCtlr != nil) {
    [taskCtlr taskSetProgress:(float)1/(float)steps];
    [taskCtlr taskSetStatus:@"Checking if server is reachable"];
}

McDbg(m, d+1, @"SLEEPING");
sleep(5); // XXX tmp debug

ServerStatusManager *statusMgr = [[ServerStatusManager alloc] init];
Boolean ready = [statusMgr isServerReady];
McDbg(m, d+1, @"Server Status ready=%s", (ready) ? "YES" : "NO");
if (!ready) {
    NSString *msg = @"Server could not be reached";
    McLogWarn(m, @"Server Not ready %@", [statusMgr serverUrlBase]);
    [self updateResult:false];
    if (notifyUser)
        [McUiError showMessage:msg];
    if (taskCtlr) {
        [taskCtlr taskSetStatus:msg];
        [taskCtlr taskFinishedUnSuccessfully];
    }
    return;
}

McDbg(m, d+1, @"Getting Asset data");
if (taskCtlr != nil) {
    [taskCtlr taskSetProgress:(float)2/(float)steps];
    [taskCtlr taskSetStatus:@"Gathering data"];
}

... etc ....

Here are UpdateServerViewController taskSetProgress and taskSetStatus:

- (void) taskSetStatus:(NSString *) status
{
char *          m = "UpdateServerViewController.taskSetStatus";
int             d = 0;

McDbg(m, d, @"Status=<%@>", status);
[statusLabel setText:status];
McDbg(m, d+1, @"Finished");
}

- (void) taskSetProgress:(float) percent
{
[progressView setHidden:false];
[progressView setProgress:percent animated:YES];
}

The debug output clearly shows that UpdateServerViewController taskSetProgress and taskSetStatus are called when they are suppose to. The problem is that the new set values don't take effect until the entire agentAssetManager.updateNow method is complete.

Based on searches on this site for similar UILabel problems I added a timer method:

- (void) scheduleInNextRunloopSelector:(SEL)selector 
{
char *          m = "UpdateServerViewController.scheduleInNext";
int             d = 0;

McDbg(m, d, @"Starting");
NSDate *fireDate = [[NSDate alloc] initWithTimeIntervalSinceNow:0.5]; // 500 ms
NSTimer *timer = [[NSTimer alloc]
                  initWithFireDate:fireDate interval:0.5 target:self
                  selector:selector userInfo:nil repeats:YES];

[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];

}

- (void)updateTime:(NSTimer *)timer 
{
char *          m = "UpdateServerViewController.updateTime";
int             d = 0;

McDbg(m, d, @"Starting");
[statusLabel setText:@"XXX Timer called"];
}

The problem is the same with or without the timer. With the timer, the debug shows that the timer is scheduled before agentAssetManager.updateNow is called, but the updateTime method is not called until agentAssetManager.updateNow finishes. This is even with a sleep(5) in updateNow to try to avoid a thread race condition.

Debugging shows that all of UpdateServerViewController and AssetAgentManager seems to run in thread 1. I don't do any NSRunLoop other than the above mentioned timer.

I must be missing something basic hear. Any help would really be appreciated!

  • What are you using to do the actual server communication? If you're using any of the ___withContentsOfURL methods they will block the main thread, and no UI updates will occur. – jsd Feb 24 '12 at 23:16
  • I'm using RestKit for the server communications. – Mike Cooper Feb 24 '12 at 23:23

1 Answers1

0

Make sure to make any ui changes in the main thread.

You may try something like this:

dispatch_sync(dispatch_get_main_queue(), ^{
        [statusLabel setText:@"XXX Timer called"];
    }
);
Julien May
  • 2,011
  • 15
  • 18
  • If I use dispatch_sync() as you suggest then the entire thread hangs/blocks. Very strange. I'm beginning to think RestKit (run from AgentAssetManager) is doing something behind the scenes. – Mike Cooper Feb 25 '12 at 22:05