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
|
//
// ExclusivityController.swift
// MullvadVPN
//
// Created by pronebird on 06/07/2020.
// Copyright © 2020 Mullvad VPN AB. All rights reserved.
//
import Foundation
class ExclusivityController: NSObject {
private let lock = NSLock()
private var operations: [String: [Operation]] = [:]
private var categoriesByOperation: [Operation: [String]] = [:]
static let shared = ExclusivityController()
func addOperation(_ operation: Operation, categories: [String]) {
lock.withCriticalBlock {
categories.forEach { category in
addOperation(operation, category: category)
}
addObserverIfNeeded(operation: operation, categories: categories)
}
}
func removeOperation(_ operation: Operation, categories: [String]) {
lock.withCriticalBlock {
categories.forEach { category in
removeOperation(operation, category: category)
}
removeObserverIfNeeded(operation: operation, categories: categories)
}
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if let operation = object as? Operation, keyPath == "isFinished" {
operationDidFinish(operation)
} else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
}
}
// MARK: - Private
private func addOperation(_ operation: Operation, category: String) {
var operationsWithThisCategory = operations[category] ?? []
if let last = operationsWithThisCategory.last {
operation.addDependency(last)
}
operationsWithThisCategory.append(operation)
operations[category] = operationsWithThisCategory
}
private func removeOperation(_ operation: Operation, category: String) {
guard var operationsWithThisCategory = operations[category],
let index = operationsWithThisCategory.firstIndex(of: operation) else { return }
operationsWithThisCategory.remove(at: index)
if operationsWithThisCategory.isEmpty {
operations.removeValue(forKey: category)
} else {
operations[category] = operationsWithThisCategory
}
}
private func addObserverIfNeeded(operation: Operation, categories: [String]) {
let existingCategories = categoriesByOperation[operation] ?? []
let newCategories = existingCategories + categories
if existingCategories.isEmpty && !newCategories.isEmpty {
operation.addObserver(self, forKeyPath: "isFinished", options: .new, context: nil)
}
if !newCategories.isEmpty {
categoriesByOperation[operation] = newCategories
}
}
private func removeObserverIfNeeded(operation: Operation, categories: [String]) {
guard var newCategories = categoriesByOperation[operation] else { return }
newCategories.removeAll { s in
categories.contains(s)
}
if newCategories.isEmpty {
operation.removeObserver(self, forKeyPath: "isFinished", context: nil)
categoriesByOperation.removeValue(forKey: operation)
} else {
categoriesByOperation[operation] = newCategories
}
}
private func operationDidFinish(_ operation: Operation) {
lock.withCriticalBlock {
let operationCategories = categoriesByOperation[operation] ?? []
removeObserverIfNeeded(operation: operation, categories: operationCategories)
operationCategories.forEach { category in
removeOperation(operation, category: category)
}
}
}
}
|