How to prevent Timing Problems When Using Closures? - ios

I'm trying to find the sum of numbers, returned from different escaping closures. The sum to be returned in main thread.
import Foundation
var randomTime: Int {
return Int.random(in: 0...1000)
}
func first(completion: #escaping (Int) -> Void) {
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(randomTime)) {
completion(1)
}
}
func second(completion: #escaping (Int) -> Void) {
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(randomTime)) {
completion(2)
}
}
func third(completion: #escaping (Int) -> Void) {
DispatchQueue(label: "anotherThread").async {
completion(3)
}
}
func fourth(completion: #escaping (Int) -> Void) {
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(randomTime)) {
completion(4)
}
}

If I got your question clear, You want to sum numbers but their values come at different times depending on a server response or some kind of delays. If that is the case then you have to use DispatchGroup
Here is a helper function, It is calling your methods first(completion: #escaping (Int) -> Void)..... fourth(completion: #escaping (Int) -> Void) and notify main queue only when the last value is received. I put some comments on the code to help understand. Let me know if something is not clear.
func computeOutPutAfterReceivingAllValues(completion: #escaping(_ sum: Int) -> Void) {
// Make a dispatch group which will notify main queue after making sure that all requests have been proceed.
let computeGroup = DispatchGroup()
var allNumbers: [Int] = []
computeGroup.enter()
first { (firstNumber) in
allNumbers.append(firstNumber)
self.second(completion: { (secondNumber) in
allNumbers.append(secondNumber)
self.third(completion: { (thirdNumber) in
allNumbers.append(thirdNumber)
self.fourth(completion: { (fourthNumber) in
allNumbers.append(fourthNumber)
// IMPORTANT: Leave a group after the last call.
computeGroup.leave()
})
})
})
}
// Notify Main queue and sum all your numbers
computeGroup.notify(queue: .main) {
/// Sum all your numbers in main queue
let sum = allNumbers.reduce(0, +)
completion(sum)
}
}
Usage:
You can test this in view didLoad.
override func viewDidLoad() {
super.viewDidLoad()
computeOutPutAfterReceivingAllValues { (sum) in
print("Here is the sum of all numbers: \(sum)")
}
}
// Output on console
Here is the sum of all numbers: 10

Related

CKOperationGroup specify an order and chain operations

I am asking this question due to the lack of documentation on Apple's website. Is there any way to chain to CKOperation inside a CKOperationGroup to execute in order one after the other? In particular, I am interested in two CKModifyRecordsOperation that I need to execute one after the other.
Many Thanks
Instead of using CKOperation.completionBlock, how about using an OperationQueue? Here is what I came up with. It's completely untested, so let me know if it's useless.
class CKOperationQueue: NSObject {
private var completionHandler: (() -> Void)?
private var isExecuting = false
private let operations: [CKOperation]
private let queue: OperationQueue
init(operations: [CKOperation]) {
self.operations = operations
queue = OperationQueue()
queue.name = "CKOperationQueue"
queue.maxConcurrentOperationCount = 1
}
#discardableResult func execute() -> CKOperationQueue {
guard !isExecuting else { return self }
isExecuting = true
queue.addOperations(operations, waitUntilFinished: false)
queue.addOperation {
self.completionHandler?()
}
return self
}
#discardableResult func onCompletion(_ handler: #escaping () -> Void) -> CKOperationQueue {
self.completionHandler = handler
return self
}
}

Swift Optimise nested async calls

I have this case where I need to make 3 nested async calls to receive the data I want.
So the second call needs data from the first one and the third one needs data from the second one. I do not have a lot of cases like this. Only this one and another one with only two nested call so I was thinking about a pure swift solution without any external libraries but I'm open to everything.
Since I'm using Firebase, is it better to move this logic to CloudFunctions? So to prepare it in the backend?
FirestoreService().fetchCollection(query: query) { (result: Result<[Request], Error>) in
// do stuff
FirestoreService().fetchCollection(query: query) { (result: Result<[Request], Error>) in
// do stuff
FirestoreService().fetchDocument(documentReference: documentReference) { (result: Result<Package, Error>) in
// finish
}
}
}
}
If you don't to used 3rd party library, then probably you want to consider wrap those operations inside some class, and utilise closure in imperative way.
here is the sample:
class CustomFirestoreHandler {
private var onFetchFirstQueryArrived: ((Result<[Request], Error>) -> ())? = nil
private var onFetchSecondQueryArrived: ((Result<[Request], Error>) -> ())? = nil
private var onFetchDocumentArrived: ((Result<Package, Error>) -> ())? = nil
init() {
onFetchFirstQueryArrived = { [weak self] (result: Result<[Request], Error>) in
self?.executeSecondQuery()
}
onFetchSecondQueryArrived = { [weak self] (result: Result<[Request], Error>) in
self?.executeFetchDocument()
}
}
func executeQuery(completion: #escaping (Result<Package, Error>) -> ()) {
self.onFetchDocumentArrived = completion
FirestoreService().fetchCollection(query: query) { [weak self] (result: Result<[Request], Error>) in
// validate if some error occurred and do early return here, so that we don't need necessarily call second query.
if (result.error == whatever) {
self?.onFetchDocumentArrived?(result)
return
}
self?.onFetchFirstQueryArrived?(result)
}
}
private func executeSecondQuery() {
FirestoreService().fetchCollection(query: query) { [weak self] (result: Result<[Request], Error>) in
// validate if some error occurred and do early return here, so that we don't need necessarily call fetch document.
if (result.error == whatever) {
self?.onFetchDocumentArrived?(result)
return
}
self?.onFetchSecondQueryArrived?(result)
}
}
private func executeFetchDocument() {
FirestoreService().fetchDocument(documentReference: documentReference) { (result: Result<Package, Error>) in
self?.onFetchDocumentArrived?(result)
}
}
}
And here's the usage of CustomFirestoreHandler above :
let firestoreHandler = CustomFirestoreHandler()
firestoreHandler.executeQuery { (result: Result<Package, Error>) in
// Handle `result` here...
}
I know it look complicated, but this is the only way I think (CMIIW) at the moment to prevent pyramid of dooms since swift doesn't have async await style(just like javascript does).

OperationQueue / DispatchGroup and recursion

I have a problem understanding how to use GCD when using asynchronous, recursive calls to the API.
Below is one of the three similar methods that contains the same logic, just for different data and API endpoint. If there is no next page request the method should finish and next method should start.
How would I make sure that fetchItems2 gets called after fetchItems1 finishes, and fetchItems3 after fetchItems2?
private func fetchItems1(completion: #escaping (Error?) -> Void) {
var _items = [Item]()
func handleReceivedItemsPage(_ page: PagingObject<Item>, _completion: ((Error?) -> Void)?) {
let newItems = page.items!
_tracks.append(contentsOf: newTracks)
if page.canMakeNextRequest {
page.getNext(success: { nextPage in
handleReceivedItemsPage(nextPage)
}) { nextError in
_completion?(nextError)
}
} else {
// Finished, next method can now start
self.items = _items
_completion?(nil)
}
}
API.getSavedItems(success: { page in
handleReceivedItemsPage(page, _completion: completion)
}, failure: completion)
}
private func fetchItems2(completion: #escaping (Error?) -> Void)) { ... }
private func fetchItems3(completion: #escaping (Error?) -> Void)) { ... }
You can keep an extra variable that keeps track of when the API calls are complete. In the completion block, increment this variable. Then, when the variable reaches the amount of API calls complete, perform your task.
I would use DispatchGroup:
public void FetchItems(completion: #escaping (Error?) -> Void) {
let group = DispatchGroup()
group.enter()
fetchItems1() { error in
completion(error)
group.leave()
}
group.wait()
group.enter()
fetchItems2() { error in
completion(error)
group.leave()
}
// 3rd function call
}
Code after group.wait() is not called until the number of group.enter() and group.leave() invocations is equal.

Perform polling request for async task

I have already written an Rx query to perform an async task in a timer. This also handles scenario where i need to discard order request whose response comes later. This is written in C#:
public static IObservable<T> PollingAync<T> (Func<Task<T>> AsyncCall, double TimerDuration)
{
return Observable
.Create<T>(o =>
{
var z = 0L;
return
Observable
.Timer(TimeSpan.Zero, TimeSpan.FromSeconds(TimerDuration))
.SelectMany(nr =>
Observable.FromAsync<T>(AsyncCall),
(nr, obj) => new { nr, obj})
.Do(res => z = Math.Max(z, res.nr))
.Where(res => res.nr >= z)
.Select(res => res.obj)
.Subscribe(o);
});
}
I wish to write the same implementation in swift which handles an async task and also discard order request whose response comes later. I wish to write this in swift3.0
Since i am new to swift please help to let me know how can i achieve the same result in swift without using Rx.
This was an interesting question to answer...
enum Result<T> {
case success(T)
case failure(Error)
}
typealias Cancel = () -> Void
func pollingAsync<T>(asyncCall: #escaping (#escaping (Result<T>) -> Void) -> Cancel, duration: TimeInterval, callback: #escaping (Result<T>) -> Void) -> Cancel {
let timer = DispatchSource.makeTimerSource(queue: DispatchQueue.main)
timer.scheduleRepeating(deadline: .now(), interval: .milliseconds(Int(duration * 1000)), leeway: .milliseconds(10))
var asyncCallCancel: Cancel? = nil
timer.setEventHandler {
asyncCallCancel?()
asyncCallCancel = asyncCall {
callback($0)
}
}
timer.resume()
return {
asyncCallCancel?()
timer.cancel()
}
}
To use the above, you would do something like this:
let cancel = pollingAsync(asyncCall: myAsyncOp, duration: 2.0) {
print($0)
}
If you lose track of the Cancel object that is returned from this function, you won't be able to shut down the timer.
For reference, here is the equivalent code in RxSwift:
func pollingAsync<T>(asyncCall: #escaping () -> Observable<T>, duration: TimeInterval) -> Observable<Event<T>> {
return Observable<Int>.interval(duration, scheduler: MainScheduler.instance)
.flatMapLatest { _ in
asyncCall().materialize().filter { !$0.isCompleted }
}
}

How to hide UICollectionView Cell in ios after specific time. eg. Half Hour, 7 week etc

I going to hide a cell after half an hour time interval from populating time. If app is in not running state then also performs the same operation. Please Help me. Thank's in advance.
Here is the function delay that can help you to process delays even in background state (delay will be processed immediately after app becomes active if time is ok). And with this code you can easy cancel this delay if needed But this solution will not work for the situation when the app is not running (for this case I will give another solution):
import Foundation
import UIKit
typealias dispatch_cancelable_closure = (_ cancel : Bool) -> Void
#discardableResult
func delay(_ time:TimeInterval, closure: #escaping ()->Void) -> dispatch_cancelable_closure? {
// DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(Int(time * 1000))) {
// closure()
// }
//
// return nil
func dispatch_later(_ clsr:#escaping ()->Void) {
DispatchQueue.main.asyncAfter(
deadline: DispatchTime.now() + Double(Int64(time * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC), execute: clsr)
}
var closure:(()->Void)? = closure
var cancelableClosure:dispatch_cancelable_closure?
let delayedClosure:dispatch_cancelable_closure = { cancel in
if closure != nil {
if (cancel == false) {
// DispatchQueue.main.async {
// closure?()
// }
DispatchQueue.main.async(execute: closure!)
// DispatchQueue.main.async(execute: closure as! #convention(block) () -> Void);
}
}
closure = nil
cancelableClosure = nil
}
cancelableClosure = delayedClosure
dispatch_later {
if let delayedClosure = cancelableClosure {
delayedClosure(false)
}
}
return cancelableClosure;
}
func cancel_delay(_ closureToCancel:dispatch_cancelable_closure?) {
if closureToCancel != nil {
closureToCancel!(true)
}
}
But for case when your application is not running, you need to save the time when you want to remove the cell in NSDefaults before application comes into background, and when application becomes active you can use this delay function to set the rest of the time (or if time expired you can remove the cell immidiatly)

Resources