Obj-C从委托方法返回一个块?

时间:2014-11-30 17:09:28

标签: objective-c

我正在编写一个运行自己的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?

3 个答案:

答案 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时在主线程中运行代码。

我发现这种方式符合我的需要:

  1. 首先为我的AppDelegate声明一个弱存储空间:__weak AppDelegate *weakSelf = self;。这样做我可以访问块内的所有属性。
  2. 从块中声明对AppDelegate的强引用,如下所示:__strong AppDelegate* strongSelf = weakSelf;
  3. 使用NSOperationQueue来对齐mainThread上的操作:

    [[NSOperationQueue mainQueue] addOperationWithBlock:^ {
    
                                    //Your code goes in here
                                    NSLog(@"Main Thread Code");
                                    [strongSelf myMethodOnMainThread];
    
                                }];
    
  4. 这样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