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

import Foundation

public final class TransformOperation<Input, Output, Failure: Error>:
    ResultOperation<Output, Failure>,
    InputOperation
{
    public typealias ExecutionBlock = (Input, TransformOperation<Input, Output, Failure>) -> Void
    public typealias ThrowingExecutionBlock = (Input) throws -> Output
    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 executionBlock: ExecutionBlock?
    private var cancellationBlocks: [() -> Void] = []

    public init(
        dispatchQueue: DispatchQueue? = nil,
        input: Input? = nil,
        block: ExecutionBlock? = nil
    ) {
        __input = input
        executionBlock = block

        super.init(dispatchQueue: dispatchQueue)
    }

    public init(
        dispatchQueue: DispatchQueue? = nil,
        input: Input? = nil,
        throwingBlock: @escaping ThrowingExecutionBlock
    ) {
        __input = input
        executionBlock = Self.wrapThrowingBlock(throwingBlock)

        super.init(dispatchQueue: dispatchQueue)
    }

    override public func main() {
        let inputValue = inputBlock?()

        _input = inputValue

        guard let inputValue = inputValue, let executionBlock = executionBlock else {
            finish(completion: .cancelled)
            return
        }

        executionBlock(inputValue, self)
    }

    override public func operationDidCancel() {
        let blocks = cancellationBlocks
        cancellationBlocks.removeAll()

        for block in blocks {
            block()
        }
    }

    override public func operationDidFinish() {
        cancellationBlocks.removeAll()
        executionBlock = nil
    }

    // MARK: - Block handlers

    public func setExecutionBlock(_ block: @escaping ExecutionBlock) {
        dispatchQueue.async {
            assert(!self.isExecuting && !self.isFinished)
            self.executionBlock = block
        }
    }

    public func setExecutionBlock(_ block: @escaping ThrowingExecutionBlock) {
        setExecutionBlock(Self.wrapThrowingBlock(block))
    }

    public func addCancellationBlock(_ block: @escaping () -> Void) {
        dispatchQueue.async {
            if self.isCancelled {
                block()
            } else {
                self.cancellationBlocks.append(block)
            }
        }
    }

    // MARK: - Input injection

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

    private class func wrapThrowingBlock(_ executionBlock: @escaping ThrowingExecutionBlock)
        -> ExecutionBlock
    {
        return { input, operation in
            do {
                let value = try executionBlock(input)

                operation.finish(completion: .success(value))
            } catch {
                let castedError = error as! Failure

                operation.finish(completion: .failure(castedError))
            }
        }
    }
}