《一》
在使用 dispatch_group_t
进行异步任务管理时,如果多次调用 dispatch_group_enter
而没有相应次数的 dispatch_group_leave
,则会导致 dispatch_group_notify
永远不会被触发,因为 dispatch_group
的计数没有归零。
问题分析
如果你在一个 UIViewController
中执行这个代码段,而在该 UIViewController
被 pop
时,异步任务还没有完成,以下两种情况可能会发生:
-
内存泄漏:由于
dispatch_group_notify
永远不会被触发,它所捕获的上下文(包括UIViewController
)可能会一直保留在内存中,导致内存泄漏。这是因为 GCD 会保留这些块,直到它们完成,而由于未完成的dispatch_group_leave
,这些块永远不会完成。 -
闪退:在某些情况下,如果你对已经释放的对象进行访问,可能会导致闪退。然而,GCD 本身不会直接导致闪退,因为它会安全地保留块和上下文。
示例代码
考虑以下示例代码:
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_enter(group);
dispatch_group_enter(group);
dispatch_async(queue, ^{
// Some async task
dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// This will never be called if the dispatch_group_enter count does not match dispatch_group_leave count
NSLog(@"All tasks are done");
});
// Simulate popViewControllerAnimated
[self.navigationController popViewControllerAnimated:YES];
在这个代码中,dispatch_group_enter
被调用了两次,但是 dispatch_group_leave
只被调用了一次,因此 dispatch_group_notify
永远不会被触发。如果 dispatch_group_notify
的块捕获了 UIViewController
的上下文,并且这个 UIViewController
被 pop
,则可能会发生内存泄漏。
解决方案
确保每个 dispatch_group_enter
都有对应的 dispatch_group_leave
。你可以在所有异步任务完成后调用 dispatch_group_leave
,例如:
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_enter(group);
dispatch_async(queue, ^{
// Some async task
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
// Another async task
dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"All tasks are done");
});
这样确保了每次 dispatch_group_enter
都有对应的 dispatch_group_leave
,避免内存泄漏和其他问题。
《二》
要主动放弃 dispatch_group_notify
的调用并避免内存泄漏,可以使用 dispatch_group_wait
或其他机制来确保在特定条件下(如视图控制器被 pop
时)不再等待 dispatch_group_notify
。
使用 dispatch_group_wait
一种简单的方法是在视图控制器 dealloc
或 viewWillDisappear
方法中调用 dispatch_group_wait
,并设置一个超时时间来确保不会无限期等待。以下是一个示例:
@interface YourViewController ()
@property (nonatomic, strong) dispatch_group_t group;
@end
@implementation YourViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_enter(self.group);
dispatch_async(queue, ^{
// Some async task
dispatch_group_leave(self.group);
});
dispatch_group_enter(self.group);
dispatch_async(queue, ^{
// Another async task
dispatch_group_leave(self.group);
});
dispatch_group_notify(self.group, dispatch_get_main_queue(), ^{
NSLog(@"All tasks are done");
});
}
- (void)dealloc {
// If the view controller is deallocated, wait for a short time and then stop waiting
dispatch_group_wait(self.group, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)));
}
@end
使用标志位
另一种方法是使用一个标志位来表示视图控制器是否已经被 pop
,并在 dispatch_group_notify
回调中检查这个标志位。
@interface YourViewController ()
@property (nonatomic, strong) dispatch_group_t group;
@property (nonatomic, assign) BOOL isViewControllerPopped;
@end
@implementation YourViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.group = dispatch_group_create();
self.isViewControllerPopped = NO;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_enter(self.group);
dispatch_async(queue, ^{
// Some async task
dispatch_group_leave(self.group);
});
dispatch_group_enter(self.group);
dispatch_async(queue, ^{
// Another async task
dispatch_group_leave(self.group);
});
dispatch_group_notify(self.group, dispatch_get_main_queue(), ^{
if (!self.isViewControllerPopped) {
NSLog(@"All tasks are done");
}
});
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
self.isViewControllerPopped = YES;
}
@end
使用 dispatch_block_cancel
如果你使用 dispatch_block_t
进行异步任务调度,你可以在适当的时候调用 dispatch_block_cancel
来取消未完成的任务。这里是一个简单的示例:
@interface YourViewController ()
@property (nonatomic, strong) dispatch_group_t group;
@property (nonatomic, strong) dispatch_block_t notifyBlock;
@end
@implementation YourViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_enter(self.group);
dispatch_async(queue, ^{
// Some async task
dispatch_group_leave(self.group);
});
dispatch_group_enter(self.group);
dispatch_async(queue, ^{
// Another async task
dispatch_group_leave(self.group);
});
self.notifyBlock = dispatch_block_create(0, ^{
NSLog(@"All tasks are done");
});
dispatch_group_notify(self.group, dispatch_get_main_queue(), self.notifyBlock);
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if (self.notifyBlock) {
dispatch_block_cancel(self.notifyBlock);
self.notifyBlock = nil;
}
}
@end
总结
这些方法可以帮助你在视图控制器被 pop
时避免 dispatch_group_notify
的内存泄漏问题。选择适合你应用场景的方法,以确保异步任务管理的健壮性。
《三》
在示例代码中,如果 self.notifyBlock
被触发后,引用并不会自动指向空。你需要手动将其设为空,以确保在 viewWillDisappear
中不会重复取消已经完成的任务。
以下是更新的代码示例,确保在 notifyBlock
被执行后将其设为空:
@interface YourViewController ()
@property (nonatomic, strong) dispatch_group_t group;
@property (nonatomic, strong) dispatch_block_t notifyBlock;
@end
@implementation YourViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_enter(self.group);
dispatch_async(queue, ^{
// Some async task
dispatch_group_leave(self.group);
});
dispatch_group_enter(self.group);
dispatch_async(queue, ^{
// Another async task
dispatch_group_leave(self.group);
});
__weak typeof(self) weakSelf = self;
self.notifyBlock = dispatch_block_create(0, ^{
__strong typeof(self) strongSelf = weakSelf;
if (strongSelf) {
NSLog(@"All tasks are done");
strongSelf.notifyBlock = nil; // Ensure the block is set to nil after execution
}
});
dispatch_group_notify(self.group, dispatch_get_main_queue(), self.notifyBlock);
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if (self.notifyBlock) {
dispatch_block_cancel(self.notifyBlock);
self.notifyBlock = nil;
}
}
@end
代码说明
-
手动将
notifyBlock
设为空:
在notifyBlock
被触发后,手动将其设为空。self.notifyBlock = nil; // Ensure the block is set to nil after execution
-
确保在
viewWillDisappear
中取消并清空notifyBlock
:- (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; if (self.notifyBlock) { dispatch_block_cancel(self.notifyBlock); self.notifyBlock = nil; } }
这样可以确保在 notifyBlock
被触发后,其引用被设为空,从而避免在 viewWillDisappear
中重复取消已经完成的任务。
总结
在异步任务完成后,手动将 notifyBlock
设为空,以确保在视图控制器消失时不会重复取消已经完成的任务。这样可以避免内存泄漏,并确保任务管理的健壮性。
网友评论