使用 NSOperation 子类 (ARC) 对主线程的块回调

发布于 2024-12-11 04:46:59 字数 1586 浏览 0 评论 0原文

这个问题类似于这个问题,具有自动引用 我有一个 NSOperation 子类,它接受一个块参数,

该参数旨在作为主 (UI) 线程的回调。我的初衷是在后台执行一些操作,然后使用dispatch_async和主队列来执行回调。

最初的前提:

@interface MySubclass : NSOperation {
@protected
    dispatch_block_t _callback;
}

- (id)initWithCallback:(dispatch_block_t)callback;

@end

@implementation MySubclass

- (void)main
{
    // Do stuff

    if (![self isCancelled]) { 
        dispatch_async(dispatch_get_main_queue(), _callback);
    }   
}

@end 

当块范围之外对 UIKit 对象的所有引用都被删除时,就会出现问题。 (例如,UIViewController 从导航堆栈中弹出。)这会留下对块内对象的唯一引用,因此当块位于该块所在的线程上时,该对象将被释放。被释放。。从主线程中释放 UIKit 对象会导致应用程序崩溃,并显示错误消息 尝试从主线程或 Web 线程以外的线程获取 Web 锁。这可能是从辅助线程调用 UIKit 的结果。现在崩溃了...

作为一种解决方法,我向回调 ivar 添加了一个 __block 修饰符,并使用 dispatch_sync 来确保释放的所有内容都在 main 上线。

@interface MySubclass : NSOperation {
@protected
    __block dispatch_block_t _callback;
}
- (id)initWithCallback:(dispatch_block_t)callback;

@end

@implementation MySubclass

- (void)main
{
    // Do Stuff

    if (![self isCancelled]) {
        dispatch_block_t block = ^{
            _callback();
            _callback = nil;
        };

        // Cover all our bases to prevent deadlock
        if ([NSThread isMainThread]) block();
        else dispatch_sync(dispatch_get_main_queue(), block);
    }
}

@end

我想知道是否有更好的方法可以在这个前提下完成某些事情。我的解决方法感觉很hacky,而且我不喜欢这样,我可能最终会在队列中出现多个操作,所有这些操作都在等待主线程的启动才能完成。

This question is similar to this question with automatic reference counting thrown in.

I have an NSOperation subclass that accepts a block argument that is intended as a callback to the main (UI) thread. My original intention was to perform some operation in the background, and then use dispatch_async and the main queue to perform the callback.

Original premise:

@interface MySubclass : NSOperation {
@protected
    dispatch_block_t _callback;
}

- (id)initWithCallback:(dispatch_block_t)callback;

@end

@implementation MySubclass

- (void)main
{
    // Do stuff

    if (![self isCancelled]) { 
        dispatch_async(dispatch_get_main_queue(), _callback);
    }   
}

@end 

Problems arise when all references to a UIKit object outside the scope of the block are removed. (E.g. a UIViewController is popped off a navigation stack.) This leaves the only reference to the object inside the block, so the object is deallocated when the block is, on the thread where the block is deallocated. Deallocating a UIKit object off the main thread crashes the app with the error message Tried to obtain the web lock from a thread other than the main thread or the web thread. This may be a result of calling to UIKit from a secondary thread. Crashing now...

As a workaround, I added a __block modifier to the callback ivar, and am using dispatch_sync to make sure everything released is on the main thread.

@interface MySubclass : NSOperation {
@protected
    __block dispatch_block_t _callback;
}
- (id)initWithCallback:(dispatch_block_t)callback;

@end

@implementation MySubclass

- (void)main
{
    // Do Stuff

    if (![self isCancelled]) {
        dispatch_block_t block = ^{
            _callback();
            _callback = nil;
        };

        // Cover all our bases to prevent deadlock
        if ([NSThread isMainThread]) block();
        else dispatch_sync(dispatch_get_main_queue(), block);
    }
}

@end

I am wondering if there is a better way to accomplish something with this premise. My workaround feels hacky, and I don't like that I might end up with several operations in my queue all waiting for a turn on the main thread before they can complete.

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

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

发布评论

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

评论(2

老娘不死你永远是小三 2024-12-18 04:46:59

如果您需要确保回调运行,即使控制器已从堆栈中弹出,那么您的解决方法是正确的。

但是,如果您确实只需要在控制器仍然存在的情况下运行回调,那么在回调中使用弱引用来确保块本身不会首先保留控制器会更简单。它看起来像这样:

- (void)demoMethod {
    __weak id weakSelf = self;
    MySubclass *subclass = [[MySubclass alloc] initWithCallback:^{
        if (!weakSelf) {
            return;
        }
        else {
            // Do whatever the callback does here
        }
    }];

    // Do something with `subclass` here
}

If you need to ensure the callback runs even if the controller has been popped from the stack, then your workaround is correct.

If, however, you really only need the callback to run if the controller is still around, then it would be simpler to use weak references in the callback to ensure that the block itself doesn't retain the controller in the first place. It would look something like this:

- (void)demoMethod {
    __weak id weakSelf = self;
    MySubclass *subclass = [[MySubclass alloc] initWithCallback:^{
        if (!weakSelf) {
            return;
        }
        else {
            // Do whatever the callback does here
        }
    }];

    // Do something with `subclass` here
}
沫离伤花 2024-12-18 04:46:59

API 的用户应该维护对 UIView 和任何其他存在此问题的对象的弱引用。然后回调将不再保留 UIView。在块内,他们应该将弱引用分配给强引用,测试该强引用是否为零,然后适当地进行。

视图控制器应该小心,不要不必要地实例化它们的视图。在访问 [self view] 之前,请始终使用 [self isViewLoaded]。 (这也适用于 UITableView 子类中的 [self tableView],因为这只是 view 的正确键入的别名。)

Users of your API should maintain weak references to UIViews and any other objects with this problem. The callback will then no longer keep the UIView around. Within the block, they should assign the weak reference to a strong reference, test that strong reference against nil, and proceed appropriately.

View controllers should be careful not to unnecessarily instantiate their views. Always use [self isViewLoaded] prior to accessing [self view]. (This also applies to [self tableView] in UITableView subclasses, since that's just a correctly typed alias for view.)

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