summaryrefslogtreecommitdiffhomepage
path: root/ios/MullvadVPN/Operations/AsyncOperation.swift
blob: 98a5f8aa393f290a8f73cf2e818390ea13d0e071 (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
132
133
134
135
136
137
138
139
140
141
142
//
//  AsyncOperation.swift
//  MullvadVPN
//
//  Created by pronebird on 01/06/2020.
//  Copyright © 2020 Mullvad VPN AB. All rights reserved.
//

import Foundation

/// A base implementation of an asynchronous operation
class AsyncOperation: Operation, OperationProtocol {

    /// A state lock used for manipulating the operation state flags in a thread safe fashion.
    fileprivate let stateLock = NSRecursiveLock()

    /// Operation state flags.
    private var _isExecuting = false
    private var _isFinished = false
    private var _isCancelled = false

    final override var isExecuting: Bool {
        return stateLock.withCriticalBlock { _isExecuting }
    }

    final override var isFinished: Bool {
        return stateLock.withCriticalBlock { _isFinished }
    }

    final override var isCancelled: Bool {
        return stateLock.withCriticalBlock { _isCancelled }
    }

    final override var isAsynchronous: Bool {
        return true
    }

    final override func start() {
        stateLock.withCriticalBlock {
            if self._isCancelled {
                self.finish()
            } else {
                self.setExecuting(true)
                self.main()
            }
        }
    }

    override func main() {
        // Override in subclasses
    }

    /// Cancel operation
    /// Subclasses should override `operationDidCancel` instead
    final override func cancel() {
        stateLock.withCriticalBlock {
            if !self._isCancelled {
                self.setCancelled(true)

                if self._isExecuting {
                    self.operationDidCancel()
                }
            }
        }
    }

    /// Override in subclasses to support task cancellation.
    /// Subclasses should call `finish()` to complete the operation
    func operationDidCancel() {
        // no-op
    }

    final func finish() {
        stateLock.withCriticalBlock {
            if !self._isFinished {
                self.observers.forEach { $0.operationWillFinish(self) }
            }

            if self._isExecuting {
                self.setExecuting(false)
            }

            if !self._isFinished {
                self.setFinished(true)
                self.observers.forEach { $0.operationDidFinish(self) }
            }
        }
    }

    private func setExecuting(_ value: Bool) {
        willChangeValue(for: \.isExecuting)
        _isExecuting = value
        didChangeValue(for: \.isExecuting)
    }

    private func setFinished(_ value: Bool) {
        willChangeValue(for: \.isFinished)
        _isFinished = value
        didChangeValue(for: \.isFinished)
    }

    private func setCancelled(_ value: Bool) {
        willChangeValue(for: \.isCancelled)
        _isCancelled = value
        didChangeValue(for: \.isCancelled)
    }

    // MARK: - Observation

    /// The operation observers.
    fileprivate var observers: [AnyOperationObserver<AsyncOperation>] = []

    /// Add type-erased operation observer
    fileprivate func addAnyObserver(_ observer: AnyOperationObserver<AsyncOperation>) {
        stateLock.withCriticalBlock {
            self.observers.append(observer)
        }
    }
}

/// This extension exists because Swift has some issues to infer the
extension OperationProtocol where Self: AsyncOperation {
    func addObserver<T: OperationObserver>(_ observer: T) where T.OperationType == Self {
        let transform = TransformOperationObserver<AsyncOperation>(observer)
        let wrapped = AnyOperationObserver(transform)
        addAnyObserver(wrapped)
    }
}


protocol OperationSubclassing {
    /// Use this method in subclasses or extensions where you would like to synchronize
    /// the class members access using the same lock used for guarding from race conditions
    /// when managing operation state.
    func synchronized<T>(_ body: () -> T) -> T
}

extension AsyncOperation: OperationSubclassing {
    func synchronized<T>(_ body: () -> T) -> T {
        return stateLock.withCriticalBlock(body)
    }
}