Skip to content

Dealloc completion blocks on the context they're scheduled on #38

@lilyball

Description

@lilyball

We should at least do this for .main, if not other contexts.

PMHTTP does something similar for its completion blocks. It uses code like

extension HTTPManagerRequestPerformable {
    /// A workaround to ensure the completion block deinits on the queue where it's fired from.
    ///
    /// Ownership of blocks is not passed directly into the queue that runs them, which means they could get
    /// deallocated from the calling thread if the queue runs the block before control returns back to that
    /// calling thread. We guard against that here by forcing the completion block to be released immediately
    /// after it's fired. This ensures that it can only be deallocated on the completion queue or the thread that
    /// creates the task in the first place.
    fileprivate func completionThunk(for block: @escaping (_ task: HTTPManagerTask, _ result: HTTPManagerTaskResult<ResultValue>) -> Void) -> (HTTPManagerTask, HTTPManagerTaskResult<ResultValue>) -> Void {
        var unmanagedThunk: Unmanaged<Thunk<(HTTPManagerTask, HTTPManagerTaskResult<ResultValue>), Void>>? = Unmanaged.passRetained(Thunk(block))
        return { (task, result) in
            let thunk = unmanagedThunk.unsafelyUnwrapped.takeRetainedValue()
            unmanagedThunk = nil // effectively a debug assertion that ensures we don't call the completion block twice
            thunk.block((task, result))
        }
    }
}

/// A wrapper class that allows us to stuff the block into an `Unmanaged`.
private class Thunk<Args,ReturnValue> {
    let block: (Args) -> ReturnValue
    init(_ block: @escaping (Args) -> ReturnValue) {
        self.block = block
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions