summaryrefslogtreecommitdiffhomepage
path: root/ios/Operations/ResultOperation.swift
blob: 9be0f1c0f40310a3a6135cca32c32ba44c062234 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
//
//  ResultOperation.swift
//  Operations
//
//  Created by pronebird on 23/03/2022.
//  Copyright © 2022 Mullvad VPN AB. All rights reserved.
//

import Foundation

/// Base class for operations producing result.
open class ResultOperation<Success, Failure: Error>: AsyncOperation {
    public typealias Completion = OperationCompletion<Success, Failure>
    public typealias CompletionHandler = (Completion) -> Void

    private let nslock = NSLock()
    private var completionValue: Completion?
    private var _completionQueue: DispatchQueue?
    private var _completionHandler: CompletionHandler?
    private var pendingFinish = false

    public var completion: Completion? {
        nslock.lock()
        defer { nslock.unlock() }
        return completionValue
    }

    public var completionQueue: DispatchQueue? {
        get {
            nslock.lock()
            defer { nslock.unlock() }

            return _completionQueue
        }
        set {
            nslock.lock()
            _completionQueue = newValue
            nslock.unlock()
        }
    }

    public var completionHandler: CompletionHandler? {
        get {
            nslock.lock()
            defer { nslock.unlock() }

            return _completionHandler
        }
        set {
            nslock.lock()
            defer { nslock.unlock() }
            if !pendingFinish {
                _completionHandler = newValue
            }
        }
    }

    override public init(dispatchQueue: DispatchQueue?) {
        super.init(dispatchQueue: dispatchQueue)
    }

    public init(
        dispatchQueue: DispatchQueue?,
        completionQueue: DispatchQueue?,
        completionHandler: CompletionHandler?
    ) {
        _completionQueue = completionQueue
        _completionHandler = completionHandler

        super.init(dispatchQueue: dispatchQueue)
    }

    @available(*, unavailable)
    override public func finish() {
        _finish(error: nil)
    }

    @available(*, unavailable)
    override public func finish(error: Error?) {
        _finish(error: error)
    }

    open func finish(completion: Completion) {
        nslock.lock()
        if completionValue == nil {
            completionValue = completion
        }
        nslock.unlock()

        _finish(error: completion.error)
    }

    private func _finish(error: Error?) {
        nslock.lock()
        // Bail if operation is already finishing.
        guard !pendingFinish else {
            nslock.unlock()
            return
        }

        // Mark that operation is pending finish.
        pendingFinish = true

        // Copy completion handler.
        let completionHandler = _completionHandler

        // Unset completion handler.
        _completionHandler = nil

        // Copy completion value.
        let completion = completionValue ?? .cancelled

        // Copy completion queue.
        let completionQueue = _completionQueue
        nslock.unlock()

        let block = {
            // Call completion handler.
            completionHandler?(completion)

            // Finish operation.
            super.finish(error: error)
        }

        if let completionQueue = completionQueue {
            completionQueue.async(execute: block)
        } else {
            block()
        }
    }
}