【发布时间】: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