Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/ios/103.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Ios 如何实现超时/等待NSStream以有效地使方法同步_Ios_Objective C_Multithreading_Timeout_Nstimer - Fatal编程技术网

Ios 如何实现超时/等待NSStream以有效地使方法同步

Ios 如何实现超时/等待NSStream以有效地使方法同步,ios,objective-c,multithreading,timeout,nstimer,Ios,Objective C,Multithreading,Timeout,Nstimer,我有一个蓝牙连接附件的输入流和输出流 我希望实现以下目标: 将数据写入outputStream 等待inputStream上接收到的数据或等待10秒 如果inputStream数据到达,则返回数据 否则返回零 我试着这么做: - (APDUResponse *)sendCommandAndWaitForResponse:(NSData *)request { APDUResponse * result; if (!deviceIsBusy && request != Ni

我有一个蓝牙连接附件的输入流和输出流

我希望实现以下目标:

将数据写入outputStream 等待inputStream上接收到的数据或等待10秒 如果inputStream数据到达,则返回数据 否则返回零

我试着这么做:

- (APDUResponse *)sendCommandAndWaitForResponse:(NSData *)request {
  APDUResponse * result;
  if (!deviceIsBusy && request != Nil) {
    deviceIsBusy = YES;
    timedOut = NO;
    responseReceived = NO;
    if ([[mySes outputStream] hasSpaceAvailable]) {
      [NSThread detachNewThreadSelector:@selector(startTimeout) toTarget:self withObject:nil];
      [[mySes outputStream] write:[request bytes] maxLength:[request length]];
      while (!timedOut && !responseReceived) {
        sleep(2);
        NSLog(@"tick");
      }
      if (responseReceived && response !=nil) {
        result = response;
        response = nil;
      }
      [myTimer invalidate];
      myTimer = nil;
    }
  }
  deviceIsBusy = NO;
  return result;
}

- (void) startTimeout {
  NSLog(@"start Timeout");
  myTimer = [NSTimer timerWithTimeInterval:10.0 target:self selector:@selector(timerFireMethod:) userInfo:nil repeats:YES];
  [[NSRunLoop currentRunLoop] addTimer:myTimer forMode:NSRunLoopCommonModes];
}

- (void)timerFireMethod:(NSTimer *)timer {
  NSLog(@"fired");
  timedOut = YES;
}

- (void)stream:(NSStream*)stream handleEvent:(NSStreamEvent)streamEvent
{
  switch (streamEvent)
  {
    case NSStreamEventHasBytesAvailable:
      // Process the incoming stream data.
      if(stream == [mySes inputStream])
      {
        uint8_t buf[1024];
        unsigned int len = 0;
        len = [[mySes inputStream] read:buf maxLength:1024];
        if(len) {
          _data = [[NSMutableData alloc] init];
          [_data appendBytes:(const void *)buf length:len];
          NSLog(@"Response: %@", [_data description]);
          response = [[APDUResponse alloc] initWithData:_data];
          responseReceived = YES;
        } else {
          NSLog(@"no buffer!");
        }
      }
      break;
     ... //code not relevant 
  }
}
因此,理论是让一个NSTimer在一个单独的线程上运行,该线程在启动时会设置一个布尔值,然后如果收到数据,也让handleEvent委托方法设置另一个布尔值。 在该方法中,我们有一个while循环,其中一个bool被设置时,睡眠将停止

我遇到的问题是,在“超时场景”中,没有调用我的timerFireMethod。我的直觉是,我实际上没有在一个单独的线程上正确设置计时器


有人能看到这里出了什么问题,或者能为上述需求提出更好的实现方案吗?

相反,要对固有的异步问题强制采用不适当的同步方法,请使您的方法
sendCommandWaitForResponse
异步

可以将“流写入”任务包装为异步操作/任务/方法。例如,您可以使用以下接口以并发子类
NSOperation
结束:

typedef void (^DataToStreamCopier_completion_t)(id result);

@interface DataToStreamCopier : NSOperation

- (id) initWithData:(NSData*)sourceData
  destinationStream:(NSOutputStream*)destinationStream
         completion:(DataToStreamCopier_completion_t)completionHandler;

@property (nonatomic) NSThread* workerThread;
@property (nonatomic, copy) NSString* runLoopMode;
@property (atomic, readonly) long long totalBytesCopied;


// NSOperation
- (void) start;
- (void) cancel;
@property (nonatomic, readonly) BOOL isCancelled;
@property (nonatomic, readonly) BOOL isExecuting;
@property (nonatomic, readonly) BOOL isFinished;

@end
您可以使用
cancel
方法实现“超时”功能

您的方法
sendCommandWaitForResponse:
与完成处理程序变得异步:

- (void)sendCommand:(NSData *)request 
         completion:(DataToStreamCopier_completion_t)completionHandler
{
    DataToStreamCopier* op = [DataToStreamCopier initWithData:request 
                                            destinationStream:self.outputStream 
                                                   completion:completionHandler];
   [op start];

   // setup timeout with block:  ^{ [op cancel]; }
   ...
}
用法: 警告: 不幸的是,使用运行循环的底层任务正确地实现并发的
NSOperation
子类并不是那么简单。会出现一些微妙的并发问题,这些问题迫使您使用同步原语(如锁或调度队列)和其他一些技巧,以使其真正可靠

幸运的是,将任何运行循环任务包装到并发
NSOperation
子类中需要基本相同的“锅炉板”代码。因此,一旦你有了一个通用的解决方案,编码工作就是从一个“模板”中复制/过去,然后为你的特定目的定制代码

替代解决方案: 严格地说,如果您不打算将许多任务放入
NSOperationQueue
中,您甚至不需要
NSOperation的子类。只需发送
start
方法即可启动并发操作-不需要
NSOperationQueue
。然后,不使用
NSOperation
的子类可以使您自己的实现更简单,因为子类化
NSOperation
本身有其微妙之处

然而,您实际上需要一个“操作对象”,它包装您的运行循环,驱动一个
NSStream
对象,因为实现需要保持状态,而这不能用简单的异步方法实现

因此,您可以使用任何可以被视为异步操作的自定义类,该类具有
start
cancel
方法,并具有在基础任务完成时通知调用站点的机制

还有比完成处理程序更强大的通知调用站点的方法。例如:承诺或未来(见维基文章)

假设您实现了自己的“异步操作”类,并承诺将其作为通知呼叫站点的手段,例如:

@interface WriteDataToStreamOperation : AsyncOperation

- (void) start;
- (void) cancel;

@property (nonatomic, readonly) BOOL isCancelled;
@property (nonatomic, readonly) BOOL isExecuting;
@property (nonatomic, readonly) BOOL isFinished;
@property (nonatomic, readonly) Promise* promise;

@end
您最初的问题看起来更“同步”——尽管是异步的:

您的
sendCommand
方法变为:

注意:假设承诺类的某个实现:

- (Promise*) sendCommand:(NSData *)command {
    WriteDataToStreamOperation* op = 
     [[WriteDataToStreamOperation alloc] initWithData:command 
                                         outputStream:self.outputStream];
    [op start];
    Promise* promise = op.promise;
    [promise setTimeout:100]; // time out after 100 seconds
    return promise;
}
注意:承诺设置了“超时”。这基本上是注册一个计时器和一个处理程序。如果计时器在基础任务解析承诺之前触发,则计时器块将使用超时错误解析承诺。如何(以及是否)实现这一点取决于Promise库。(这里,我假设RXPromise库是我的作者。其他实现也可能实现这样的功能)

用法: 替代用途: 您可以用不同的方式设置超时。现在,假设我们没有在
sendCommand:
方法中设置超时

我们可以设置“外部”超时:

使异步方法同步 通常,您不需要也不应该在应用程序代码中将异步方法“转换”为某个同步方法。这总是导致次优和低效的代码,不必要地消耗系统资源,如线程

尽管如此,您可能希望在单元测试中这样做:

单元测试中“同步”异步方法的示例 在测试实现时,您经常希望“等待”(同步地是)结果。您的底层任务实际上是在运行循环上执行的,可能是在您希望等待结果的同一个线程上执行的,这并不能使解决方案更简单

但是,您可以通过RXPromise库利用
runLoopWait
方法轻松实现这一点,该方法可以有效地进入运行循环并在那里等待承诺得到解决:

-(void) testSendingCommandShouldReturnResponseBeforeTimeout10 {
    Promise* promise = [self sendCommand:request];
    [promise setTimeout:10];
    [promise.then(^id(APDUResponse* response) {
        // do something with the response
        XCTAssertNotNil(response);            
        return  ...;  // returns the result of the handler
    }, 
    ^id(NSError*error) {
         // A Stream error or a timeout error
        XCTestFail(@"failed with error: %@", error);
        return nil;  // returns nothing

    }) runLoopWait];  // "wait" on the run loop
}
在这里,方法
runLoopWait
将进入一个运行循环,并等待通过超时错误或在基础任务已解析承诺时解析承诺。承诺不会阻塞主线程,也不会轮询运行循环。当承诺得到解决时,它将离开运行循环。其他运行循环事件将照常处理

注意:您可以安全地从主线程调用
testSendingCommandShouldReturnResponseBeforeTimeout10
,而无需阻塞它。这是绝对必要的,因为流委托方法也可能在主线程上执行

在单元测试库中通常可以找到其他方法,这些方法
[self sendCommand:request].then(^id(APDUResponse* response) {
    // do something with the response
    ...
    return  ...;  // returns the result of the handler
}, 
^id(NSError*error) {
    // A Stream error or a timeout error
    NSLog(@"Error: %@", error);
    return nil;  // returns nothing
});
Promise* promise = [self sendCommand:request];
[promise setTimeout:100];
promise.then(^id(APDUResponse* response) {
    // do something with the response
    ...
    return  ...;  // returns the result of the handler
}, 
^id(NSError*error) {
    // A Stream error or a timeout error
    NSLog(@"Error: %@", error);
    return nil;  // returns nothing
});
-(void) testSendingCommandShouldReturnResponseBeforeTimeout10 {
    Promise* promise = [self sendCommand:request];
    [promise setTimeout:10];
    [promise.then(^id(APDUResponse* response) {
        // do something with the response
        XCTAssertNotNil(response);            
        return  ...;  // returns the result of the handler
    }, 
    ^id(NSError*error) {
         // A Stream error or a timeout error
        XCTestFail(@"failed with error: %@", error);
        return nil;  // returns nothing

    }) runLoopWait];  // "wait" on the run loop
}