Observer pattern with actor - swift

I try to implement some repository that can notify about change its state.
protocol Observer {
}
actor Repository: Actor {
var observers: [Observer] = []
func add(observer: Observer) {
self.observers.append(observer)
}
}
and add unit test for it
func test_get_update() async {
let repository = Repository()
let observer = MockObserver()
await repository.add(observer: observer)
}
and get warning
Non-sendable type 'any Observer' passed in implicitly asynchronous call to actor-isolated instance method 'add(observer:)' cannot cross actor boundary
but in main app there is no warning.
class Bar {
func foo() async {
let repository = Repository()
let observer = MockObserver()
await repository.add(observer: observer)
}
}
I don't understand is this implementation of repository correct or not?

You could certainly make your Observer a Sendable type:
protocol Observer: Sendable {
func observe(something: Int)
}
struct MockObserver: Observer {
func observe(something: Int) {
print("I observed \(something)")
}
}
actor Repository {
private var observers: [any Observer] = []
func add(observer: any Observer) {
observers.append(observer)
}
private func notifyObservers(value: Int) {
for observer in observers {
observer.observe(something: value)
}
}
func doSomething() {
notifyObservers(value: 42)
}
}
let myRepo = Repository()
Task {
let myObserver = MockObserver()
await myRepo.add(observer: myObserver)
await myRepo.doSomething()
}
But since Sendable imposes so many restrictions on things, that might make implementing an interesting observer a challenge.
I would be inclined to use the Observer implementations already available in the Combine framework:
actor AnotherRepo {
nonisolated let interestingState: AnyPublisher<Int, Never>;
private let actualState = PassthroughSubject<Int, Never>();
init() {
interestingState = actualState.eraseToAnyPublisher()
}
func doSomething() {
actualState.send(42)
}
}
let anotherRepo = AnotherRepo()
anotherRepo.interestingState.sink { print("I also observed \($0)") }
Task {
await anotherRepo.doSomething()
}

Related

Generic protocol for observing changes in Swift 5

Trying hard to code in Swift 5 the Java example below.
Generally, I want to have an Observable protocol which will be adopted by multiple other protocols. I need these protocols to be types in functions' arguments, so that these functions can add additional observers.
In Java, it is very easy to do. The code prints out:
Observer 1 changed to 10
Observer 2 changed to 10
,
interface Observable<O> {
void addObserver(O observer);
}
interface Settings extends Observable<SettingsObserver> {
void setInterval(int interval);
}
interface SettingsObserver {
void intervalChanged(int interval);
}
class AppSettings implements Settings {
private List<SettingsObserver> observers = new ArrayList<>();
#Override public void addObserver(SettingsObserver observer) { observers.add(observer); }
#Override public void setInterval(int interval) { observers.forEach(observer -> observer.intervalChanged(interval)); }
}
class Observer1 implements SettingsObserver {
#Override public void intervalChanged(int interval) {
System.out.println("Observer 1 changed to " + interval);
}
}
class Observer2 implements SettingsObserver {
#Override public void intervalChanged(int interval) {
System.out.println("Observer 2 changed to " + interval);
}
}
class Main {
public static void main(String[] args) {
Observer1 observer1 = new Observer1();
Settings settings = new AppSettings();
settings.addObserver(observer1);
Main main = new Main();
main.run(settings);
}
void run(Settings settings) {
Observer2 observer2 = new Observer2();
settings.addObserver(observer2);
settings.setInterval(10);
}
}
While it's simple to create a generic wrapper to which you can add your own observables, there are two native solutions that you should use instead.
Notifications.
When value is changed, send a notification using NotificationCenter.default. Observers should listen to these notifications. Notification are a crucial part of the ecosystem:
class AppSettings {
enum Notifications {
static let intervalChanged = Notification.Name("AppSettingsIntervalChangedNotification")
}
var interval: TimeInterval = 0 {
didSet {
NotificationCenter.default.post(name: Notifications.intervalChanged, object: self)
}
}
}
let settings = AppSettings()
let observer = NotificationCenter.default.addObserver(
forName: AppSettings.Notifications.intervalChanged,
object: settings,
queue: nil
) { [weak settings] _ in
guard let settings = settings else { return }
print(settings.interval)
}
settings.interval = 10
Key-value observing (KVO)
If you inherit your objects from NSObject, you can simply add a direct observer to any Obj-C compatible value:
class AppSettings: NSObject {
#objc dynamic var interval: TimeInterval = 0
}
let settings = AppSettings()
let observer: NSKeyValueObservation = settings.observe(\.interval, options: .new) { _, change in
print(change.newValue)
}
settings.interval = 10
See https://developer.apple.com/documentation/swift/cocoa_design_patterns/using_key-value_observing_in_swift
Just for completeness a simple generic observer here:
class Observable<ValueType> {
typealias Observer = (ValueType) -> Void
var observers: [Observer] = []
var value: ValueType {
didSet {
for observer in observers {
observer(value)
}
}
}
init(_ defaultValue: ValueType) {
value = defaultValue
}
func addObserver(_ observer: #escaping Observer) {
observers.append(observer)
}
}
class AppSettings {
let interval: Observable<TimeInterval> = Observable(0)
}
let settings = AppSettings()
settings.interval.addObserver { interval in
print(interval)
}
settings.interval.value = 10
Note that all my observers are simple closures. The reason why Java uses objects as observers is mostly historical due to Java limitations. There is no need for Observable or Observer protocols in Swift.
Depending on your needs, you may be able to get by with property observers in Swift. It allows you to take action when a property is going to be changed or has changed. It is also less complicated than a full observerable type.
Here is Apple's example from the Swift manual:
class StepCounter {
var totalSteps: Int = 0 {
willSet(newTotalSteps) {
print("About to set totalSteps to \(newTotalSteps)")
}
didSet {
if totalSteps > oldValue {
print("Added \(totalSteps - oldValue) steps")
}
}
}
}
let stepCounter = StepCounter()
stepCounter.totalSteps = 200
// About to set totalSteps to 200
// Added 200 steps
stepCounter.totalSteps = 360
// About to set totalSteps to 360
// Added 160 steps
stepCounter.totalSteps = 896
// About to set totalSteps to 896
// Added 536 steps
You would want to use the didSet() function. You could also call another function within the observer.
You could also use the property observers to write a simple observable-like class if you do not want to use a framework such as RxSwift or Apple's new Combine.
Here is a simple example that just uses closures instead of classes:
class ClassToWatch {
typealias ObservingFunc = (ClassToWatch) -> Void
private var observers: [ObservingFunc] = []
func addObserver(_ closure: #escaping ObservingFunc) {
observers.append(closure)
}
private func valueChanged() {
observers.forEach { observer in
observer(self)
}
}
var value1: Int = 0 {
didSet {
valueChanged()
}
}
var value2: String = "" {
didSet {
valueChanged()
}
}
}
var myclass = ClassToWatch()
myclass.addObserver { object in
print("Observer 1: \(object.value1) \(object.value2)")
}
myclass.addObserver { object in
print("Observer 2: \(object.value1) \(object.value2)")
}
myclass.value1 = 3
myclass.value2 = "Test"
Your Java code could be directly translated into Swift code. Here is my translation, with some degree of "Swiftification":
protocol Observable {
associatedtype ObserverType
func addObserver(_ observer: ObserverType)
}
protocol Settings : Observable where ObserverType == SettingsObserver {
var interval: Int { get set }
}
protocol SettingsObserver {
func intervalDidChange(newValue: Int)
}
class Observer1 : SettingsObserver {
func intervalDidChange(newValue: Int) {
print("Observer 1 changed to \(newValue)")
}
}
class Observer2 : SettingsObserver {
func intervalDidChange(newValue: Int) {
print("Observer 2 changed to \(newValue)")
}
}
class AppSettings: Settings {
var interval: Int = 0 {
didSet {
observers.forEach { $0.intervalDidChange(newValue: interval) }
}
}
private var observers: [SettingsObserver] = []
func addObserver(_ observer: SettingsObserver) {
observers.append(observer)
}
}
let settings = AppSettings()
settings.addObserver(Observer1())
settings.addObserver(Observer2())
settings.interval = 10
Although Observable cannot be used as a parameter type, the protocols that derive from it that also specifies the associated type, can.
You could go one step further and make SettingsObserver a typealias of (Int) -> Void. This way you don't need all those different ObserverX classes.
typelias SettingsObserver = (Int) -> Void
The addObserver calls would then become:
settings.addObserver { print("Observer 1 changed to \($0)") }
settings.addObserver { print("Observer 2 changed to \($0)") }
And the call in didSet would change to:
observers.forEach { $0(interval) }
Also, I don't understand why Settings exist. Can't you just conform AppSettings directly to Observable? I mean, I know the idea of program to interface and all that, but IMO this is a bit too much...

Test PublishSubject for ViewState

I'm trying to test the main functionality of my ViewModel. The important step is to test te loaded state completed. But for sure, for a better test it could be interesting to test al states.
I was reading a lot of post and information about RxTest and RxBlocking but I'm not able to test this module. If someone can help me, it would be great!
struct Product: Equatable { }
struct Promotion { }
protocol ProductsRepository {
func fetchProducts() -> Observable<Products>
func fetchPromotions() -> Observable<[Promotion]>
}
struct ProductCellViewModel: Equatable {
let product: Product
}
struct Products {
let products: [Product]
}
enum ProductsViewState: Equatable {
case loading
case empty
case error
case loaded ([ProductCellViewModel])
}
class ProductsViewModel {
var repository: ProductsRepository
let disposeBag = DisposeBag()
private var productCellViewModel: [ProductCellViewModel]
private var promotions: [Promotion]
// MARK: Input
init(repository: ProductsRepository) {
self.repository = repository
productCellViewModel = [ProductCellViewModel]()
promotions = [Promotion]()
}
func requestData(scheduler: SchedulerType) {
state.onNext(.loading)
resetCalculate()
repository.fetchProducts()
.observeOn(scheduler)
.flatMap({ (products) -> Observable<[ProductCellViewModel]> in
return self.buildCellViewModels(data: products)
}).subscribe(onNext: { (cellViewModels) in
self.productCellViewModel = cellViewModels
}, onError: { (error) in
self.state.onNext(.error)
}, onCompleted: {
self.repository.fetchPromotions()
.flatMap({ (promotions) -> Observable<[Promotion]> in
self.promotions = promotions
return Observable.just(promotions)
}).subscribe(onNext: { (_) in
self.state.onNext(.loaded(self.productCellViewModel))
}, onError: { (error) in
self.state.onNext(.error)
}).disposed(by: self.disposeBag)
}).disposed(by: disposeBag)
}
// MARK: Output
var state = PublishSubject<ProductsViewState>()
// MARK: ViewModel Map Methods
private func buildCellViewModels(data: Products) -> Observable <[ProductCellViewModel]> {
var viewModels = [ProductCellViewModel]()
for product in data.products {
viewModels.append(ProductCellViewModel.init(product: product))
}
return Observable.just(viewModels)
}
func resetCalculate() {
productCellViewModel = [ProductCellViewModel]()
}
}
The goal is to be able to test all of ProductsViewState after viewmodel.requestData() is being called
The key here is that you have to inject your scheduler into the function so you can inject a test scheduler. Then you will be able to test your state. BTW that state property should be a let not a var.
class ProductsViewModelTests: XCTestCase {
var scheduler: TestScheduler!
var result: TestableObserver<ProductsViewState>!
var disposeBag: DisposeBag!
override func setUp() {
super.setUp()
scheduler = TestScheduler(initialClock: 0)
result = scheduler.createObserver(ProductsViewState.self)
disposeBag = DisposeBag()
}
func testStateLoaded() {
let mockRepo = MockProductsRepository(products: { .empty() }, promotions: { .empty() })
let viewModel = ProductsViewModel(repository: mockRepo)
viewModel.state.bind(to: result).disposed(by: disposeBag)
viewModel.requestData(scheduler: scheduler)
scheduler.start()
XCTAssertEqual(result.events, [.next(0, ProductsViewState.loading), .next(1, .loaded([]))])
}
func testState_ProductsError() {
let mockRepo = MockProductsRepository(products: { .error(StubError()) }, promotions: { .empty() })
let viewModel = ProductsViewModel(repository: mockRepo)
viewModel.state.bind(to: result).disposed(by: disposeBag)
viewModel.requestData(scheduler: scheduler)
scheduler.start()
XCTAssertEqual(result.events, [.next(0, ProductsViewState.loading), .next(1, .error)])
}
func testState_PromotionsError() {
let mockRepo = MockProductsRepository(products: { .empty() }, promotions: { .error(StubError()) })
let viewModel = ProductsViewModel(repository: mockRepo)
viewModel.state.bind(to: result).disposed(by: disposeBag)
viewModel.requestData(scheduler: scheduler)
scheduler.start()
XCTAssertEqual(result.events, [.next(0, ProductsViewState.loading), .next(1, .error)])
}
}
struct StubError: Error { }
struct MockProductsRepository: ProductsRepository {
let products: () -> Observable<Products>
let promotions: () -> Observable<[Promotion]>
func fetchProducts() -> Observable<Products> {
return products()
}
func fetchPromotions() -> Observable<[Promotion]> {
return promotions()
}
}

Extending target-action methods with RxSwift

class A{
func addTarget(target: Any, action: Selector)
}
Let's say I don't have class A source available (framework). How would I extend this class reactively to emit Rx events through an Observable?
I can create a class that just forwards the events through a PublishSubject, but in that case I wouldn't be creating a Reactive extension but doing it through a proxy class.
let a = A()
let del = CustomClassThatAddsItselfAsATarget(a)
del.event.subscribe( ...
instead of
let a = A()
a.rx.event.subscribe( ...
This was a fun exploration. I patterned the below off of how UIControl is set up in RxCocoa.
In answer to your followup questions:
Which object will add itself as a target(addTarget method) to base?
You have to create a class that is designed to do that. I named it ATarget.
Who retains that object?
You make the object conform to Disposable and then it will be retained until disposed of.
extension Reactive where Base: A {
var event: Observable<A> {
return Observable.create { [weak a = self.base] observer in
guard let a = a else {
observer.on(.completed)
return Disposables.create()
}
let aTarget = ATarget(a: a, callback: { a in
observer.on(.next(a))
})
return Disposables.create(with: aTarget.dispose)
}
.takeUntil(deallocated)
}
}
class ATarget: NSObject, Disposable {
typealias Callback = (A) -> Void
let selector: Selector = #selector(ATarget.eventHandler)
weak var a: A?
var callback: Callback?
init(a: A, callback: #escaping Callback) {
self.a = a
self.callback = callback
super.init()
a.addTarget(target: self, action: selector)
}
#objc func eventHandler() {
if let callback = self.callback, let a = self.a {
callback(a)
}
}
func dispose() {
self.a?.removeTarget(target: self)
self.callback = nil
}
}
RxCocoa and most other RxSwift-based frameworks take the following approach -
public extension Reactive where Base: TheOriginalClass {
See CKRecord+Rx or Bundle+Rx for an example of implementation.
Things get more complicated if you need to provide a proxy delegate, but this is out of scope of this question.

Observer Pattern in Swift

I want to implement an observer pattern, but I do not find the proper programming language constructs in Swift (also 2.0). The main problems are:
protocol and extension does not allow stored properties.
In classes we could add stored properties, but we can not force a subclass to override some of its inherited methods.
This is what I want:
{class|protocol|extension|whathaveyou} Sensor {
var observers = Array<Any>() // This is not possible in protocol and extensions
// The following is does not work in classes
func switchOn()
func switchOff()
var isRunning : Bool {
get
}
}
class LightSensor : Sensor {
//...
override func switchOn() {
// turn the sensor on
}
}
// In the class C, implementing the protocol 'ObserverProtocol'
var lightSensor = LightSensor()
lightSensor.switchOn()
lightSensor.registerObserver(self) // This is what I want
And here comes what is possible to my knowledge:
class Sensor {
private var observers = Array<Observer>()
func registerObserver(observer:ObserverDelegate) {
observers.append(observer)
}
}
protocol SensorProtocol {
func switchOn()
func switchOff()
var isRunning : Bool {
get
}
}
class LightSensor : Sensor, SensorProtocol {
func switchOn() {
//
}
func switchOff() {
//
}
var isRunning : Bool {
get {
return // whatever
}
}
}
But this is not very convenient, because both Sensor and SensorProtocol should come hand in hand, and are both requirements the subclass LightSensor has to fulfill.
Any ideas?
A protocol is an abstract set of requirements shared across a number of (potentially very different) other objects. As such, it's illogical to store data in a protocol. That would be like global state. I can see that you want to define the specification for how the observers are stored though. That would also allow 'you' to remove 'someone else' from being an observer, and it's very restrictive about how the observers are stored.
So, your protocol should expose methods to add and remove 'yourself' as an observer. It's then the responsibility of the object implementing the protocol to decide how and where the observers are stored and to implement the addition and removal.
You could create a struct to work with your protocols, something like:
protocol Observer: class {
func notify(target: Any)
}
protocol Observable {
mutating func addObserver(observer: Observer)
mutating func removeObserver(observer: Observer)
}
struct Observation: Observable {
var observers = [Observer]()
mutating func addObserver(observer: Observer) {
print("adding")
observers.append(observer)
}
mutating func removeObserver(observer: Observer) {
print("removing")
for i in observers.indices {
if observers[i] === observer {
observers.removeAtIndex(i)
break
}
}
}
func notify(target: Any) {
print("notifying")
for observer in observers {
observer.notify(target)
}
}
}
struct ATarget: Observable {
var observation = Observation()
mutating func addObserver(observer: Observer) {
observation.addObserver(observer)
}
mutating func removeObserver(observer: Observer) {
observation.removeObserver(observer)
}
func notifyObservers() {
observation.notify(self)
}
}
class AnObserver: Observer {
func notify(target: Any) {
print("notified!")
}
}
let myObserver = AnObserver()
var myTarget: Observable = ATarget()
myTarget.addObserver(myObserver)
if let myTarget = myTarget as? ATarget {
myTarget.notifyObservers()
}
This is my solution in Swift 3
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
var objectToObserve = ObjectToObserve()
let observer = Observer()
let observer1 = Observer()
objectToObserve.add(observer: observer, notifyOnRegister: true)
objectToObserve.add(observer: observer1, notifyOnRegister: true)
}
}
//
// MARK: Protocol
//
protocol Observing: class {
func doSomething()
func doSomethingClosure(completion: () -> Void)
}
protocol Observable {
}
extension Observable {
private var observers: [Observing] {
get {
return [Observing]()
}
set {
//Do nothing
}
}
mutating func add(observer: Observing, notifyOnRegister: Bool) {
if !observers.contains(where: { $0 === observer }) {
observers.append(observer)
if notifyOnRegister {
observer.doSomething()
observer.doSomethingClosure(completion: {
print("Completion")
})
}
}
}
mutating func remove(observer: Observing) {
observers = observers.filter({ $0 !== observer })
}
}
//
// MARK: Observing
//
class ObjectToObserve: Observable {
init() {
print("Init ObjectToObserve")
}
}
class Observer: Observing {
init() {
print("Init Observer")
}
func doSomething() {
print("Do something")
}
func doSomethingClosure(completion: () -> Void) {
print("Do something Closure")
completion()
}
}
All answers above incorrectly use an array for retaining the observers, which may create retain cycles because of the strong references.
Also in general you may not want to allow the same observer to register itself twice.
The presented solutions also are not general purpose or lack type safety. I reference my blog post here which presents a full solution in a Swifty manner:
https://www.behindmedia.com/2017/12/23/implementing-the-observer-pattern-in-swift/
Well, you can certainly overcome the restriction of not having stored properties on extensions.
Maybe that way you can complement one of the proposed solutions with an extension that helps you avoid creating the observer list in each subclass / protocol implementation.
Although extensions can't have stored properties, you can actually get them by using the Objective-C Runtime. Assuming you have a base class for your sensors (BaseSensor) and a protocol for observers (SensorObserver):
import Foundation
import ObjectiveC
private var MyObserverListKey: UInt8 = 0
extension BaseSensor {
var observers:[SensorObserver] {
get {
if let observers = objc_getAssociatedObject( self, &MyObserverListKey ) as? [SensorObserver] {
return observers
}
else {
var observers = [SensorObserver]()
objc_setAssociatedObject( self, &MyObserverListKey, observers, objc_AssociationPolicy(OBJC_ASSOCIATION_RETAIN_NONATOMIC) )
return observers
}
}
set(value) {
objc_setAssociatedObject( self, &MyObserverListKey, observers, objc_AssociationPolicy(OBJC_ASSOCIATION_RETAIN_NONATOMIC) )
}
}
}
To be clear, even though you would need BaseSensor and all Sensors to inherit from it in order to have this property, BaseSensor wouldn't actually implement the Sensor protocol.
It's a bit weird, but I think it would suit your needs:
class BaseSensor {
}
protocol Sensor {
func switchOn()
}
class LightSensor: BaseSensor, Sensor {
func switchOn() {
// whatever
}
}
With Swift 2.0 this would be much simpler, since you can use Protocol Extensions, so you could simply do this:
protocol Sensor {
func switchOn()
}
extension Sensor {
// Here the code from the previous implementation of the extension of BaseSensor
}
class LightSensor : Sensor {
func switchOn() {
// whatever
}
}
Way better.

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