I want to download data from my server to be displayed on a map. Therefore I use async methods to get the data. The goal is to have an array of annotation objects to be displayed.
Therefore I first download Information A and then Information B. As both are async methods, I guess I need to wait for the completionHandler to return true so I know the data is loaded. This is easy for one method. But how do I handle to wait for both methods before the completionHandler of getInformationFromServer returns true and triggers therefore the addition of annotations?
override func viewWillAppear(animated: Bool) {
self.customizeInterface()
self.getInformationFromServer { (completed) -> Void in
if(completed) {
self.mapView.addAnnotations(self.annotationArray)
}
}
}
func getInformationFromServer(completionHandler: (completed: Bool) -> Void) {
getInformationFromServerA { (downloadCompleted) -> Void in
completionHandler(completed: downloadCompleted)
}
// HOW DO I MANAGE TO ONLY RETURN THE COMPLETION HANDLER TRUE WHEN
// BOTH FUNCTIONS RETURNED TRUE?
}
func getInformationFromServerA(completionHandler: (downloadCompleted: Bool) -> Void) {
Server().getJsonInformationFromServer(url: "aeds", completionHandler: { (response) -> Void in
self.parseAEDInformationToAnnotation(response["data"])
completionHandler(downloadCompleted: true)
})
}
func getInformationFromServerB(completionHandler: (downloadCompleted: Bool) -> Void) {
Server().getJsonInformationFromServer(url: "aeds", completionHandler: { (response) -> Void in
self.parseAEDInformationToAnnotation(response["data"])
completionHandler(downloadCompleted: true)
})
}
You may use a dispatch group to wait until both downloads finish.
func getInformationFromServer(completionHandler: (completed: Bool) -> Void) {
let dispatchGroup = dispatch_group_create()
var downloadCompletedA: Bool = false
dispatch_group_enter(dispatchGroup)
getInformationFromServerA { (downloadCompleted) -> Void in
downloadCompletedA = downloadCompleted
dispatch_group_leave(dispatchGroup)
}
var downloadCompletedB: Bool = false
dispatch_group_enter(dispatchGroup)
getInformationFromServerB { (downloadCompleted) -> Void in
downloadCompletedB = downloadCompleted
dispatch_group_leave(dispatchGroup)
}
// wait until both downloads are finished
dispatch_group_wait(dispatchGroup, DISPATCH_TIME_FOREVER)
completionHandler(downloadCompletedA && downloadCompletedB)
}
See Apple's Concurrency Programming Guide:
Dispatch groups are a way to block a thread until one or more tasks
finish executing. You can use this behavior in places where you cannot
make progress until all of the specified tasks are complete.
Another solution which I can recommend for you and it's not so "complex" as solution with dispatch_group_wait:
func getInformationFromServer(completionHandler: (completed: Bool) -> Void) {
getInformationFromServerA { [weak self] (downloadCompleted: Bool) -> Void in
if downloadCompleted {
self?.getInformationFromServerB({ (downloadCompleted: Bool) -> Void in
completionHandler(completed: downloadCompleted)
})
}
else {
completionHandler(completed: downloadCompleted)
}
}
}
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)
}
}
}
If you have a sync function, how would you convert it to an async function?
func syncFunc() -> Int {
//Do something
}
Would this work?
func asyncFunc(_ syncFunc:()->Int, _ completion:(Int)->()) -> Int {
DispatchQueue.background.async{
completion( syncFunc() )
}
}
No, functions containing an asynchronous task cannot return any value from the closure body and both closures must be marked as #escaping
func asyncFunc(_ syncFunc: #escaping ()->Int, completion: #escaping (Int)->()) {
DispatchQueue.global().async {
completion( syncFunc() )
}
}
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 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)")
}
}
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).