我的代码是:
- (NSString*)run:(NSString*)command{
_semaphore = dispatch_semaphore_create(0);
// Create and start timer
NSTimer *timer = [NSTimer timerWithTimeInterval:0.5f
target:self
selector:@selector(getState:)
userInfo:nil
repeats:YES];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addTimer:timer forMode:NSRunLoopCommonModes];
[runLoop run];
//and it stuck there
// Wait until signal is called
dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
return _state;
}
- (void)getState:(NSTimer*)time{
// Send the url-request.
NSURLSessionDataTask* task =
[_session dataTaskWithRequest:_request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (!error) {
NSLog(@"result: %@", data);
} else {
_state = @"error";
NSLog(@"received data is invalid.");
}
if (![_state isEqualToString:@"inProgress"]) {
dispatch_semaphore_signal(_semaphore);
// Stop timer
[timer invalidate];
}
}];
[task resume];
}
运行代码后
[runLoop run];
它什么都没发生!
那么,代码有什么问题?
调用dispatch_semaphore_wait
将阻止线程,直到调用dispatch_semaphore_signal
。这意味着必须从不同的线程调用信号,因为当前线程被完全阻塞。此外,您永远不应该从主线程调用wait,只能从后台线程调用wait。
这有用吗?
答案 0 :(得分:0)
有几点意见:
您不必使用信号量。 run
不会返回,直到计时器失效。
The documentation run
建议您不要使用您已经概述的方法,而是使用runMode:beforeDate:
循环,如下所示:
_shouldKeepRunning = true;
while (_shouldKeepRunning && [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]) {
// this is intentionally blank
}
然后,在您使计时器无效的位置,您可以将_shouldKeepRunning
设置为false
。
这种旋转运行循环以运行计时器的概念,没有任何违法行为,有点过时了。如果我真的需要在后台线程上运行一个计时器,我会使用一个调度计时器,就像https://stackoverflow.com/a/23144007/1271826的前半部分所述。为这样的事情启动runloop是一种效率低下的模式。
但是让我们退一步看看你想要实现的目标,我假设你正试图在某个服务器上查询一些状态,并希望在收到时通知你#39; s不再处于"正在进行中#34;州。如果是这样,我会采用异步模式,例如完成处理程序,并执行以下操作:
- (void)checkStatus:(void (^ _Nonnull)(NSString *))completionHandler {
// Send the url-request.
NSURLSessionDataTask* task = [_session dataTaskWithRequest:_request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSString *state = ...
if (![state isEqualToString:@"inProgress"]) {
completionHandler(@"inProgress");
} else {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self checkStatus:completionHandler];
});
}
}];
[task resume];
}
然后像这样调用它:
[self checkStatus:^(NSString *state) {
// can look at `state` here
}];
// but not here, because the above is called asynchronously (i.e. later)
完全不需要运行循环。我也消除了计时器模式,而在先前请求完成后再次尝试 x 秒#34;,因为如果一个请求没有完成,则可能会出现计时器问题。下一个计时器开火的时间。 (是的,我知道您可以通过引入额外的状态变量来解决这个问题,以便跟踪您当前是否处于请求状态,但这很愚蠢。)