在NSOperation中执行阻止

时间:2011-11-19 12:43:38

标签: objective-c cocoa-touch objective-c-blocks nsinvocation

我在某个类中有一个方法,它使用块执行某些任务。当我使用NSInvocationOperation执行该方法时,控件永远不会进入块。我尝试在块内部进行登录,但实际上从未调用过。但是,如果我只是用该类的实例调用该方法,那么一切都按预期工作。

不要阻止在NSOperation中运行吗?

NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:myClassObj selector:@selector(myClassMethod:) object:obj1];
[[AppDelegate sharedOpQueue] addOperation:op];
[op release];


- (void)myClassMethod:(id)obj
{
    AnotherClass *otherClass = [[AnotherClass allco] init]
    [otherClass fetchXMLWithCompletionHandler:^(WACloudURLRequest* request, xmlDocPtr doc, NSError* error)
     {
         if(error){
             if([_delegate respondsToSelector:@selector(handleFail:)]){
                 [_delegate handleFail:error];
             }
             return;
         }

         if([_delegate respondsToSelector:@selector(doSomeAction)]){
             [_delegate doSomeAction];
         }
     }];

}

- (void) fetchXMLWithCompletionHandler:(WAFetchXMLHandler)block
{
    _xmlBlock = [block copy];
    [NSURLConnection connectionWithRequest:request delegate:self];
}

-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    if(_xmlBlock) {
        const char *baseURL = NULL;
        const char *encoding = NULL;

        xmlDocPtr doc = xmlReadMemory([_data bytes], (int)[_data length], baseURL, encoding, (XML_PARSE_NOCDATA | XML_PARSE_NOBLANKS)); 

        NSError* error = [WAXMLHelper checkForError:doc];

        if(error){
            _xmlBlock(self, nil, error);
        } else {
            _xmlBlock(self, doc, nil);
        }

        xmlFreeDoc(doc);
    }
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
    if(_xmlBlock) {
        _xmlBlock(self, nil, error);
    }
}

2 个答案:

答案 0 :(得分:0)

您正在异步执行NSConnection(在NSOperation中您不需要这样做,因为您应该已经在后台线程上。)

致电fetchXMLWithCompletionHandler后,您的方法结束。这表明NSOperation已经完成并且它被释放并且它的线程被重用于其他东西,或者更可能被释放。这意味着当你收到回调时,你的初始对象就不存在了!

有两种解决方案:

1)使用NSURLConnection synchronously。这将在您的myClassMethod中等待,直到收到回复为止。

2)了解NSOperations's concurrent模式。我不知道这是否适用于NSInvocationOperation :(与选项(1)相比,它相当复杂。

我会使用方法(1) - 你已经创建了一个后台线程来执行你的操作,为什么还要创建另一个来执行你的连接请求?

答案 1 :(得分:0)

有两种方法可以解决您的问题:

轻松出路

是 - 正如Dean建议的那样 - 使用+[NSURLConnection sendSynchronousRequest:returningResponse:error:],因为你已经在另一个线程上了。你已经覆盖了 - 我会说 - 80-90%的时间,实现起来非常简单,Just Works™。

另一种方式

只是稍微复杂一点,你可以通过访问问题的根源来了解第一种方法不够的所有情况:

NSURLConnection与runloop一起使用 - 由NSOperationQueue管理的线程不一定使用(甚至拥有!)相关的runloop。

调用+[NSURLConnection connectionWithRequest:delegate:]时会隐式创建一个runloop,如果需要,它不会导致runloop实际运行!

当您使用的NSOperationQueue不是与主线程关联的队列时,

这是您的责任

为此,请将fetchXMLWithCompletionHandler:的实施更改为类似于以下内容:

- (void)fetchXMLWithCompletionHandler:(WAFetchXMLHandler)block
{
    self.xmlHandler = block; // Declare a @property for the block with the copy attribute set

    self.mutableXMLData = [NSMutableData data]; // again, you should have a property for this...

    self.currentConnection = [NSURLConnection connectionWithRequest:request delegate:self]; // having a @property for the connection allows you to cancel it, if needed.

    self.connectionShouldBeRunning = YES; // ...and have a BOOL like this one, setting it to NO in connectionDidFinishLoad: and connection:didFailWithError:

    NSRunLoop *loop = [NSRunLoop currentRunLoop];
    NSDate *neverExpire = [NSDate distantFuture];

    BOOL runLoopDidIterateRegularly = YES;
    while( self.connectionShouldBeRunning && runLoopDidIterateRegularly ) {
        runLoopDidIterateRegularly = [loop runMode:NSDefaultRunLoopMode beforeDate:neverExpire];
    }
}

通过这些微小的变化,你很高兴。额外奖励:这非常灵活,并且(最终)可以在所有代码中重复使用 - 如果您将XML解析移出该类并使您的处理程序只需要NSDataNSError和(可选) NSURLResponse

由于你可能不希望你的加载器的客户端看到并且可能搞乱我刚建议你应该添加的属性,你可以在类继续中声明它们。