【问题标题】:Allow user input during loop execution for macOS在 macOS 的循环执行期间允许用户输入
【发布时间】:2021-11-04 00:30:46
【问题描述】:

我在 Xcode 12.3 中有一个 Objective C MacOS 项目,其中包含一个循环,其中包含写入用户界面控件并可能显示警报的代码。当循环运行时,光标变成一个旋转的彩虹盘。在循环终止之前,单击工具栏项(或任何用户界面控件)无效。

我想让一个工具栏项在循环执行期间接受用户点击。虽然在单独的线程中运行循环将允许这样做,但需要大量重新编码以从循环代码中删除接口引用和警报。

有没有办法暂停循环执行以检查来自用户控件(如工具栏项)的输入?在循环代码的开头添加[[NSRunloop mainRunLoop] runUntilDate:[NSDate datewithTimeIntervalSinceNow:0.5]];并不能实现这一点。

我已尝试使用

在单独的线程中运行循环代码 (runBatch)
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul);
    dispatch_async(queue, ^{
        [self runBatch];
                dispatch_sync(dispatch_get_main_queue(), ^{
                    
                });
    });

循环代码包含在 runBatch 中,它设置和读取各种 UI 控件,这些控件被标记为只能在运行时从主线程访问。该项目构建正常。在异步队列完成后将这些 UI 交互放在主线程上会很困难。

显示问题的代码示例如下。该项目由一个带有 NSTextField (outlet textData) 和三个按钮的窗口组成,其中两个运行一个循环,第三个 (Stop) 设置一个停止标志。 runMain 在 textData 中显示索引,但是当它运行时,只显示最终值并且停止按钮没有响应。光标离开“开始”按钮约 3 秒后变为彩色轮。

当循环在后台线程上运行时,停止按钮是响应式的,但 textData 无法从后台线程更新。

我希望 textData 在循环运行时显示索引值。

AppDelegate.h

#import <Cocoa/Cocoa.h>
@interface AppDelegate : NSObject <NSApplicationDelegate>
@property (weak) IBOutlet NSTextField *textData;
@end

AppDelegate.m

#import "AppDelegate.h"

@interface AppDelegate ()

@property (strong) IBOutlet NSWindow *window;
@end

@implementation AppDelegate
@synthesize textData;
static bool stopBatch = false;
- (IBAction)runMain:(id)sender {
stopBatch = false;
[self runMain];
 }

 - (IBAction)stopClick:(id)sender {
 stopBatch = true;
 }
  - (IBAction)runBackground:(id)sender {
 stopBatch = false;
[self runBatchBackground];
}

-(void) runMain{
[textData setStringValue:@"Start"];
[textData displayIfNeeded];

NSString * iString = @"0";
for (int i=0;i<=10000 ;i++)
    {
        iString= [NSString stringWithFormat: @"%d",i];
        [textData setStringValue:iString];
        [textData displayIfNeeded];
    
        if(stopBatch)
        {
           break;
        }
    }
NSString *iStringFinal = iString;
}

-(void)runBatchBackground{
    [textData setStringValue:@""];
    NSString * __block iString = @"0";
    dispatch_queue_t  backgroundQueue =      dispatch_queue_create("Network",nil);
    dispatch_async(backgroundQueue, ^(void){

    for (int i=0;i<=10000000 ;i++)
    {
         iString= [NSString stringWithFormat: @"%d",i];
        //[self->_textData setStringValue:iString];
        //[self->_textData displayIfNeeded];
        if(stopBatch)
        {
            break;
        }
    }
    NSString *iStringFinal = iString;
});
}

@结束

经过一些实验,我发现了一个比@willeke 提供的更简单的解决方案。使用如下所示的 runMain 代码,添加 timerCalled 方法并添加类变量 iVal 允许在循环运行时执行停止按钮操作。似乎 10000 个计时器请求已排队,然后在不阻塞主循环(以及对用户控件的访问)的情况下执行,直到使用 return 语句退出 timerCalled,如图所示。这种方法有什么问题吗?

-(void) runMain{
    for (int i=0;i<10000 ;i++)
    {
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(timerCalled) userInfo:nil repeats:NO];
   }
}

-(void)timerCalled{
   if(stopBatch) return;
   for (int i=0;i<10;i++)
   {
    iVal++;
    iString= [NSString stringWithFormat: @"%ld",iVal];
    [textData setStringValue:iString];
   }
}

【问题讨论】:

  • 为什么必须从循环代码中删除接口引用和警报?你不能把它们包在dispatch_async(dispatch_get_main_queue(), ^{}中吗?
  • 针对此问题编辑了问题。
  • 在原始问题中添加了一个可重现的最小示例。
  • 而不是 10000 NSTimers 我会使用 1 重复 NSTimer 并在 timerCalled 中更新一次文本字段。

标签: objective-c macos


【解决方案1】:

给你

- (void)runBatchBackground {
    [self.textData setStringValue:@""];
    NSString * __block iString = @"0";
    dispatch_queue_t backgroundQueue = dispatch_queue_create("Network",nil);
    dispatch_async(backgroundQueue, ^(void){

        for (int i = 0; i <= 10000000; i++)
        {
            // Simulate some processing
            // If the code on the background thread runs faster than the code
            // on the main thread, then the main thread is lagging behind and doesn't
            // have time to process events.
            [NSThread sleepForTimeInterval:0.25];
            
            iString = [NSString stringWithFormat: @"%d",i];
            
            // Execute UI code on the main thread.
            dispatch_async(dispatch_get_main_queue(), ^{
                [self.textData setStringValue:iString];
                //[self.textData displayIfNeeded]; displayIfNeeded is not needed
            });
            
            if (self->stopBatch)
            {
                break;
            }
        }
    });
}

【讨论】:

  • 已编辑问题以显示使用 NSTimer 的更简单的解决方案。这种方法有什么问题吗?
猜你喜欢
  • 2013-10-21
  • 1970-01-01
  • 2015-12-27
  • 1970-01-01
  • 2013-10-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-06-16
相关资源
最近更新 更多