ARC 异步块最佳实践

ARC Async Block Best Practices

我的应用程序通过 HTTP 请求发送大量消息。我围绕一个 HTTP 请求编写了一个简单的包装器,它有一个主要方法来触发一个请求和 return 任何 NSData 作为结果。

看起来像这样:

- (void)sendRequest:(NSURLRequest *)request {
    [[[NSURLSession sharedSession] dataTaskWithRequest:request
                                     completionHandler:^(NSData *data,
                                                         NSURLResponse *response,
                                                         NSError *error) {
                                         NSLog(@"---->> request returned");

                                         if (_delegate == nil)
                                             return;

                                         if (error == nil) {
                                             // forward along
                                             [_delegate requestReturnedResult:data withResponse:response];

                                         }
                                         else {
                                             // error occurred
                                             [_delegate requestReturnedError:error withResponse:response];
                                         }
                                     }] resume];
}

这个HttpRequestHelper对象是我的一个消息抽象对象OutboundMessageivar,将数据打包发送出去

我认为有一个 MessageSender 对象可以获取一些数据,创建一个 OutboundMessage 对象,然后将其发送出去。

这个抽象工作得很好,除了我认为我遇到了 ARC 的问题。

当有人打电话给[MessageSender send:]时,我alloc一个OutboundMessage然后发送出去。

我通过 NSLog 看到的是我的 OutboundMessage 被清理了(大概当 send 退出时,我看到 HttpRequestHelper 被清理了。然后 我在 dataTaskWithRequest 的完成处理程序中看到了 NSLog

这向我表明,在 HTTP 请求完成之前,我的请求助手周围的所有内容都已清理干净。然后你可以想象,很快我尝试通过 delegate 冒泡我的回复,我得到 exc_bad_access.

我可以想出一些有创意的方法来防止重新分配并防止这种情况发生,但我想知道的是从概念上讲,处理这种触发消息模式的最佳做法是什么,其中封装对象仅在请求已完成。

谢谢。

更新 每个包装器对象都使用 initWithDelegate:self 样式模式,我注意到这些委托是这样保存的:

@property (nonatomic, assign) id<HttpRequesterDelegate> delegate;

我将它们更改为 strong,它似乎更正了它,但我不确定我现在是否正在泄漏内存,或者是否有更优化的方法。

首先,由NSURLSession - dataTaskWithRequest:completionHandler:方法创建的NSURLSessionDataTask对象保留了completionHandler块对象。在数据任务完成时执行块之前,块对象不会被释放。

接下来,块自动捕获(保留)局部变量。代码中的下面一行

if (_delegate == nil)

相同
if (self.delegate == nil)

如果delegate属性是strong

@property (nonatomic, strong) id<HttpRequesterDelegate> delegate;

然后self被块保留。在这种情况下,selfHttpRequestHelper 对象。所以 HttpRequestHelper 对象和 delegate 对象只要数据任务完成就一直存在。很安全。

但是delegate属性是assign

@property (nonatomic, assign) id<HttpRequesterDelegate> delegate;

__unsafe_unretained相同。 Blocks 根本不保留 __unsafe_unretained 对象。因此self没有被保留,在数据任务完成前被释放

已添加

在这种情况下,您可以使用局部变量来保留 delegate 对象。因为 completionHandler 仅使用 delegate 对象,而不是 HttpRequestHelper 对象本身。

id<HttpRequesterDelegate> delegate = _delegate;

[[[NSURLSession sharedSession] dataTaskWithRequest:request
                                 completionHandler:^(NSData *data,
                                                     NSURLResponse *response,
                                                     NSError *error) {
                                     NSLog(@"---->> request returned");

                                     /*
                                      * Use `delegate` instead of `_delegate`
                                      */
                                     if (delegate == nil)
                                         return;

                                     ...

这样的话,delegate属性的属性是strong还是assign,完全没有关系。