How to chain functions with a completion handler in Swift? - swift

I am chaining some functions together and I can't figure out how to call a completion handler with a return value once all the functions are done running.
func getAirQuality(completion: (aqi: Int?) -> Void) {
callAPI()
}
private func callAPI() {
// ... get data
self.parseDataForAQI(data: data)
}
private func parseDataForAQI(data: Data) {
let aqi = aqi
// Send aqi up to completion handler in getAirQuality
}
So that when everything is said and done I can just do something like this:
getAirQuality(completion: { aqi -> Void in {
// Do something with aqi
})

My first assumption is that your first 3 functions are part of a class. If so, one approach is to save the completion handler as an instance variable.
class AirQualityProvider {
var aBlock: ((Int?) -> Void)?
func getAirQuality(completion: #escaping (Int?) -> Void) {
aBlock = completion
callAPI()
}
private func callAPI() {
let data = Data()
parseDataForAQI(data: data)
}
private func parseDataForAQI(data: Data) {
let aqi = 1
if let completion = aBlock {
completion(aqi)
}
}
}
Here's an example of a caller as written in a playground.
let aqp = AirQualityProvider()
aqp.getAirQuality { (value) in
if let value = value {
print("Value = \(value)")
}
}

Related

How to append a closure to another closure?

So I have my first closure here:
var instructions: (() -> Void) = {
print("First")
}
instructions() /// prints "First"
Now I have another closure:
let additionalInstructions: (() -> Void) = {
print("Second")
}
additionalInstructions() /// prints "Second"
I want to "append" additionalInstructions to the end of instructions... is this possible? I tried making a new closure containing both of them, like this:
let finalInstructions: (() -> Void) = {
instructions()
additionalInstructions()
}
finalInstructions()
This prints
First
Second
But, when I replace instructions with finalInstructions, I get an EXC_BAD_ACCESS.
instructions = finalInstructions
instructions() /// error here
I think this is because closures are reference types, and some sort of loop happens when instructions contains itself. Is there a way to avoid the error? Making a new closure like finalInstructions, just as a container, also seems kind of clunky.
This is some interesting semantics I didn't expect, but what you're seeing is an infinite recursion, which crashes due to a stack overflow. You can confirm this by running this code in a project, where the debugger will catch the error. You'll see thousands of stackframes of the same closure being called.
It appears that the reference to instructions within your closure is capturing its new, self-referential value, rather than its previous value.
Your approach of using a new variable like finalInstructions is much better. Not only because it avoids this issue, but also because it's much more readable code.
Here's a simpler, more minimal demonstration of the problem:
var closure = { print("initial definition") }
closure() // prints "initial definition"
closure = {
print("second definition")
closure()
}
closure() // prints "second definition" repeatedly, until the stack overflows
FYI, I asked about this on the Swift forums: https://forums.swift.org/t/mutable-closures-can-capture-themselves/43228
It's easy to put them together. It's not easy to take them apart, like C#'s delegates are.
To do that, you'll need to maintain a bunch of junk like below. Better to use Combine, these days.
final class MultiClosureTestCase: XCTestCase {
func test_multiClosure() {
var x = 0
let closures: [EquatableClosure<()>] = [
.init { _ in x += 1 },
.init { _ in x += 2 }
]
let multiClosure = MultiClosure(closures)
multiClosure()
XCTAssertEqual(x, 3)
multiClosure -= closures.first!
multiClosure()
XCTAssertEqual(x, 5)
}
func test_setRemoval() {
let closure: EquatableClosure<()>! = .init { _ in }
let multiClosure = MultiClosure(closure)
XCTAssertNotEqual(multiClosure.closures, [])
XCTAssertNotEqual(closure.multiClosures, [])
multiClosure -= closure
XCTAssertEqual(multiClosure.closures, [])
XCTAssertEqual(closure.multiClosures, [])
}
func test_deallocation() {
var closure: EquatableClosure<()>! = .init { _ in }
var multiClosure: MultiClosure! = .init(closure)
XCTAssertNotEqual(multiClosure.closures, [])
closure = nil
XCTAssertEqual(multiClosure.closures, [])
closure = EquatableClosure { _ in }
multiClosure += closure
XCTAssertNotEqual(closure.multiClosures, [])
multiClosure = nil
XCTAssertEqual(closure.multiClosures, [])
}
}
/// Use this until Swift has unowned-referencing collections.
public final class UnownedReferencer<Reference: AnyObject>: HashableViaID {
public init(_ reference: Reference) {
self.reference = reference
}
public unowned let reference: Reference
}
/// Use this until Swift has weak-referencing collections.
public final class WeakReferencer<Reference: AnyObject>: HashableViaID {
public init(_ reference: Reference) {
self.reference = reference
}
public weak var reference: Reference?
}
/// Remove the first `UnownedReferencer` with this `reference`
public func -= <Reference: Equatable>(
set: inout Set< UnownedReferencer<Reference> >,
reference: Reference
) {
guard let referencer = ( set.first { $0.reference == reference} )
else { return }
set.remove(referencer)
}
/// Remove the first `WeakReferencer` with this `reference`
public func -= <Reference: Equatable>(
set: inout Set< WeakReferencer<Reference> >,
reference: Reference
) {
guard let referencer = ( set.first { $0.reference == reference } )
else { return }
set.remove(referencer)
}
/// A workaround for Swift not providing a way to remove closures
/// from a collection of closures.
///- Note: Designed for one-to-many events, hence no return value.
/// Returning a single value from multiple closures doesn't make sense.
public final class MultiClosure<Input>: Equatable {
public init(_ closures: EquatableClosure<Input>...) {
self += closures
}
public init<Closures: Sequence>(_ closures: Closures)
where Closures.Element == EquatableClosure<Input> {
self += closures
}
var closures: Set<
UnownedReferencer< EquatableClosure<Input> >
> = []
// MARK: deallocation
// We can't find self in `closures` without this.
fileprivate lazy var unownedSelf = UnownedReferencer(self)
// Even though this MultiClosure will be deallocated,
// its corresponding WeakReferencers won't be,
// unless we take this manual action or similar.
deinit {
for closure in closures {
closure.reference.multiClosures.remove(unownedSelf)
}
}
}
public extension MultiClosure {
/// Execute every closure
func callAsFunction(_ input: Input) {
for closure in closures {
closure.reference(input)
}
}
}
public extension MultiClosure where Input == () {
/// Execute every closure
func callAsFunction() {
self(())
}
}
/// A wrapper around a closure, for use with MultiClosures
public final class EquatableClosure<Input>: Equatable {
public init(_ closure: #escaping (Input) -> Void) {
self.closure = closure
}
/// Execute the closure
func callAsFunction(_ input: Input) {
closure(input)
}
private let closure: (Input) -> Void
// MARK: deallocation
var multiClosures: Set<
UnownedReferencer< MultiClosure<Input> >
> = []
// We can't find self in `multiClosures` without this.
fileprivate lazy var unownedSelf = UnownedReferencer(self)
deinit {
for multiClosure in multiClosures {
multiClosure.reference.closures.remove(unownedSelf)
}
}
}
/// Add `closure` to the set of closures that runs
/// when `multiClosure` does
public func += <Input>(
multiClosure: MultiClosure<Input>,
closure: EquatableClosure<Input>
) {
multiClosure.closures.formUnion([closure.unownedSelf])
closure.multiClosures.formUnion([multiClosure.unownedSelf])
}
/// Add `closures` to the set of closures that runs
/// when `multiClosure` does
public func += <
Input,
Closures: Sequence
>(
multiClosure: MultiClosure<Input>,
closures: Closures
)
where Closures.Element == EquatableClosure<Input> {
for closure in closures {
multiClosure += closure
}
}
/// Remove `closure` from the set of closures that runs
/// when `multiClosure` does
public func -= <Input>(
multiClosure: MultiClosure<Input>,
closure: EquatableClosure<Input>
) {
multiClosure.closures.remove(closure.unownedSelf)
closure.multiClosures.remove(multiClosure.unownedSelf)
}
/// An `Identifiable` instance that uses its `id` for equality and hashability.
public protocol HashableViaID: Hashable, Identifiable { }
// MARK: - Equatable
public extension HashableViaID {
static func == (equatable0: Self, equatable1: Self) -> Bool {
equatable0.id == equatable1.id
}
}
// MARK: - Hashable
public extension HashableViaID {
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
}
// MARK: - AnyObject
public extension Equatable where Self: AnyObject {
static func == (class0: Self, class1: Self) -> Bool {
class0 === class1
}
}

Swift Combine: Check if Subject has observer?

In RxSwift we can check if a *Subject has any observer, using hasObserver, how can I do this in Combine on e.g. a PassthroughSubject?
Some time after posting my question I wrote this simple extension. Much simpler than #Asperi's solution. Not sure about disadvantages/advantages between the two solutions besides simplicity (of mine).
private enum CounterChange: Int, Equatable {
case increased = 1
case decreased = -1
}
extension Publisher {
func trackNumberOfSubscribers(
_ notifyChange: #escaping (Int) -> Void
) -> AnyPublisher<Output, Failure> {
var counter = NSNumber.init(value: 0)
let nsLock = NSLock()
func updateCounter(_ change: CounterChange, notify: (Int) -> Void) {
nsLock.lock()
counter = NSNumber(value: counter.intValue + change.rawValue)
notify(counter.intValue)
nsLock.unlock()
}
return handleEvents(
receiveSubscription: { _ in updateCounter(.increased, notify: notifyChange) },
receiveCompletion: { _ in updateCounter(.decreased, notify: notifyChange) },
receiveCancel: { updateCounter(.decreased, notify: notifyChange) }
).eraseToAnyPublisher()
}
}
Here are some tests:
import XCTest
import Combine
final class PublisherTrackNumberOfSubscribersTest: TestCase {
func test_four_subscribers_complete_by_finish() {
doTest { publisher in
publisher.send(completion: .finished)
}
}
func test_four_subscribers_complete_by_error() {
doTest { publisher in
publisher.send(completion: .failure(.init()))
}
}
}
private extension PublisherTrackNumberOfSubscribersTest {
struct EmptyError: Swift.Error {}
func doTest(_ line: UInt = #line, complete: (PassthroughSubject<Int, EmptyError>) -> Void) {
let publisher = PassthroughSubject<Int, EmptyError>()
var numberOfSubscriptions = [Int]()
let trackable = publisher.trackNumberOfSubscribers { counter in
numberOfSubscriptions.append(counter)
}
func subscribe() -> Cancellable {
return trackable.sink(receiveCompletion: { _ in }, receiveValue: { _ in })
}
let cancellable1 = subscribe()
let cancellable2 = subscribe()
let cancellable3 = subscribe()
let cancellable4 = subscribe()
XCTAssertNotNil(cancellable1, line: line)
XCTAssertNotNil(cancellable2, line: line)
XCTAssertNotNil(cancellable3, line: line)
XCTAssertNotNil(cancellable4, line: line)
cancellable1.cancel()
cancellable2.cancel()
complete(publisher)
XCTAssertEqual(numberOfSubscriptions, [1, 2, 3, 4, 3, 2, 1, 0], line: line)
}
}
No one time needed this... Apple does not provide this by API, and, actually, I do not recommend such thing, because it is like manually checking value of retainCount in pre-ARC Objective-C for some decision in code.
Anyway it is possible. Let's consider it as a lab exercise. Hope someone find this helpful.
Disclaimer: below code was not tested with all Publisher(s) and not safe as for some real-world project. It is just approach demo.
So, as there are many kind of publishers and all of them are final and private and, moreover there might be come via type-eraser, we needed generic thing applying to any publisher, thus operator
extension Publisher {
public func countingSubscribers(_ callback: ((Int) -> Void)? = nil)
-> Publishers.SubscribersCounter<Self> {
return Publishers.SubscribersCounter<Self>(upstream: self, callback: callback)
}
}
Operator gives us possibility to inject in any place of of publishers chain and provide interesting value via callback. Interesting value in our case will be count of subscribers.
As operator is injected in both Upstream & Downstream we need bidirectional custom pipe implementation, ie. custom publisher, custom subscriber, custom subscription. In our case they must be transparent, as we don't need to modify streams... actually it will be Combine-proxy.
Posible usage:
1) when SubscribersCounter publisher is last in chain, the numberOfSubscribers property can be used directly
let publisher = NotificationCenter.default
.publisher(for: UIApplication.didBecomeActiveNotification)
.countingSubscribers()
...
publisher.numberOfSubscribers
2) when it somewhere in the middle of the chain, then receive callback about changed subscribers count
let publisher = URLSession.shared
.dataTaskPublisher(for: URL(string: "https://www.google.com")!)
.countingSubscribers({ count in print("Observers: \(count)") })
.receive(on: DispatchQueue.main)
.map { _ in "Data received" }
.replaceError(with: "An error occurred")
Here is implementation:
import Combine
extension Publishers {
public class SubscribersCounter<Upstream> : Publisher where Upstream : Publisher {
private(set) var numberOfSubscribers = 0
public typealias Output = Upstream.Output
public typealias Failure = Upstream.Failure
public let upstream: Upstream
public let callback: ((Int) -> Void)?
public init(upstream: Upstream, callback: ((Int) -> Void)?) {
self.upstream = upstream
self.callback = callback
}
public func receive<S>(subscriber: S) where S : Subscriber,
Upstream.Failure == S.Failure, Upstream.Output == S.Input {
self.increase()
upstream.receive(subscriber: SubscribersCounterSubscriber<S>(counter: self, subscriber: subscriber))
}
fileprivate func increase() {
numberOfSubscribers += 1
self.callback?(numberOfSubscribers)
}
fileprivate func decrease() {
numberOfSubscribers -= 1
self.callback?(numberOfSubscribers)
}
// own subscriber is needed to intercept upstream/downstream events
private class SubscribersCounterSubscriber<S> : Subscriber where S: Subscriber {
let counter: SubscribersCounter<Upstream>
let subscriber: S
init (counter: SubscribersCounter<Upstream>, subscriber: S) {
self.counter = counter
self.subscriber = subscriber
}
deinit {
Swift.print(">> Subscriber deinit")
}
func receive(subscription: Subscription) {
subscriber.receive(subscription: SubscribersCounterSubscription<Upstream>(counter: counter, subscription: subscription))
}
func receive(_ input: S.Input) -> Subscribers.Demand {
return subscriber.receive(input)
}
func receive(completion: Subscribers.Completion<S.Failure>) {
subscriber.receive(completion: completion)
}
typealias Input = S.Input
typealias Failure = S.Failure
}
// own subcription is needed to handle cancel and decrease
private class SubscribersCounterSubscription<Upstream>: Subscription where Upstream: Publisher {
let counter: SubscribersCounter<Upstream>
let wrapped: Subscription
private var cancelled = false
init(counter: SubscribersCounter<Upstream>, subscription: Subscription) {
self.counter = counter
self.wrapped = subscription
}
deinit {
Swift.print(">> Subscription deinit")
if !cancelled {
counter.decrease()
}
}
func request(_ demand: Subscribers.Demand) {
wrapped.request(demand)
}
func cancel() {
wrapped.cancel()
if !cancelled {
cancelled = true
counter.decrease()
}
}
}
}
}

Could not infer generic parameter when calling protocol static method

Using a Playground and given these definitions:
import Foundation
protocol MoneyTrakObject {
var key: String { get set }
init()
}
extension MoneyTrakObject {
static func objectFromDB<T: MoneyTrakObject>(for key: String, queue: DispatchQueue? = nil, completion: #escaping (T) -> Void) -> String? {
// after data is retrieved, call completion closure
let valueObject = T()
completion(valueObject)
return "dbToken"
}
}
protocol Transaction: MoneyTrakObject {
var amount: Int { get set }
}
struct BasicTransaction: Transaction {
var key = UUID().uuidString
var amount = 0
init() {}
}
struct RecurringTransaction: Transaction {
var key = UUID().uuidString
var amount = 0
init() {}
}
I would expect that I could do this:
let token1 = BasicTransaction.objectFromDB(for: "") { (transaction) in
// use transaction
}
let token2 = RecurringTransaction.objectFromDB(for: "") { (transaction) in
// use transaction
}
However I get the Generic parameter 'T' could not be inferred error when calling the static method and I'm not sure why.
I do not see why you need the generic constraint. If you change the extension of your protocol to this:
extension MoneyTrakObject {
static func objectFromDB(for key: String, queue: DispatchQueue? = nil, completion: #escaping (Self) -> Void) -> String? {
// after data is retrieved, call completion closure
let valueObject = Self()
completion(valueObject)
return "dbToken"
}
}
Your code compiles just fine. Self is a placeholder for the actually implementing type.
Well... the only place where T is used is inside the completion handler argument. When you write this:
let token1 = BasicTransaction.objectFromDB(for: "") { (transaction) in
// use transaction
}
The compiler has no idea what type transaction is and hence cannot specialize the generic function. Provide some type info like this:
let token1 = BasicTransaction.objectFromDB(for: "") { (transaction: Transaction) in
// use transaction
}
let token2 = BasicTransaction.objectFromDB(for: "") { (transaction: BasicTransaction) in
// use transaction
}

Working with Swift completion handlers for chained functions

I am chaining some functions together and I can't figure out how to call a completion handler with a return value once all the functions are done running.
class AirQualityProvider {
var aBlock: ((Int?) -> Void)?
func getAirQuality(completion: #escaping (Int?) -> Void) {
aBlock = completion
callAPI()
}
private func callAPI() {
let data = Data()
parseDataForAQI(data: data)
}
private func parseDataForAQI(data: Data) {
for d in data {
dosomeMath(d)
}
}
private func dosomeMath(data: Int) {
// HERE IS WHERE I WANT IT TO SUM UP ALL THE NUMBERS
THEN ONLY RETURN ONE VALUE using a completion handler.
Currently, it returns the average as it is being generated.
}
Almost got it working with help to Alexander. The code Alexander supplied works perfectly, it is amazing. The issue is, when I run taskrunner inside alamofire it returns empty. Outside alamofire it works as usual. I need to run this inside alamofire.
func A(json : JSON){
for (key,subJson) in json{
if subJson["free"].doubleValue > 0.0 {
func B(asset: subJson["asset"].stringValue, json: subJson)
}
}
print(taskRunner.getResults())
}
func B(asset : String, json : JSON){
//OUTSIDE ALAMOFIRE WORKS
self.taskRunner.execute{
return 100
}
Alamofire.request(url).responseJSON { response in
//INSIDE ALAMOFIRE DOESN'T WORK. Returns []
self.taskRunner.execute{
return 100
}
}
}
I would use a dispatch queue to synchronize the aggregation of results (by synchronizing Array.append(_:) calls, and the subsequent reading of the array). Here's a simple example:
import Dispatch
import Foundation
class ParallelTaskRunner<Result> {
private var results = [Result]()
private let group = DispatchGroup()
private let resultAggregatorQueue = DispatchQueue(label: "Result Aggregator")
func execute(_ closure: (#escaping (Result) -> Void) -> Void) {
group.enter() // Register that a new task is in-flight
closure { result in
self.resultAggregatorQueue.sync { // Synchronize access to the array
self.results.append(result) // Record the result
}
self.group.leave() // This task is done
}
}
func getResults() -> [Result] {
group.wait() // Make sure all in-flight tasks are done
return resultAggregatorQueue.sync { return results }
}
}
let taskQueue = DispatchQueue(label: "Task Queue", attributes: .concurrent)
let taskRunner = ParallelTaskRunner<Int>()
for i in 0...100 {
taskRunner.execute { completionHandler in
taskQueue.async { // Simulated async computation
let randomTime = 3.0
print("Sleeping for \(randomTime)")
Thread.sleep(forTimeInterval: randomTime) // Simulates intesnive computation
let result = i // Simulate a result
completionHandler(result)
}
}
}
print(taskRunner.getResults()) // Oh look, all the results are here! :D

Are there anything similar to Java's "Future" in Swift?

Java has Future or FutureTask that can run a task in a new thread. Then, return the execution result to the original thread. Are there any feature in Swift can achieve that?
You're looking into some kind of language construction called Futures and promises. You can find some examples, like:
https://bitbucket.org/al45tair/async (C#-like async/await primitives in Swift)
https://github.com/mxcl/PromiseKit (Promise kit http://promisekit.org/)
mentioned earlier https://github.com/Thomvis/BrightFutures
However the language itself misses such feature.
Not provided by the language (meaning the standard library), but you can surely roll your own or simply use a library such as https://github.com/Thomvis/BrightFutures
If Apple did implement Futures or Promises in Swift, would they say so? After all, they always avoid talking about Future products. ;)
Anyway, the original question seems to be generally about ways to do asynchronous work, not necessarily about specifically doing that with a Futures/Promises style model. So, while the third party libraries mentioned in other answers are great for that model, you can also do asynchronous work without that model using the same iOS & OS X built-in APIs that you can from ObjC: dispatch or NSOperation. For example:
NSOperationQueue().addOperationWithBlock {
// do background work
NSOperationQueue.mainQueue().addOperationWithBlock {
// back to main thread for follow up work
}
}
There is also now FutureKit
Similar to BrightFuture, but does composition more like BFTask
And I should mention Bolts BFTask, which while written in Objective-C is also a good candidate. (And is now used inside of Facebook iOS SDK)
I end up with the following solution (iOS SDK only, Swift 3) based on Operation and OperationQueue classes:
In short: Wrapping code into synchronous or asynchronous operation. Chaining operations using utility class. Adding operations into serial queue.
In case of error there is no need to cancel current operation, just skip actual code. Additionally asynchronous execution blocks must call finalize callback to inform operation queue about completion. Optionally DispatchQueue can be provided as parameter. Block of code will be asynchronously executed on that queue.
fileprivate func publishProductOnWebsite(listing: Listing) {
var resultSKU: String?
var resultError: Swift.Error?
let chain = OperationsChain{ isExecuting, finalize in
let task = ServerAPI.create(publishInfo: listing.publishInfo) { sku, error in
guard isExecuting() else {
return // We are canceled. Nothing to do.
}
if let error = error {
resultError = error
} else if let sku = sku {
resultSKU = sku // Arbitrary thread. But OK as this example for serial operation queue.
}
finalize() // This will finish asynchronous operation
}
task.resume()
}
chain.thenAsync(blockExecutionQueue: DispatchQueue.main) { _, finalize in
if let sku = resultSKU {
listing.sku = sku
DBStack.mainContext.saveIfHasChanges(savingParent: true) { error in
resultError = error
finalize()
}
} else {
finalize()
}
}
chain.thenSync(blockExecutionQueue: DispatchQueue.main) { [weak self] in
if let error = resultError {
self?.handleError(error) // Executed on Main thread.
} else {
self?.trackPublish()
self?.eventHandler?(.publishCompleted)
}
}
operationQueue.cancelAllOperations()
operationQueue.addOperations(chain.operations, waitUntilFinished: false)
}
OperationsChain class: Wraps block of code into Operation and saves operation into operations array maintaining dependencies.
public class OperationsChain {
public private(set) var operations = [Operation]()
public init(blockExecutionQueue: DispatchQueue? = nil,
executionBlock: #escaping AsynchronousBlockOperation.WorkItemBlock) {
let op = AsynchronousBlockOperation(blockExecutionQueue: blockExecutionQueue, executionBlock: executionBlock)
operations.append(op)
}
public init(blockExecutionQueue: DispatchQueue? = nil,
executionBlock: #escaping SynchronousBlockOperation.WorkItemBlock) {
let op = SynchronousBlockOperation(blockExecutionQueue: blockExecutionQueue, executionBlock: executionBlock)
operations.append(op)
}
#discardableResult
public func thenAsync(blockExecutionQueue: DispatchQueue? = nil,
executionBlock: #escaping AsynchronousBlockOperation.WorkItemBlock) -> AsynchronousBlockOperation {
let op = AsynchronousBlockOperation(blockExecutionQueue: blockExecutionQueue, executionBlock: executionBlock)
if let lastOperation = operations.last {
op.addDependency(lastOperation)
} else {
assertionFailure()
}
operations.append(op)
return op
}
#discardableResult
public func thenSync(blockExecutionQueue: DispatchQueue? = nil,
executionBlock: #escaping SynchronousBlockOperation.WorkItemBlock) -> SynchronousBlockOperation {
let op = SynchronousBlockOperation(blockExecutionQueue: blockExecutionQueue, executionBlock: executionBlock)
if let lastOperation = operations.last {
op.addDependency(lastOperation)
} else {
assertionFailure()
}
operations.append(op)
return op
}
}
SynchronousBlockOperation and AsynchronousBlockOperation classes.
public final class SynchronousBlockOperation: Operation {
public typealias WorkItemBlock = (Void) -> Void
fileprivate var executionBlock: WorkItemBlock?
fileprivate var blockExecutionQueue: DispatchQueue?
public init(blockExecutionQueue: DispatchQueue? = nil, executionBlock: #escaping SynchronousBlockOperation.WorkItemBlock) {
self.blockExecutionQueue = blockExecutionQueue
self.executionBlock = executionBlock
super.init()
}
public override func main() {
if let queue = blockExecutionQueue {
queue.async { [weak self] in
self?.executionBlock?()
}
} else {
executionBlock?()
}
}
}
open class AsynchronousBlockOperation: AsynchronousOperation {
public typealias FinaliseBlock = (Void) -> Void
public typealias StatusBlock = (Void) -> Bool
public typealias WorkItemBlock = (#escaping StatusBlock, #escaping FinaliseBlock) -> Void
fileprivate var executionBlock: WorkItemBlock?
fileprivate var blockExecutionQueue: DispatchQueue?
public init(blockExecutionQueue: DispatchQueue? = nil, executionBlock: #escaping AsynchronousBlockOperation.WorkItemBlock) {
self.blockExecutionQueue = blockExecutionQueue
self.executionBlock = executionBlock
super.init()
}
open override func onStart() {
if let queue = blockExecutionQueue {
queue.async { [weak self] in
self?.executionBlock?({ return self?.isExecuting ?? false }) {
self?.finish()
}
}
} else {
executionBlock?({ [weak self] in return self?.isExecuting ?? false }) { [weak self] in
self?.finish()
}
}
}
}
AsynchronousOperation class: Reusable subclass of Operation.
open class AsynchronousOperation: Operation {
fileprivate var lockOfProperties = NonRecursiveLock.makeDefaultLock()
fileprivate var lockOfHandlers = NonRecursiveLock.makeDefaultLock()
fileprivate var mFinished = false
fileprivate var mExecuting = false
}
extension AsynchronousOperation {
public final override var isAsynchronous: Bool {
return true
}
public final override var isExecuting: Bool {
return lockOfProperties.synchronized { mExecuting }
}
public final override var isFinished: Bool {
return lockOfProperties.synchronized { mFinished }
}
}
extension AsynchronousOperation {
public final override func start() {
if isCancelled || isFinished || isExecuting {
return
}
willChangeValue(forKey: "isExecuting")
lockOfProperties.synchronized { mExecuting = true }
onStart()
didChangeValue(forKey: "isExecuting")
}
public final override func cancel() {
super.cancel()
if isExecuting {
onCancel()
finish()
} else {
onCancel()
lockOfProperties.synchronized {
mExecuting = false
mFinished = true
}
}
}
public final func finish() {
willChangeValue(forKey: "isExecuting")
willChangeValue(forKey: "isFinished")
lockOfProperties.synchronized {
mExecuting = false
mFinished = true
}
onFinish()
didChangeValue(forKey: "isExecuting")
didChangeValue(forKey: "isFinished")
}
}
extension AsynchronousOperation {
/// Subclasses must launch job here.
///
/// **Note** called between willChangeValueForKey and didChangeValueForKey calls, but after property mExecuting is set.
open func onStart() {
}
/// Subclasses must cancel job here.
///
/// **Note** called immediately after calling super.cancel().
open func onCancel() {
}
/// Subclasses must release job here.
///
/// **Note** called between willChangeValueForKey and didChangeValueForKey calls,
/// but after properties mExecuting and mFinished are set.
open func onFinish() {
}
}
[Java Future and Promise]
Swift's Combine framework uses these constructions