使用 GCD 并回调主线程时如何设置正确的对象拆卸?

发布于 2024-11-01 23:42:11 字数 787 浏览 0 评论 0原文

考虑以下设置:

对象 A 创建对象 B 来执行某些工作,并将其自身设置为 B 的委托以了解工作进度。

B 使用 GCD 块进行一些工作,并使用委托方法向 A 发出有关工作完成的信号。 A 想要在工作完成后拆除(释放)B。

用代码术语来说:

对象 A:

B *b = [[B alloc] init];
b.delegate = self;
[b doSomeWork];
- (void) didSomeWorkFromB:(B *)b {
    [b release];
    b = nil;
}

对象 B:

- (void) doSomeWork {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        doSomeWork();

        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"Work is complete.");
            [self.delegate didSomeWorkFromB:self];
        });

    });
}

问题:在对象 A 内调用 [b release] 会导致崩溃。我认为这是因为当 A 尝试释放 B 时,调度队列/后台代码仍在运行。

问题:在这种情况下,如何正确设置对象和信号,以确保 A 仅在所有后台工作完成时才销毁 B已完成?

Consider this setup:

Object A creates object B for doing some work, and sets itself as B's delegate to be informed of work progress.

B does some work with GCD blocks, and signals back to A with the delegate method about work completion. A wants to tear down (release) B upon work completion.

In code terms:

Object A:

B *b = [[B alloc] init];
b.delegate = self;
[b doSomeWork];
- (void) didSomeWorkFromB:(B *)b {
    [b release];
    b = nil;
}

Object B:

- (void) doSomeWork {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        doSomeWork();

        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"Work is complete.");
            [self.delegate didSomeWorkFromB:self];
        });

    });
}

PROBLEM: calling [b release] inside object A causes a crash. I think it's because the dispatch queue/background code is still running when A tries to release B.

QUESTION: how do I properly set up the objects and signaling in this case, to make sure that A only destroys B when all the background work has been completed?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

£噩梦荏苒 2024-11-08 23:42:11

虚假的问题。它实际上按预期工作,并且上面的代码不会崩溃。崩溃是由一些不相关的代码引起的。

Bogus question. It actually works as expected and the code above does not crash. The crash was caused by some unrelated code.

两个我 2024-11-08 23:42:11

你是对的,代码按原样工作。但这是不必要的复杂。

您可以让 B 的 doSomeWork 保留自身(通过在 doSomeWork[self keep][self release] code> 或仅在 dispatch_async 块中引用 self,这将为我们保留它),并让 A 在调用后立即清理doSomeWork,因此在 didSomeWorkFromB 中不需要进一步清理。

这种模式在 iOS 中很常见。例如,如果您查看 NSURLConnection 的许多常见实现,因为它在下载连接时保留自身,并在连接完成后释放自身,所以我们通常不会同时保留对连接并在 connectionDidFinishLoading 中清理它。只需让引用计数内存管理的魔力为您处理一切即可。

A 中:

- (void) test
{
    B *b = [[B alloc] init];
    b.delegate = self;
    [b doSomeWork];
    [b release];    // you could also autorelease above, but I just wanted to make it more explicit for the purposes of the demonstration
}

- (void) didSomeWorkFromB:(B *)b
{
    // [b release]; // don't need to release it ... we already did
    // b = nil;     // certainly don't need to nil local reference ... this does nothing useful in any scenario
}

B 中:

- (void) dealloc
{
    // let's log this so we can see when it's deallocated

    NSLog(@"%s", __FUNCTION__);

    [super dealloc];
}

- (void) doSomeWork
{
    // [self retain];  // you could manually retain if you want

    NSLog(@"%s", __FUNCTION__);

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(10);

        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"Work is complete.");

            [self.delegate didSomeWorkFromB:self];

            // [self release]; // and if you manually retained, you'd manually release, too
        });
    });
}

在我看来,这种方法(在调用 doSomeWork 后立即让 A release B )更稳健,更紧密地协调对象的平衡清理。我还认为,当您考虑最终转向 ARC 时,这会让您处于更好的位置。

You are correct, that the code works as is. But it is unnecessarily complicated.

You can just have B's doSomeWork retain itself (either by explicitly calling [self retain] and [self release] in doSomeWork or just by referencing self in the dispatch_async block, which will retain it for us), and let A clean up immediately after the invocation of doSomeWork, and therefore no further cleanup is required in didSomeWorkFromB.

This pattern is very common in iOS. For example, if you look at many common implementations of NSURLConnection, since it retains itself while the connection is downloading, and releases itself once the connection is done, we often don't both keeping a reference to the connection and cleaning it up in connectionDidFinishLoading. Just let the magic of reference counting memory management take care of everything for you.

In A:

- (void) test
{
    B *b = [[B alloc] init];
    b.delegate = self;
    [b doSomeWork];
    [b release];    // you could also autorelease above, but I just wanted to make it more explicit for the purposes of the demonstration
}

- (void) didSomeWorkFromB:(B *)b
{
    // [b release]; // don't need to release it ... we already did
    // b = nil;     // certainly don't need to nil local reference ... this does nothing useful in any scenario
}

In B:

- (void) dealloc
{
    // let's log this so we can see when it's deallocated

    NSLog(@"%s", __FUNCTION__);

    [super dealloc];
}

- (void) doSomeWork
{
    // [self retain];  // you could manually retain if you want

    NSLog(@"%s", __FUNCTION__);

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(10);

        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"Work is complete.");

            [self.delegate didSomeWorkFromB:self];

            // [self release]; // and if you manually retained, you'd manually release, too
        });
    });
}

In my opinion, this approach (of having A release B immediately after calling doSomeWork) is more robust, more closely coordinating the balancing cleanup of the object. I also think this puts you in better stead as you contemplate an eventual shift to ARC.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文