【问题标题】:__unsafe_unretained leading to message sent to deallocated instance__unsafe_unretained 导致消息发送到释放的实例
【发布时间】:2012-12-10 21:31:03
【问题描述】:

我正在使用块来执行与网络相关的任务

当我的UITableViewController 中的块完成时,我想重新加载 tableviewcontroller

__unsafe_unretained UITableView *unretTableView = self.tableview;

[myRequest postWithCompletion:(bool finished) {
    [unretTableView reloadData];
}];

这很好,除了如果我在请求完成之前离开(deallocateUITableViewController,我似乎指向一个已释放的对象(unretTableView发送到已释放实例的消息 em> 即使在[UITableViewController dealloc] 方法(正在调用)中我设置了self.tableview = nil;

其他细节:

  • 无法使用__weak,我的目标是 iOS 4.3 及更高版本
  • UITableViewControllerdealloc方法中,我设置了self.tableview = nil
  • 我不想在您离开页面时取消网络请求,我希望它继续运行。
  • 编辑:我也不想在块中保留 self.tableview

谢谢!

【问题讨论】:

  • 一堆选项: 1. 切换到委托模式,您有一些对象进行更新,并且 tableview 控制器将其委托设置为指向 tableview 控制器。 2. 使用 NSOperation 进行后台更新,如果控制器离开并且不再需要数据,则让 tableview 控制器取消它。在告诉 tableview 重新加载之前进行操作检查以确保它没有被取消。 3. 使用通知,所以通知中心会告诉tableview什么时候需要重新加载。
  • 你使用什么库来进行网络请求?当然最好取消请求,否则您将毫无意义地使用资源
  • 谢谢@Rob 虽然委托模式会有所帮助,但如果我使用块,我会处于同样的情况,因为请求可能是排队的许多发布请求之一。我可以遍历 dealloc 并将所有块设置为 nil(与将所有代表设置为 nil)只是想知道是否有更清洁的方法来执行此操作。至于选项 2,我想这将是同一件事,迭代并取消操作。我考虑过通知,但更愿意保留块(如果我可以吃蛋糕的话)

标签: objective-c ios memory-management automatic-ref-counting


【解决方案1】:

鉴于您的 cmets,我可能倾向于追求 NSOperationQueue。这样就可以了

  1. 创建背景NSOperationQueue;

  2. 如果您的下载允许一定数量的同时下载,您可以设置maxOperationCount(或者如果您想要连续下载,请将其设置为 1)。

  3. 当您启动后台作业时,您可以创建NSOperation 对象,或者直接通过addOperationWithBlock 将块提交给NSOperationQueue。如果您想享受检查isCancelled 标志的能力,您可能需要执行前者。

  4. 每个操作都可以在尝试更新视图控制器之前检查它是否被取消。

  5. 当视图控制器被解除时,它可以执行一个简单的cancelAllOperations 来取消所有排队的东西。

这是一个随机的例子:

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.queue = [[NSOperationQueue alloc] init];
    self.queue.maxConcurrentOperationCount = 4;

    // let's add 100 operations to our queue

    for (NSInteger i = 0; i < 100; i++)
    {
        NSBlockOperation *operation = [[NSBlockOperation alloc] init];
        __unsafe_unretained NSBlockOperation *weakOperation = operation; // it seems vaguely disturbing to do this with __unsafe_unretained, but given that the operation queue is taking care of this for us, I believe it's ok
        [operation addExecutionBlock:^{

            NSLog(@"Beginning operation %d", i);

            sleep(10);

            if ([weakOperation isCancelled])
                NSLog(@"Operation cancelled %d", i);
            else
            {
                // go ahead and update UI if you want
                NSLog(@"Finished operation %d", i);
            }
        }];

        [self.queue addOperation:operation];
    }
}

- (void)viewDidDisappear:(BOOL)animated
{
    [super viewDidDisappear:animated];
    [self.queue cancelAllOperations];
}

【讨论】:

    【解决方案2】:

    您稍后设置的属性与块捕获的变量设置的内容无关。如果要访问该属性,则需要访问该属性:self.foo,而不是foo。

    请注意,在这种情况下您仍然需要小心,因为在块中捕获 self 会导致 self 被保留,这可能会导致保留循环。

    (编辑) 如果您从另一个线程更新属性,这仍然不安全。您需要某种形式的序列化结构来确保安全(NSLock、NSOperationQueue、dispatch_queue_t 等...)。我假设这不是线程化的,但以防万一,这很重要。

    【讨论】:

    • 谢谢,编辑了我的问题,包括我想避免强烈捕获自我,因为长时间的超时会使我的 TableViewController 存活的时间比我想要的要长很多
    • 不管他使用self.foo还是foo,table view controller已经被释放了,所以没有变量可用。
    • @RamyAlZuhouri:这是不正确的。在块中使用self.foo 保留 self,因此在块运行之前不会释放表视图控制器。
    【解决方案3】:

    试试这个:

    __block __unsafe_unretained UITableView *unretTableView = self.tableview;
    

    编辑

    我不知道你用什么框架来发布请求。但是当你用[myRequest postWithCompletion: yourBlock]发布请求时,我想还有一种方法可以改变完成块,类似于 myRequest.completion=nil

    【讨论】:

    • __block 不会导致变量设置为零。只有一种方法可以在释放变量时自动将其设置为 nil,这就是“弱”。
    • 我知道了,我还以为unretTableView是无效的,因为他退出了方法的栈。
    • @Catfish_Man 不完全是一种方式检查MAZeroingWeakRef
    • MAZeroingWeakRef 是弱的实现,我想说,但是是的。
    【解决方案4】:

    插入健全性检查。

    __weak UITableView *table = unretTableView;
    if (table) [unretTableView reloadData];
    

    【讨论】:

    • 在这种情况下检查 nil 将无济于事。 __unsafe_unretained 的全部意义在于它不会在对象消失时设置为 nil。
    • 是的。这是 __weak 和 __unsafe_unretained 之间的唯一区别。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-08-22
    • 1970-01-01
    • 2016-08-28
    • 2011-06-16
    • 2013-08-30
    相关资源
    最近更新 更多