summaryrefslogtreecommitdiffhomepage
path: root/ios/Operations/TransformOperation.swift
blob: 22946cbe1a4303489a37ef4354312b18c3c8ab7d (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
//
//  TransformOperation.swift
//  Operations
//
//  Created by pronebird on 31/05/2022.
//  Copyright © 2022 Mullvad VPN AB. All rights reserved.
//

import Foundation
import protocol MullvadTypes.Cancellable

public final class TransformOperation<Input, Output>: ResultOperation<Output>, InputOperation {
    public typealias InputBlock = () -> Input?

    private let nslock = NSLock()

    public var input: Input? {
        return _input
    }

    private var __input: Input?
    private var _input: Input? {
        get {
            nslock.lock()
            defer { nslock.unlock() }
            return __input
        }
        set {
            nslock.lock()
            __input = newValue
            nslock.unlock()
        }
    }

    private var inputBlock: InputBlock?

    private var executor: ((Input, @escaping (Result<Output, Error>) -> Void) -> Cancellable?)?
    private var cancellableTask: Cancellable?

    public init(
        dispatchQueue: DispatchQueue? = nil,
        input: Input? = nil,
        block: @escaping (_ input: Input, _ finish: @escaping (Result<Output, Error>) -> Void) -> Void
    ) {
        super.init(dispatchQueue: dispatchQueue)
        __input = input
        executor = { input, finish in
            block(input, finish)
            return nil
        }
    }

    public init(
        dispatchQueue: DispatchQueue? = nil,
        input: Input? = nil,
        throwingBlock: @escaping (_ input: Input) throws -> Output
    ) {
        super.init(dispatchQueue: dispatchQueue)
        __input = input
        executor = { input, finish in
            finish(Result { try throwingBlock(input) })
            return nil
        }
    }

    public init(
        dispatchQueue: DispatchQueue? = nil,
        input: Input? = nil,
        cancellableTask: @escaping (_ input: Input, _ finish: @escaping (Result<Output, Error>) -> Void) -> Cancellable
    ) {
        super.init(dispatchQueue: dispatchQueue)
        __input = input
        executor = cancellableTask
    }

    override public func main() {
        if let inputBlock {
            _input = inputBlock()
        }

        guard let inputValue = _input else {
            finish(result: .failure(OperationError.unsatisfiedRequirement))
            return
        }

        let executor = executor
        self.executor = nil

        assert(executor != nil)

        cancellableTask = executor?(inputValue, self.finish)
    }

    override public func operationDidCancel() {
        cancellableTask?.cancel()
    }

    override public func operationDidFinish() {
        executor = nil
        cancellableTask = nil
    }

    // MARK: - Input injection

    public func setInputBlock(_ block: @escaping () -> Input?) {
        dispatchQueue.async {
            self.inputBlock = block
        }
    }
}