Function that executes after multiple completion block has finished - ios

I have a funciton that I would like to only execute IF the two completion block has completed (And no way to tell which one to finish first). Below is my attempt that works. However, it is very messy and if I there are three or more completion block that I want to wait for, I would have flags everywhere. I was wondering if there is a prettier way of doing it.
class TestClass: UIViewController {
var blockOneComplete = false
var blockTwoComplete = false
func blockOneDownloadImageDescription(completion:()->Void) {
downloadAsyncWithCompletion {
blockOneComplete = true
if self.blockTwoComplete == true {
self.allDataDownloadCompleted()
} else {
// Do nothing and wait for block Two to complete
}
}
}
func blockTwoDownloadImageData(completion:()->Void) {
downloadAsyncWithCompletion {
blockTwoComplete = true
if self.blockOneComplete == true {
self.allDataDownloadCompleted()
} else {
// Do nothing and wait for block One to complete
}
}
}
func allDataDownloadComplete() {
// Execute this funciton after all Async Download has complete
}
}
-- Update with final result --
Turns out that what was outlined in this website was exactly what I needed
Using dispatch groups to wait for multiple web services
I believe this was not a duplicate of the SO question mentioned in the comment because the final solution included dispatch_group_enter and dispatch_group_leave

The best option is by using dispatch_group
class TestClass: UIViewController {
var group : dispatch_group_t = dispatch_group_create()
override func viewDidLoad() {
super.viewDidLoad()
dispatch_group_notify(group, dispatch_get_main_queue()) {
allDataDownloadComplete()
}
}
func blockOneDownloadImageDescription(completion:()->Void) {
dispatch_group_enter(group)
downloadAsyncWithCompletion {
dispatch_group_leave(group)
}
}
func blockTwoDownloadImageData(completion:()->Void) {
dispatch_group_enter(group)
downloadAsyncWithCompletion {
dispatch_group_leave(group)
}
}
func allDataDownloadComplete() {
// Execute this funciton after all Async Download has complete
}
}

You will need either use dispatch_group or use functional reactive programming library like RxSwift to achieve it if you does not want to manage flags.
However, you can just use one counter flag and just make a function call or use NSNotification if is for another ViewController.
In one of my project, I need to ensure that at least 3 of the 4 completion block is completed before calling some function. I do it something like this:
class TestClass: UIViewController {
var numberOfBlockCompleted = 0
func blockOneDownloadImageDescription(completion:()->Void) {
downloadAsyncWithCompletion {
numberOfBlockCompleted += 1
self.allDataDownloadCompleted()
}
}
func blockTwoDownloadImageData(completion:()->Void) {
downloadAsyncWithCompletion {
numberOfBlockCompleted += 1
self.allDataDownloadCompleted()
}
}
func blockThreeDownloadImageDesc(completion:()->Void) {
downloadAsyncWithCompletion {
numberOfBlockCompleted += 1
self.allDataDownloadCompleted()
}
}
func allDataDownloadComplete() {
if numberOfBlockCompleted == 3 {
//do something
}
}
}
In my opinion, it depend largely on how complex is the app. If is just for one or two part, a flag is good enough. However, if the app depending largely on chaining network calls and fetching from different server that need to wait for one or another to be completed like a live stocks app then a strong knowledge of GCD or using functional reactive programming will make your job easier in the long run.

Related

Interoperating Async/await, #MainActor and DispatchQueue.main.async

Say I have this code:
class Presenter {
var viewToUpdate: UIView!
func updateUI() {
viewToUpdate.backgroundColor = .red
}
}
class ShinyNewAsyncAwaitClass {
func doAsyncAwaitThing() async {
// make network call or something
}
}
class OtherClassThatICantUpdateToAsyncAwaitYet {
func doOldClosureBasedThing(completion: #escaping () -> Void) {
// make network call or something
completion()
}
}
class TheClassThatUsesAllThisStuff {
var newClass: ShinyNewAsyncAwaitClass!
var oldClass: OtherClassThatICantUpdateToAsyncAwaitYet!
var presenter: Presenter!
func doSomethingWithNewClass() {
Task {
await self.newClass.doAsyncAwaitThing()
// ---->>> What do I do here? <<<<----
await self.presenter.updateUI()
}
}
func doSomethingWithOldClass() {
oldClass.doOldClosureBasedThing {
DispatchQueue.main.async {
self.presenter.updateUI()
}
}
}
func justUpdateTheView() {
self.presenter.updateUI()
}
}
In short, I have three classes. One I can update to async/await, the other I can't, and one that uses both. Both need access to a function that updates UI, both will need to access that function on the main thread.
I saw somewhere I can add #MainActor to the updateUI function, but in cases where I'm already on the main thread and I just want to call updateUI, like in justUpdateTheView I get this error:
Call to main actor-isolated instance method 'updateUI()' in a synchronous nonisolated context
Add '#MainActor' to make instance method 'justUpdateTheView()' part of global actor 'MainActor'
I can't define justUpdateTheView as #MainActor, because we're trying to update our project to the new concurrency stuff slowly and this would cause a chain reaction of changes that need to be made.
What to do for the best? Can I do something like this:
func doSomethingWithNewClass() {
Task {
await self.newClass.doAsyncAwaitThing()
DispatchQueue.main.async {
self.presenter.updateUI()
}
}
}
It compiles, but are there any gotchas to be aware of?
You can do something like this to run the UI code on the MainActor:
func doSomethingWithNewClass() {
Task {
await self.newClass.doAsyncAwaitThing()
await MainActor.run {
self.presenter.updateUI()
}
}
}

How can I delay a few seconds in Swift

I tried using sleep() in Darwin however that seems to delay the execution of my previous function.
SomeStruct.display(argument)
sleep(1)
If I write like this, the display function will be delayed for 1 second before being executed. I wonder if there were functions that operate as delay() in C++ that can just create a pause for a certain period of time without affecting previous execution.
edit:
This is my code:
cardModel.turn(card: card)
if card.pairID == cards[faceOnID!].pairID {
print("Paired")
} else {
print("Not Paired")
}
print("executed")
do { sleep(1) }
cardModel.turn(card: card)
cardModel.turn(card: cards[faceOnID!])
faceOnID = nil
While executed is printed, the turn() didn't take effect in contentview...
this is the code of the turn() function:
mutating func turn(card: Card) {
cards[cards.locateFirstElement(matching: card)!].isTurnedOver.toggle()
}
and this is the code of the locateFirstElement() function:
extension Array where Element: Identifiable {
func locateFirstElement(matching item: Element) -> Int? {
for i in 0..<self.count {
if self[i].id == item.id {
return i
}
}
return nil
}
}
First of all there is no need to reinvent the wheel, there is a function to get the index for a given predicate
mutating func turn(card: Card) {
guard let cardIndex = cards.firstIndex(where: {$0.id == card.id}) else { return }
cards[cardIndex].isTurnedOver.toggle()
}
Second of all, never sleep, don't, use asynchronous API to avoid blocking the current thread
cardModel.turn(card: card)
if card.pairID == cards[faceOnID!].pairID {
print("Paired")
} else {
print("Not Paired")
}
print("executed")
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
self.cardModel.turn(card: card)
self.cardModel.turn(card: cards[faceOnID!])
self.faceOnID = nil
}
Instead of sleep, which delays the execution of your previous function, look into using Timer (formerly NSTimer). I really like how it is explained here:
https://www.hackingwithswift.com/articles/117/the-ultimate-guide-to-timer
However, if you really need the delay in the current thread, you can do the following:
do {sleep(1)}

How to cancel specific Operation from OperationQueue in swift

There are 3 Operations in my OperationQueue and i'm not able to cancel specific operation from them.
I referred this example but i can't understand it
NSOperationQueue cancel specific operations
This is my code
class myOperation1 : Operation {
override func main() {
print("op1 (๐Ÿญ) working....")
for i in 1...10 {
print("๐Ÿญ")
}
}
}
class myOperation2 : Operation {
override func main() {
print("op2 (๐Ÿถ) working....")
for i in 1...10 {
print("๐Ÿถ")
}
}
}
class myOperation3 : Operation {
override func main() {
print("op3 (๐Ÿ‰) working....")
for i in 1...10 {
print("๐Ÿ‰")
}
}
}
let op1 = myOperation1()
let op2 = myOperation2()
let op3 = myOperation3()
op1.completionBlock = {
print("op1 (๐Ÿญ) completed")
}
op2.completionBlock = {
print("op2 (๐Ÿถ) completed")
}
op3.completionBlock = {
print("op3 (๐Ÿ‰) completed")
}
let opsQue = OperationQueue()
opsQue.addOperations([op1, op2, op3], waitUntilFinished: false)
DispatchQueue.global().asyncAfter(deadline: .now()) {
opsQue.cancelAllOperations()
}
Inshort i want to cancel second operation from operationQueue.
Please guide me.
Thank you
you can call op2.cancel() to cancel the operation, but you need to take additional steps to really stop your operation from running as cancel() only set the isCanceled property to true.
Please check the developer document.
https://developer.apple.com/documentation/foundation/operation/1408418-iscancelled
The default value of this property is false. Calling the cancel() method of this object sets the value of this property to true. Once canceled, an operation must move to the finished state.
Canceling an operation does not actively stop the receiverโ€™s code from executing. An operation object is responsible for calling this method periodically and stopping itself if the method returns true.
You should always check the value of this property before doing any work towards accomplishing the operationโ€™s task, which typically means checking it at the beginning of your custom main() method. It is possible for an operation to be cancelled before it begins executing or at any time while it is executing. Therefore, checking the value at the beginning of your main() method (and periodically throughout that method) lets you exit as quickly as possible when an operation is cancelled.
opsQue.cancelAllOperations() in your code cause removing non-started operations from queue and calls Operation.cancel() for each executing operation, but it only set isCancelled to true. You need to handle it explicitly
class myOperation2 : Operation {
override func main() {
print("op2 (๐Ÿถ) working....")
for i in 1...10 {
if self.isCancelled { break } // cancelled, so interrupt
print("๐Ÿถ")
}
}
}
Hope you referred to the documentation for Operation
There are several KVO-Compliant Properties for observe operation.
There is one property isCancelled - read-only
used to check this property before the execution of the operation
like this:
class myOperation2 : Operation {
override func main() {
print("op2 (๐Ÿถ) working....")
if self.isCancelled {
return
}
for i in 1...10 {
print("๐Ÿถ")
}
}
}
and for cancelation:
DispatchQueue.global().asyncAfter(deadline: .now()) {
opsQue.operations[1].cancel()
}

How can I unit test that a block of code is run on DispatchQueue.main

Caveat - I read the few questions about testing threads but may have missed the answer so if the answer is there and I missed it, please point me in the right direction.
I want to test that a tableView call to reloadData is executed on the main queue.
This should code should result in a passing test:
var cats = [Cat]() {
didSet {
DispatchQueue.main.async { [weak self] in
tableView.reloadData()
}
}
}
This code should result in a failing test:
var cats = [Cat]() {
didSet {
tableView.reloadData()
}
}
What should the test look like?
Note to the testing haters: I know this is an easy thing to catch when you run the app but it's also an easy thing to miss when you're refactoring and adding layers of abstraction and multiple network calls and want to update the UI with some data but not other data etc etc... so please don't just answer with "Updates to UI go on the main thread" I know that already. Thanks!
Use dispatch_queue_set_specific function in order to associate a key-value pair with the main queue
Then use dispatch_queue_get_specific to check for the presence of key & value:
fileprivate let mainQueueKey = UnsafeMutablePointer<Void>.alloc(1)
fileprivate let mainQueueValue = UnsafeMutablePointer<Void>.alloc(1)
/* Associate a key-value pair with the Main Queue */
dispatch_queue_set_specific(
dispatch_get_main_queue(),
mainQueueKey,
mainQueueValue,
nil
)
func isMainQueue() -> Bool {
/* Checking for presence of key-value on current queue */
return (dispatch_get_specific(mainQueueKey) == mainQueueValue)
}
I wound up taking the more convoluted approach of adding an associated Bool value to UITableView, then swizzling UITableView to redirect reloadData()
fileprivate let reloadDataCalledOnMainThreadString = NSUUID().uuidString.cString(using: .utf8)!
fileprivate let reloadDataCalledOnMainThreadKey = UnsafeRawPointer(reloadDataCalledOnMainThreadString)
extension UITableView {
var reloadDataCalledOnMainThread: Bool? {
get {
let storedValue = objc_getAssociatedObject(self, reloadDataCalledOnMainThreadKey)
return storedValue as? Bool
}
set {
objc_setAssociatedObject(self, reloadDataCalledOnMainThreadKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
dynamic func _spyReloadData() {
reloadDataCalledOnMainThread = Thread.isMainThread
_spyReloadData()
}
//Then swizzle that with reloadData()
}
Then in the test I updated the cats on the background thread so I could check if they were reloaded on the main thread.
func testReloadDataIsCalledWhenCatsAreUpdated() {
// Checks for presence of another associated property that's set in the swizzled reloadData method
let reloadedPredicate = NSPredicate { [controller] _,_ in
controller.tableView.reloadDataWasCalled
}
expectation(for: reloadedPredicate, evaluatedWith: [:], handler: nil)
// Appends on the background queue to simulate an asynchronous call
DispatchQueue.global(qos: .background).async { [weak controller] in
let cat = Cat(name: "Test", identifier: 1)
controller?.cats.append(cat)
}
// 2 seconds seems excessive but NSPredicates only evaluate once per second
waitForExpectations(timeout: 2, handler: nil)
XCTAssert(controller.tableView.reloadDataCalledOnMainThread!,
"Reload data should be called on the main thread when cats are updated on a background thread")
}
Here is an updated version of the answer provided by Oleh Zayats that I am using in some tests of Combine publishers.
extension DispatchQueue {
func setAsExpectedQueue(isExpected: Bool = true) {
guard isExpected else {
setSpecific(key: .isExpectedQueueKey, value: nil)
return
}
setSpecific(key: .isExpectedQueueKey, value: true)
}
static func isExpectedQueue() -> Bool {
guard let isExpectedQueue = DispatchQueue.getSpecific(key: .isExpectedQueueKey) else {
return false
}
return isExpectedQueue
}
}
extension DispatchSpecificKey where T == Bool {
static let isExpectedQueueKey = DispatchSpecificKey<Bool>()
}
This is an example test using Dispatch and Combine to verify it is working as expected (you can see it fail if you remove the receive(on:) operator).:
final class IsExpectedQueueTests: XCTestCase {
func testIsExpectedQueue() {
DispatchQueue.main.setAsExpectedQueue()
let valueExpectation = expectation(description: "The value was received on the expected queue")
let completionExpectation = expectation(description: "The publisher completed on the expected queue")
defer {
waitForExpectations(timeout: 1)
DispatchQueue.main.setAsExpectedQueue(isExpected: false)
}
DispatchQueue.global().sync {
Just(())
.receive(on: DispatchQueue.main)
.sink { _ in
guard DispatchQueue.isExpectedQueue() else {
return
}
completionExpectation.fulfill()
} receiveValue: { _ in
guard DispatchQueue.isExpectedQueue() else {
return
}
valueExpectation.fulfill()
}.store(in: &cancellables)
}
}
override func tearDown() {
cancellables.removeAll()
super.tearDown()
}
var cancellables = Set<AnyCancellable>()
}

How do I stop this NSThread?

I've got a function that gets called when my app enters background mode. I''d like to stop the thread if user re-opens the app. Nothing I'm trying works so far though.
Here's my code so far:
class Neversleep {
private static var callback : (()->Void)?
private static var thread: NSThread?
static func start(callback: ()->Void) {
self.callback = callback
Neversleep.thread = NSThread(target: self, selector: #selector(Neversleep.task), object: nil)
Neversleep.thread?.start()
}
static func stop() {
print("NEVERSLEEP:STOP")
Neversleep.thread?.cancel()
}
#objc static func task() {
while (true)
{
sleep(3);
print("we are still running!")
callback?()
}
}
}
I call Neversleep.start() from app Delegate's DidEnterBackground method.
I'm calling Neversleep.stop() from willEnterForeground...but it's not stopping the thread.
I'm sure I'm missing something obvious here. But what?
Calling cancel on a thread doesn't automatically kill the thread. The actual body of the thread needs to stop whatever it is doing when its thread is cancelled.
Update your task function like this:
#objc static func task() {
while (!NSThread.currentThread.cancelled)
{
sleep(3);
print("we are still running!")
callback?()
}
}
Double check the actual method and property names for currentThread and cancelled. I'm not 100% sure what they are named in Swift 2.
Even with the above, you will likely get one more call to callback after the thread is cancelled due to the sleep. You can fix this with:
#objc static func task() {
while (!NSThread.currentThread.cancelled)
{
sleep(3);
print("we are still running!")
if (!NSThread.currentThread.cancelled) {
callback?()
}
}
}

Resources