我正在编写一个运行自己的Web服务器的mac应用程序,使用GCDWebServer库(https://github.com/swisspol/GCDWebServer)。我的app委托处理GET请求,如下所示:
__weak typeof(self) weakSelf = self;
[webServer addDefaultHandlerForMethod:@"GET"
requestClass:[GCDWebServerRequest class]
processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
return [weakSelf handleRequest:request];
}];
然后handleRequest方法返回响应数据,如:
return [GCDWebServerDataResponse responseWithHTML:@"<html><body><p>Hello World!</p></body></html>"];
到目前为止一切顺利。除了现在我希望handleRequest方法使用NSSpeechSynthesizer来创建一个带有一些语音文本的音频文件,然后在返回processBlock之前等待speechSynthesizer:didFinishSpeaking方法被调用。
// NSSpeechSynthesizerDelegate method:
- (void)speechSynthesizer:(NSSpeechSynthesizer *)sender didFinishSpeaking:(BOOL)success
{
NSLog(@"did finish speaking, success: %d", success);
// return to processBlock...
}
问题是,我不知道该怎么做。有没有办法从speechSynthesizer:didFinishSpeaking方法返回到上面定义的processBlock?
答案 0 :(得分:0)
您需要在具有自己的运行循环的单独线程上运行语音合成器,并使用锁定来允许请求线程等待语音线程上的操作完成。
假设Web服务器维护自己的线程和runloop,您可以使用应用程序的主线程来运行语音合成器,并且可以使用NSCondition
向Web响应发出完成信号线程。
基本(未经测试)的示例(没有错误处理):
@interface SynchroSpeaker : NSObject<NSSpeechSynthesizerDelegate>
- (id)initWithText:(NSString*)text outputUrl:(NSURL*)url;
- (void)run;
@end
@implementation SynchroSpeaker
{
NSCondition* _lock;
NSString* _text;
NSURL* _url;
NSSpeechSynthesizer* _synth;
}
- (id)initWithText:(NSString*)text outputUrl:(NSURL*)url
{
if (self = [super init])
{
_text = text;
_url = url;
_lock = [NSCondition new];
}
return self;
}
- (void)run
{
NSAssert(![NSThread isMainThread], @"This method cannot execute on the main thread.");
[_lock lock];
[self performSelectorOnMainThread:@selector(startOnMainThread) withObject:nil waitUntilDone:NO];
[_lock wait];
[_lock unlock];
}
- (void)startOnMainThread
{
NSAssert([NSThread isMainThread], @"This method must execute on the main thread.");
[_lock lock];
//
// Set up your speech synethsizer and start speaking
//
}
- (void)speechSynthesizer:(NSSpeechSynthesizer *)sender didFinishSpeaking:(BOOL)success
{
//
// Signal waiting thread that speaking has completed
//
[_lock signal];
[_lock unlock];
}
@end
它的使用方式如下:
- (id)handleRequest:(id)request
{
SynchroSpeaker* speaker = [[SynchroSpeaker alloc] initWithText:@"Hello World" outputUrl:[NSURL fileURLWithPath:@"/tmp/foo.dat"]];
[speaker run];
////
return response;
}
答案 1 :(得分:0)
GCDWebServer确实遇到了自己的线程(我猜其中有两个) - 不在主线程中。我的解决方案需要在调用ProcessBlock时在主线程中运行代码。
我发现这种方式符合我的需要:
__weak AppDelegate *weakSelf = self;
。这样做我可以访问块内的所有属性。__strong AppDelegate* strongSelf = weakSelf;
使用NSOperationQueue来对齐mainThread上的操作:
[[NSOperationQueue mainQueue] addOperationWithBlock:^ {
//Your code goes in here
NSLog(@"Main Thread Code");
[strongSelf myMethodOnMainThread];
}];
这样myMethodOnMainThread
肯定会在它应该的地方运行。
为了清楚起见,我引用了我的相关代码部分:
webServer = [[GCDWebServer alloc] init];
webServer.delegate = self;
__weak AppDelegate *weakSelf = self;
// Add a handler to respond to GET requests
[webServer addDefaultHandlerForMethod:@"GET"
requestClass:[GCDWebServerRequest class]
asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
__strong AppDelegate* strongSelf = weakSelf;
[[NSOperationQueue mainQueue] addOperationWithBlock:^ {
//Your code goes in here
NSLog(@"Main Thread Code");
[strongSelf myMethodOnMainThread];
}];
GCDWebServerDataResponse* response = [GCDWebServerDataResponse responseWithJSONObject:packet];
completionBlock(response);
}];
答案 2 :(得分:0)
从3.0版及更高版本开始,GCWebServer支持完全异步响应[1]。
[webServer addDefaultHandlerForMethod:@"GET"
requestClass:[GCDWebServerRequest class]
asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
// 1. Trigger speech synthesizer on main thread (or whatever thread it has to run on) and save "completionBlock"
// 2. Have the delegate from the speech synthesizer call "completionBlock" when done passing an appropriate response
}];
[1] https://github.com/swisspol/GCDWebServer#asynchronous-http-responses