2

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?

brainray
  • 12,512
  • 11
  • 67
  • 116

6 Answers6

2

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

Gabriele Petronella
  • 106,943
  • 21
  • 217
  • 235
2

You're looking into some kind of language construction called Futures and promises. You can find some examples, like:

However the language itself misses such feature.

Esse
  • 3,278
  • 2
  • 21
  • 25
1

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
    }
}
rickster
  • 124,678
  • 26
  • 272
  • 326
0

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)

Michael Gray
  • 611
  • 1
  • 5
  • 13
0

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() {
   }

}
Vlad
  • 6,402
  • 1
  • 60
  • 74
0

[Java Future and Promise]

Swift's Combine framework uses these constructions

yoAlex5
  • 29,217
  • 8
  • 193
  • 205