允许用户在macOS循环执行期间输入



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

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

是否有一种方法可以暂停循环执行以检查来自用户控件(如工具栏项)的输入?在循环代码的开头添加[[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;
});
}

@end

经过一些实验,我发现了一个比@willeke提供的更简单的解决方案。使用如下所示的runMain代码,添加一个timerCalled方法并添加一个类变量iVal,以便在循环运行时执行Stop按钮动作。似乎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];
}
}

给你

- (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;
}
}
});
}

最新更新