1

我是 iOS 编程的新手,我已经找不到答案了。在 Xcode 5 中,我正在迭代一个数组,并尝试在标签发生变化时使用它们更新的值。

这是.h文件...

#import <UIKit/UIKit.h>
@interface ViewController : UIViewController
@property (strong, nonatomic) NSArray *currentNumber;
@property (strong, nonatomic) IBOutlet UILabel *showLabel;
- (IBAction)start;
@end 

这是.m文件的主要部分......

#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad
{
    [super viewDidLoad];
    self.currentNumber = [NSArray arrayWithObjects:@"1", @"2", @"3", @"4", nil];
}

这就是它变得棘手的地方......

以下工作完美......

- (IBAction)start {
    self.showLabel.text = [NSString stringWithFormat:@"new text"];
}
@end

就像这...

- (IBAction)start {
    for (NSString *p in self.currentNumber) {
        NSLog(@"%@", p);
        sleep(3);
    }
}
@end

但是当我用设置 .text 属性替换 NSLog 时,它“失败”。时间仍然会发生,并且标签会更新为数组中的最后一项......

- (IBAction)start {
    for (NSString *p in self.currentNumber) {
        self.showLabel.text = [NSString stringWithFormat:@"%@", p];
        sleep(3);
    }
}
@end

最后一点奇怪的是,如果我使用 NSLog,并尝试在调用“for”循环之前更改 .text 属性,则文本更改将被忽略,直到循环完成后...

- (IBAction)start {
    self.showLabel.text = [NSString stringWithFormat:@"5"];
    for (NSString *p in self.currentNumber) {
        NSLog(@"%@", p);
        sleep(3);
    }
}
@end

我错过了什么?

(如果你想查看源文件,你可以在https://github.com/lamarrg/iterate

4

2 回答 2

2

正如您所意识到的,UI 只会在主线程处理事件时更新。在一个循环中,它不会。

有几种方法可以解决这个问题。

最简单的是在后台线程中执行循环。但是有一个问题:这将允许用户继续与您的 UI 交互。而且,UI 只能从主线程更新。

您需要将您的工作分派到后台,然后让后台将您的工作分派回主线程。

这听起来很复杂,而且确实如此。值得庆幸的是,Apple 为 Objective-C 添加了块和 Grand Central Dispatch。您可以使用它们来分解代码块并确保它们在正确的线程上执行。

- (IBAction)start {
    [self disableMyUI];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_NORMAL, 0), ^{
        // this code will be executed "later", probably after start has returned.
        // (in all cases, later should be considered "soon but not immediately.")
        for (NSString *p in self.currentNumber) {
            dispatch_async(dispatch_get_main_queue(),^{
                // this code will be executed "later" by the main loop.
                // You may have already moved on to the next thing, and even
                // dispatched the next UI update.
                // Don't worry; the main queue does things in order.
                self.showLabel.text = [NSString stringWithFormat:@"%@", p];
            });
            sleep(3); // do your heavy lifting here, but keep in mind:
                      // you're on a background thread.
        }
        dispatch_async(dispatch_get_main_queue,^{
            // this occurs "later," but after other all other UI events queued
            // to the main queue.
            [self enableMyUI];
        });
    }
    // this line of code will run before work is complete
}

你必须写disableMyUIenableMyUI; 确保他们禁用所有内容(如果您使用导航,包括后退按钮,如果您使用标签栏控制器,则包括标签栏等)。

解决此问题的另一种方法是使用NSTimer. 但是,如果你这样做,你仍然在主线程上做你的工作。如果你可以将你的工作分成可预测的小块,它会起作用,但你最好在后台线程上做。

要记住的一件事:虽然你在开发过程中不太可能遇到问题,但在主线程上做繁重的工作导致用户崩溃。在 iOS 上,有一个进程可以监视应用程序是否正在响应事件,例如绘图更新。如果应用程序没有及时响应事件,它将被终止。因此,您不能选择缺少 UI 更新。您只需要从后台线程执行耗时的操作。

也可以看看:

于 2013-10-29T23:30:46.337 回答
0

如果要定期更新标签,请不要使用sleep. 如果您在主线程上调用它,您将阻塞 UI,这不是很理想。

改为使用 a NSTimer,使其每 N 秒触发一次。

这样的事情会做:

- (void)startUpdatingLabel {
    [NSTimer scheduledTimerWithTimeInterval:0 target:self selector:@selector(updateLabelWithIndex:) userInfo:@0 repeats:NO];
}

- (void)updateLabel:(NSTimer *)timer {
    NSInteger index = [timer.userInfo integerValue];
    if (index >= self.currentNumber.count) {
        return;
    }
    self.showLabel.text = [NSString stringWithFormat:@"%@", self.currentNumber[index]];
    [NSTimer scheduledTimerWithTimeInterval:3 target:self selector:@selector(updateLabelWithIndex:) userInfo:@(index+1) repeats:NO];
}

每次updateLabel:调用它都会安排一个新的计时器,该计时器将在 3 秒内再次调用它。每次index值增加并传递。

于 2013-10-29T23:30:40.063 回答