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()
}
}
}
}
}
Related
I'm playing about with writing a custom Combine publisher in order to better understand how I can turn various classes into them. Admittedly this is not something I want to do a lot, I just want to understand how it could be done if I need to.
The scenario I'm working with is where I have a class that generates values over time and potentially has multiple subscribers listening. It's not a case of the publisher generating values when requested, but pushing values when it desires. This might occur (for example) when reading text, or with random input from a UI.
To test this out I've started with a simple integer generator that's something like this:
class IntPublisher {
func generate() {
DispatchQueue.global(qos: .background).async { [weak self] in
self?.send(0)
self?.send(1)
self?.send(2)
self?.complete()
}
}
private func send(_ value: Int) {
queueOnMain()
}
func queueOnMain() {
Thread.sleep(forTimeInterval: 0.5)
DispatchQueue.main.async { /* ... */ }
}
}
And here's the generator as a Publisher and Subscription:
class IntPublisher: Publisher {
typealias Output = Int
typealias Failure = Never
class Subscription: Combine.Subscription, Equatable {
private var subscriber: AnySubscriber<Int, Never>?
private var didFinish: ((Subscription) -> Void)?
init<S>(subscriber: S, didFinish:#escaping (Subscription) -> Void) where S: Subscriber, S.Input == Output, S.Failure == Failure {
self.subscriber = AnySubscriber(subscriber)
self.didFinish = didFinish
}
func request(_ demand: Subscribers.Demand) {
}
func cancel() {
finish()
}
func complete() {
self.subscriber?.receive(completion: .finished)
finish()
}
func finish() {
didFinish?(self)
subscriber = nil
didFinish = nil
}
func send(_ value: Int) {
_ = subscriber?.receive(value)
}
static func == (lhs: PublisherTests.IntPublisher.Subscription, rhs: PublisherTests.IntPublisher.Subscription) -> Bool {
return lhs.subscriber?.combineIdentifier == rhs.subscriber?.combineIdentifier
}
}
var subscriptions = [Subscription]()
func receive<S>(subscriber: S) where S : Subscriber, S.Failure == Failure, S.Input == Output {
let subscription = Subscription(subscriber: subscriber) { [weak self] (subscription) in
self?.subscriptions.remove(subscription)
}
subscriptions.append(subscription)
subscriber.receive(subscription: subscription)
}
func generate() {
DispatchQueue.global(qos: .background).async { [weak self] in
self?.send(0)
self?.send(1)
self?.send(2)
self?.complete()
}
}
private func send(_ value: Int) {
queueOnMain { $0.send(value) }
}
private func complete() {
queueOnMain { $0.complete() }
}
func queueOnMain(_ block: #escaping (Subscription) -> Void) {
Thread.sleep(forTimeInterval: 0.5)
DispatchQueue.main.async { self.subscriptions.forEach { block($0) } }
}
}
My question revolves around the way I've had to track the subscriptions in the publisher. Because it's generating the values and needs to forward them to the subscriptions, I've had to setup an array and store the subscriptions within it. In turn I had to find a way for the subscriptions to remove themselves from the publisher's array when they're cancelled or completed because the array effective forms a circular reference between the publisher and subscription.
In all the blogs I've read on custom publishing, they all cover the scenario where a publisher is waiting around for subscribers to request values. The publisher doesn't need to store a reference to the subscriptions because it passes closures which they can call to get a value. My use case is different because the publisher controls the request, not the subscribers.
So my question is this - Is using an array a good way to handle this? or is there something in Combine I've missed?
As Apple suggest for Creating Your Own Publishers. You should use Use a concrete subclass of Subject, a CurrentValueSubject, or #Published
For example:
func operation() -> AnyPublisher<String, Error> {
let subject = PassthroughSubject<String, Error>()
subject.send("A")
subject.send("B")
subject.send("C")
return subject.eraseToAnyPublisher()
}
New Dev's idea has made a massive reduction in the code. I don't know why I didn't think of it ... oh wait, I do. I was so focused on implementing I clean forgot to consider the option of using a decorator pattern around a subject.
Anyway, here's the (much simpler) code:
class IntPublisher2: Publisher {
typealias Output = Int
typealias Failure = Never
private let passThroughSubject = PassthroughSubject<Output, Failure>()
func receive<S>(subscriber: S) where S : Subscriber, S.Failure == Failure, S.Input == Output {
passThroughSubject.receive(subscriber: subscriber)
}
func generate() {
DispatchQueue.global(qos: .background).async {
Thread.sleep(forTimeInterval: 0.5)
self.passThroughSubject.send(0)
Thread.sleep(forTimeInterval: 0.5)
self.passThroughSubject.send(1)
Thread.sleep(forTimeInterval: 0.5)
self.passThroughSubject.send(2)
Thread.sleep(forTimeInterval: 0.5)
self.passThroughSubject.send(completion: .finished)
}
}
}
I'm building a SwiftUI app that is using the MVVM pattern. The data source for the view models is provided by a custom Publisher for a Realm database. As I'm trying to be good and do a bit of test-driven development, I wrote a test to ensure that the view model responds appropriately to inputs from the SwiftUI front end (specifically in this instance, only querying the Realm once the UI was displayed). The code functions as expected but the test doesn't...
This is almost certainly because I'm not accounting for background processing / thread issues. My normal approach would to set up an expectation but this doesn't help as I need to use the property I'm interested in to create a Publisher but this completes immediately after emitting the initial state and I don't know how to keep it "alive" until the expectation expires. Can anyone point me in the right direction?
View model:
final class PatientListViewModel: ObservableObject, UnidirectionalDataFlowType {
typealias InputType = Input
enum Input {
case onAppear
}
private var cancellables = Set<AnyCancellable>()
private let onAppearSubject = PassthroughSubject<Void, Never>()
// MARK: Output
#Published private(set) var patients: [Patient] = []
// MARK: Private properties
private let realmSubject = PassthroughSubject<Array<Patient>, Never>()
private let realmService: RealmServiceType
// MARK: Initialiser
init(realmService: RealmServiceType) {
self.realmService = realmService
bindInputs()
bindOutputs()
}
// MARK: ViewModel protocol conformance (functional)
func apply(_ input: Input) {
switch input {
case .onAppear:
onAppearSubject.send()
}
}
// MARK: Private methods
private func bindInputs() {
let _ = onAppearSubject
.flatMap { [realmService] _ in realmService.all(Patient.self) }
.share()
.eraseToAnyPublisher()
.receive(on: RunLoop.main)
.subscribe(realmSubject)
.store(in: &cancellables)
}
private func bindOutputs() {
let _ = realmSubject
.assign(to: \.patients, on: self)
.store(in: &cancellables)
}
}
Test class: (very bulky due to my debugging code!)
import XCTest
import RealmSwift
import Combine
#testable import AthenaVS
class AthenaVSTests: XCTestCase {
private var cancellables = Set<AnyCancellable>()
private var service: RealmServiceType?
override func setUp() {
service = TestRealmService()
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
service = nil
cancellables.removeAll()
}
func testPatientListViewModel() {
let viewModel = PatientListViewModel(realmService: service!)
let expectation = self.expectation(description: #function)
var outcome = Array<Patient>()
let _ = viewModel.patients.publisher.collect()
.handleEvents(receiveSubscription: { (subscription) in
print("Receive subscription")
}, receiveOutput: { output in
print("Received output: \(output)")
outcome = output
}, receiveCompletion: { _ in
print("Receive completion")
expectation.fulfill()
}, receiveCancel: {
print("Receive cancel")
expectation.fulfill()
}, receiveRequest: { demand in
print("Receive request: \(demand)")})
.sink { _ in }
.store(in: &cancellables)
viewModel.apply(.onAppear)
waitForExpectations(timeout: 2, handler: nil)
XCTAssertEqual(outcome.count, 4, "ViewModel state should change once triggered")
}
}
EDITED:
My apologies for the lack of clarity. The rest of the code base is as follows:
SwiftUI View
struct ContentView: View {
#ObservedObject var viewModel: PatientListViewModel
var body: some View {
NavigationView {
List {
ForEach(viewModel.patients) { patient in
Text(patient.name)
}
.onDelete(perform: delete )
}
.navigationBarTitle("Patients")
.navigationBarItems(trailing:
Button(action: { self.viewModel.apply(.onAdd) })
{ Image(systemName: "plus.circle")
.font(.title)
}
)
}
.onAppear(perform: { self.viewModel.apply(.onAppear) })
}
func delete(at offset: IndexSet) {
viewModel.apply(.onDelete(offset))
}
}
Realm Service
protocol RealmServiceType {
func all<Element>(_ type: Element.Type, within realm: Realm) -> AnyPublisher<Array<Element>, Never> where Element: Object
#discardableResult
func addPatient(_ name: String, to realm: Realm) throws -> AnyPublisher<Patient, Never>
func deletePatient(_ patient: Patient, from realm: Realm)
}
extension RealmServiceType {
func all<Element>(_ type: Element.Type) -> AnyPublisher<Array<Element>, Never> where Element: Object {
all(type, within: try! Realm())
}
func deletePatient(_ patient: Patient) {
deletePatient(patient, from: try! Realm())
}
}
final class TestRealmService: RealmServiceType {
private let patients = [
Patient(name: "Tiddles"), Patient(name: "Fang"), Patient(name: "Phoebe"), Patient(name: "Snowy")
]
init() {
let realm = try! Realm()
guard realm.isEmpty else { return }
try! realm.write {
for p in patients {
realm.add(p)
}
}
}
func all<Element>(_ type: Element.Type, within realm: Realm) -> AnyPublisher<Array<Element>, Never> where Element: Object {
return Publishers.realm(collection: realm.objects(type).sorted(byKeyPath: "name")).eraseToAnyPublisher()
}
func addPatient(_ name: String, to realm: Realm) throws -> AnyPublisher<Patient, Never> {
let patient = Patient(name: name)
try! realm.write {
realm.add(patient)
}
return Just(patient).eraseToAnyPublisher()
}
func deletePatient(_ patient: Patient, from realm: Realm) {
try! realm.write {
realm.delete(patient)
}
}
}
Custom Publisher (using Realm as a backend)
/ MARK: Custom publisher - produces a stream of Object arrays in response to change notifcations on a given Realm collection
extension Publishers {
struct Realm<Collection: RealmCollection>: Publisher {
typealias Output = Array<Collection.Element>
typealias Failure = Never // TODO: Not true but deal with this later
let collection: Collection
init(collection: Collection) {
self.collection = collection
}
func receive<S>(subscriber: S) where S : Subscriber, Failure == S.Failure, Output == S.Input {
let subscription = RealmSubscription(subscriber: subscriber, collection: collection)
subscriber.receive(subscription: subscription)
}
}
}
// MARK: Convenience accessor function to the custom publisher
extension Publishers {
static func realm<Collection: RealmCollection>(collection: Collection) -> Publishers.Realm<Collection> {
return Publishers.Realm(collection: collection)
}
}
// MARK: Custom subscription
private final class RealmSubscription<S: Subscriber, Collection: RealmCollection>: Subscription where S.Input == Array<Collection.Element> {
private var subscriber: S?
private let collection: Collection
private var notificationToken: NotificationToken?
init(subscriber: S, collection: Collection) {
self.subscriber = subscriber
self.collection = collection
self.notificationToken = collection.observe { (changes: RealmCollectionChange) in
switch changes {
case .initial:
// Results are now populated and can be accessed without blocking the UI
let _ = subscriber.receive(Array(collection.elements))
// case .update(_, let deletions, let insertions, let modifications):
case .update(_, _, _, _):
let _ = subscriber.receive(Array(collection.elements))
case .error(let error):
fatalError("\(error)")
#warning("Impl error handling - do we want to fail or log and recover?")
}
}
}
func request(_ demand: Subscribers.Demand) {
// no impl as RealmSubscriber is effectively just a sink
}
func cancel() {
subscriber = nil
notificationToken = nil
}
}
The issue I'm experiencing is a failure of the test case. I am anticipating the the view model will map an input (.onAppear) from the SwiftUI front end into an array of 'Patients' and assign this array to its patients property. The code works as expected but XCTAssertEqual fails, reporting that the 'patients' property is an empty array after calling 'viewmodel.assign(.onAppear)'. If I put a property observer on 'patients' it does update as expected but the test is not "seeing" the this.
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
I'm looking for a smart way of implementing a rate limit in an HTTP client. Let's assume the rate limit on the API is 5 requests per second on any of the resources. Right now the implementation looks similar to this:
final class HTTPClient: HTTPClientProtocol {
func getUser() -> Observable<User> {
return Observable<User>.create { (observer) -> Disposable in
...
}
}
func getProfile() -> Observable<Profile> {
return Observable<Profile>.create { (observer) -> Disposable in
...
}
}
func getMessages() -> Observable<Messages> {
return Observable<Messages>.create { (observer) -> Disposable in
...
}
}
func getFriends() -> Observable<Friends> {
return Observable<Friends>.create { (observer) -> Disposable in
...
}
}
}
Now ideally I would like to use these methods as needed throughout the application without worrying about the rate limit at all.
Back to the example of 5 requests per second: The first five requests can be executed immediately. But all requests after that have to wait. So within a window of 1 second 5 requests can be executed at most. All other requests have to wait.
Is there any smart way of doing this in RxSwift?
You need a custom Scheduler.
final class DelayScheduler: ImmediateSchedulerType {
init(delay: TimeInterval, queue: DispatchQueue = .main) {
self.queue = queue
dispatchDelay = delay
}
func schedule<StateType>(_ state: StateType, action: #escaping (StateType) -> Disposable) -> Disposable {
let cancel = SingleAssignmentDisposable()
lastDispatch = max(lastDispatch + dispatchDelay, .now())
queue.asyncAfter(deadline: lastDispatch) {
guard cancel.isDisposed == false else { return }
cancel.setDisposable(action(state))
}
return cancel
}
var lastDispatch: DispatchTime = .now()
let queue: DispatchQueue
let dispatchDelay: TimeInterval
}
Then you implement your Service by having all its Observables subscribe on this scheduler:
final class HTTPClient: HTTPClientProtocol {
func getUser() -> Observable<User> {
return Observable<User>.create { (observer) -> Disposable in
...
}.subscribeOn(scheduler)
}
func getProfile() -> Observable<Profile> {
return Observable<Profile>.create { (observer) -> Disposable in
...
}.subscribeOn(scheduler)
}
func getMessages() -> Observable<Messages> {
return Observable<Messages>.create { (observer) -> Disposable in
...
}.subscribeOn(scheduler)
}
func getFriends() -> Observable<Friends> {
return Observable<Friends>.create { (observer) -> Disposable in
...
}.subscribeOn(scheduler)
}
let scheduler = DelayScheduler(delay: 0.5)
}
Daniel T's use of a custom scheduler is brilliant, and I'm finding that it works well in practice. Here's a version of his code that implements a true sliding-window rate limit:
final class RateLimitedScheduler: ImmediateSchedulerType {
let period: TimeInterval
let queue: DispatchQueue
var dispatchHistory: [DispatchTime]
var dhIndex = 0
init(maxEvents: Int, period: TimeInterval, queue: DispatchQueue = .main) {
self.period = period
self.queue = queue
let periodAgo = DispatchTime.now() - period
dispatchHistory = Array(repeating: periodAgo, count: maxEvents)
}
func schedule<StateType>(_ state: StateType, action: #escaping (StateType) -> Disposable) -> Disposable {
let cancel = SingleAssignmentDisposable()
queue.asyncAfter(deadline: nextDeadline()) {
guard cancel.isDisposed == false else { return }
cancel.setDisposable(action(state))
}
return cancel
}
private func nextDeadline() -> DispatchTime {
let windowStartTime = dispatchHistory[dhIndex]
let deadline = max(windowStartTime + period, DispatchTime.now())
dispatchHistory[dhIndex] = deadline
dhIndex = (dhIndex >= dispatchHistory.count - 1) ? 0 : (dhIndex + 1)
return deadline
}
}
Note that perfect accuracy requires tracking the dispatch times of the previous N entries, so it's memory-expensive for rates of hundreds or thousands of operations per period. Consider using a "token bucket" for those cases - it's less accurate but requires only constant state (see this thread).
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