我有一个输入的流和输出流的蓝牙连接配件
我想要实现以下目标:
写入数据到 outputStream 等待,直到收到对题目: 下面的数据或直到 10 秒传递如果题目: 下面数据到达其他返回数据返回零
我试着所以执行此类似:
- (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 委托方法也有设置另一个 boolean 类型的值如果收到的数据的一个单独的线程上运行。在方法中,我们有一段时间睡一觉,当这些 bool 之一设置时停止循环。
我遇到的问题在 '超时的情况下' 我的 timerFireMethod 不是是越来越叫。我的直觉是我是不实际正确设置计时器在一个单独的线程上。
任何人都可以看看究竟怎么在这里或建议的上述要求的更好实施吗?
解决方法 1:
而是施加不当同步方法本质上异步的问题,使您的方法 sendCommandAndWaitForResponse 异步。
它是可能要包装的"流写入"任务为异步操作/任务/方法。例如,你可能最终与并发子类的 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 方法。
您的方法 sendCommandAndWaitForResponse: 成为异步完成处理程序:
- (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]; }
...
}
用法:
[self sendCommand:request completion:^(id result) {
if ([result isKindOfClass[NSError error]]) {
NSLog(@"Error: %@", error);
}
else {
// execute on a certain execution context (main thread) if required:
dispatch_async(dispatch_get_main_queue(), ^{
APDUResponse* response = result;
...
});
}
}];
警告:
不幸的,执行并发 NSOperation 子类正常使用雇用一个运行的循环的基本任务不是那么微不足道作为它应该是。那里将会出现微妙的并发问题,迫使您可以使用同步基元像锁或调度队列和其他几个技巧,使它真正可靠。
幸运的,换任何运行循环任务并发 NSOperation 子类基本上要求相同的"锅炉板"代码。所以,一旦你有一个通用的解决方案,了编码的工作量是只是复制和粘贴从"模板",然后为您的特定目的定制代码。
替代的解决方案:
严格地说,你甚至不需要一个子类, NSOperation 如果你不打算放入这些任务数 NSOperationQueue 。可以简单地将它发送入门并发操作 start 方法-有没有 NSOperationQueue 所需。然后,不使用类的子类 NSOperation 可以使您自己的实现比较简单,因为子类 NSOperation 本身有其自己微妙之处。
但是,你其实需要换行您运行循环驾驶"操作对象" NSStream 对象,因为执行需要保留状态,不能在一个简单的异步方法完成。
因此,您可以使用任何自定义的类,可以查看作为异步操作有 start 和 cancel 方法和有一种机制来通知调用站点完成基本任务。
也有更强大的手段来通知调用站点比完成处理程序。例如: 承诺或期货 (见 wiki 文章期货和承诺)。
假设作为一种手段,例如通知调用站点,实现您自己的"异步操作"类的承诺:
@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;
}
注:承诺已设置"超时"。这基本上注册一个计时器和一个处理程序。如果之前承诺的底层任务获取解决激发计时器,计时器块解析超时错误的诺言。如何 (和如果) 这实施取决于承诺图书馆。(在这里,我假设的 RXPromise 库,我在哪里作者。其他执行也可以实现这种功能)。
用法:
[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
});
替代用法:
您可能会以不同的方式设置超时时间。现在,假设我们没有设置超时时间内的 sendCommand: 方法。
我们可以设置"外部"超时:
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
});
使异步方法同步
通常,你不需要和不应该"转换"的异步方法的几种同步方法在应用程序代码中。这总是导致次优和效率低下的代码不必要地消耗系统资源,线程类似。
然而,你可能会想要这样做是有意义的单元测试中:
"同步"在单元测试中的异步方法的示例
测试您的实施时, 经常要"等待"(是同步) 的结果。你基础的任务实际上正在运行的循环,执行这一事实可能在相同的线程要等待结果的位置,不会使解决方案更简单。
但是,您可以轻松完成此与 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 从主线程不会阻止它。而这是绝对必要的因为你流委托方法可能太在主线程上执行 !
有其他方法通常在单元测试库,其中提供类似的功能来异步方法或操作,而进入运行的循环的结果"等待"中找到。
不建议使用其他方法以异步方法或操作的最终结果为"等待"。这些通常会派遣到私人的线程的方法,然后阻止它,直到结果可用。
有用资源
操作像类,将流复制到另一个流利用的承诺 (上要点) 的代码段: RXStreamToStreamCopier