Preventing Memory Leaks and Crashes with DispatchGroup and Weak Self in Swift

MAIN THREAD – CRASHED

closure #1 (Swift.Bool) -> () in YourProject.addPublisherHandler() -> () :0
Combine
0x1a551b000 + 44748
Combine
0x1a551b000 + 44640
Combine
0x1a551b000 + 79636
Combine
0x1a551b000 + 78876
Combine
0x1a551b000 + 77648
Combine
0x1a551b000 + 77260
Redflower
closure #3 () -> () in YourProject.fetchDetails() -> () ViewModel+Network.swift:47
Redflower
reabstraction thunk helper from @escaping @callee_guaranteed () -> () to @escaping @callee_unowned @convention(block) () -> () :0
libdispatch.dylib
_dispatch_call_block_and_release
libdispatch.dylib
_dispatch_client_callout
libdispatch.dylib
_dispatch_main_queue_drain
libdispatch.dylib
_dispatch_main_queue_callback_4CF
CoreFoundation
CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE
CoreFoundation
__CFRunLoopRun
CoreFoundation
CFRunLoopRunSpecific
GraphicsServices
GSEventRunModal
UIKitCore
-[UIApplication _run]
UIKitCore
UIApplicationMain
OKEx
main main.m:33
0x0 + 0

It looks like the crash is caused by accessing self inside a DispatchGroup.notify closure after self has already been deallocated. This happens because, by default, closures capture strong references to self, which can lead to retain cycles or crashes if the object is released before the closure is executed.

ViewModel+Network.swift:47
var fetchDataPublisher = PassthroughSubject<Bool, Never>()
func fetchDetails() {
        let group = DispatchGroup()
        group.enter()
        self.fetchName() { [weak self] names in
            guard let self = self else { return }
            group.leave()
        }
         group.notify(queue: .main) { 
            fetchDataPublisher.send() // CRASHED HERE
         }
    }

The solution is to use [weak self] in the closure, as you correctly noted, to avoid retaining self and causing a crash if it is deallocated before the tasks in the DispatchGroup complete.

Here’s a concise summary of the steps involved in fixing the issue:

  1. Problem: self is being captured in a group.notify closure, and if self is deallocated before the tasks in the DispatchGroup complete, the app will crash.
  2. Solution: Use [weak self] in the group.notify closure to avoid retaining self. This ensures that the closure won’t access a deallocated object, thus preventing a crash.

Corrected Code:

func fetchDetails() {
    let group = DispatchGroup()
    group.enter()

    self.fetchName() { [weak self] names in
        guard let self = self else { return }
        group.leave()
    }

    group.notify(queue: .main) { [weak self] in
        guard let self = self else {
            print("Object was deallocated before tasks completed")
            return
        }
        self.fetchDataPublisher.send() // Ensure self exists
    }
}

Key Takeaways:

  • Weak self in closures: Ensures that self is not retained unnecessarily, preventing retain cycles and crashes when self is deallocated before the closure is executed.
  • Tasks continue executing even after self is deallocated: The tasks within the DispatchGroup will complete regardless of whether the object that initiated the group (like a view controller) has been deallocated.
  • Memory safety: By using [weak self], we ensure that the closure will not attempt to access self after it has been deallocated, thus preventing crashes.

This pattern is crucial when dealing with asynchronous tasks and dispatch groups, especially in cases where object deallocation can happen while tasks are still in progress.

In

Leave a Reply

Your email address will not be published. Required fields are marked *