【问题标题】:A strange block issue in Objective-CObjective-C 中的一个奇怪的块问题
【发布时间】:2013-05-18 11:39:16
【问题描述】:

我有一个 AuthService 类,该类具有执行异步连接登录的方法。该类实现了 NSURLConnectionDataDelegate 协议,因此当服务器响应时,它会调用视图控制器先前设置的完成处理程序来更新 UI。

这是完成处理程序的定义

@property void (^completionHandler)(LoginResult *result);

这是类接收服务器响应的时候

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
    NSString *response = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];

    //Do something with the response and create an instance of LoginResult class

    self.completionHandler(loginResult);
}

如果完成处理程序块只是调用 NSLog 将作为参数传递的登录结果信息写入控制台,那么它可以完美运行而没有错误。但是当我想调用拥有该块的 ViewController 的方法时,就会发生一些奇怪的事情。

我知道当您在拥有该块的块中包含一个对象时存在一个保留周期。所以这就是我的编码方式。

__block typeof(self) bself = self;

[authService login:blablabla completionHandler:^(LoginResult *result) {
    [bself didReceiveLoginResult:result];
}

我认为这将防止进入保留周期。但调试时出现“Thread: EXC_BAD_ACESS”错误。

附: 例如,即使该属性未声明为“复制”,以下代码也可以完美运行

[authService login:blablabla completionHandler:^(LoginResult *result) {
    NSLog(@"Login %@", result.success ? @"success" : @"failed");
}

【问题讨论】:

  • 请注意,didReceiveData 不是调用完成处理程序的正确位置。可以使用大量响应数据多次调用它。您必须累积数据并在connectionDidFinishLoadingdidFailWithError: 中调用完成处理程序。

标签: ios objective-c objective-c-blocks


【解决方案1】:

该属性应声明为copy,否则该块将保留在堆栈上,并且在您调用它时可能已经被释放。

此外,还有一些简单的方法可以防止保留圈。当你使用它时,只需释放它,例如

self.completionHandler(loginResult);
self.completionHandler = nil;

__block 不需要任何聪明的魔法。临时保留周期是允许的。

编辑:

如果块中没有对self 的引用,编译器会将其设为全局 块并且它永远不会被释放。见http://www.cocoawithlove.com/2009/10/how-blocks-are-implemented-and.html

【讨论】:

  • 天哪!非常感谢您帮助我:) 我没有意识到块存储在堆栈中的简单事实。但我仍然可以检查块对象,所以我不确定它是否已被释放。我注意到一个块对象有一个名为 funcPtr 的成员,它是一个 C 风格的函数指针,指向一个不再可用的地址。
  • 完成处理程序属性声明为“复制”后问题解决。但是,当完成处理程序不调用 View Controller 的方法时,应用程序运行完美是没有意义的。
  • @Poligun 添加了有关您的案例的更多信息。
  • “此外,使用 MRC 有一些简单的方法可以防止保留循环。” 1)这与它是MRC还是ARC无关。 2)它确实做了一个保留循环;它只是稍后打破保留周期。
【解决方案2】:

如果您想在当前函数之外使用块,则需要复制它们,因此您需要先复制它,然后再将其存储到您的属性中:

- (void)setCompletionHandler:(void (^)(LoginResult *))handler {
    _completionHandler = [handler copy];
}

然后,当您在 login:completionHandler: 方法中分配完成处理程序时,它将在存储到实例变量之前被复制。

这样,你传递给函数的块在存储到属性之前会被复制,并且副本会位于堆上,而不是栈上,所以以后运行时它仍然存在。

【讨论】:

  • 感谢您帮助我 :) 它有效。但我也发现,如果完成处理程序只是调用 NSLog(@"%@", loginResult),即使属性没有声明为“复制”也没关系。你能解释一下原因吗?
  • ARC 有非常有趣的自动对象释放规则,似乎在某些情况下,如果块存储在其他地方的属性中,即使它没有被复制或保留,它也可能不会立即被释放.显然,块内其他对象的使用会影响这个过程。
  • 即你得到的EXC_BAD_ACCESS 错误可能不一定是由于块被释放,而是你试图从内部引用的对象。如果您发布崩溃的堆栈跟踪,我们将能够进一步了解这一点。
  • 因为setCompletionHandler:是一个接受块参数的方法,它应该负责块的内存管理,而不是调用者
  • @PartiallyFinite:堆栈块未“分配”或“解除分配”;它们具有自动存储持续时间,因为它们是作为局部变量实现的。一旦超出范围,它们就不复存在了。
猜你喜欢
  • 2010-12-09
  • 1970-01-01
  • 2013-12-12
  • 1970-01-01
  • 2016-02-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-20
相关资源
最近更新 更多