How can I delay a few seconds in Swift - ios

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)}

Related

Trying to do UI updates in async function

I have an async function that purchases a subscription. It then dismisses the view via the DismissAction that was passed to it, sets a #Published value to true, and then sets the alternate app icon.
The following code doesn't work properly due to UI updates being called from an async function:
func makePurchase(dismiss: DismissAction) async throws {
let (_, purchaserInfo, _) = try await Purchases.shared.purchasePackage(selectedPackage)
dismiss() // Doesn't like this
if purchaserInfo.hasPermissions {
upgradeToUltimate() // Or this
}
}
struct SubscriptionService {
function upgradeToUltimate() {
Auth.shared.user?.isUltimate = true // This is setting a #Published that updates UI elements
if UIApplication.shared.supportsAlternateIcons,
UIApplication.shared.alternateIconName == nil {
UIApplication.shared.setAlternateIconName(Constants.AppIconNames.ultimate) // This stops my dismiss() from happening
}
}
}
So to fix this I've done the following by adding #MainActor to the first function and wrapping the second one in a DispatchQueue call. It works, but I'm just wondering if there's a better way to do this:
#MainActor
func makePurchase(dismiss: DismissAction) async throws {
let (_, purchaserInfo, _) = try await Purchases.shared.purchasePackage(selectedPackage)
dismiss()
if purchaserInfo.hasPermissions {
SubscriptionService.upgradeToUltimate()
}
}
struct SubscriptionService {
static func upgradeToUltimate() {
DispatchQueue.main.async {
Auth.shared.user?.isUltimate = true
if UIApplication.shared.supportsAlternateIcons,
UIApplication.shared.alternateIconName == nil {
UIApplication.shared.setAlternateIconName(Constants.AppIconNames.ultimate)
}
}
}
}
Interacting with the UI must only be done on the main thread/queue/actor. You've done this correctly.

Swift. How to return a function from a closure

I have a question regarding closure
function AA(){
localPlayer.authenticateHandler{
//…
if trigger {
retrun
}
}
dosomething()
}
In the above code,
I want to write a code that wants to return to the AA() function when the trigger is satisfied in the authenticateHandler closure that is called by an asynchronous callback.
The result of the code above is
When trigger occurs, only the closure is returned and the dosomething() method is executed below.
Is there any way
I have short English skills, but thank you for your help.
Closure does not support return statement. Instead use completion block inside main thread to perform the task:
i.e
function AA( completion: #escaping (Result<[Return Type], Error>) -> Void) {
localPlayer.authenticateHandler{
//…
if trigger {
DispatchQueue.main.async {
completion(//Whatever Return Value)
}
}
dosomething()
}
}
Closure does not support return statement.
Do it this way.
here i used string comparison to show how completion block will work.
func call() {
compareString(str1: "hey", str2: "hey") { isMatch in
if isMatch! { //Trigger
self.doSomething()
}
}
}
func compareString(str1:String,str2:String,completion:#escaping ((Bool?) -> Void)) {
if str1 == str2 {
completion(true)
}else {
completion(false)
}
}
func doSomething() {
}
It doesn’t work that way. A synchronous function can’t wait for the result of an asynchronous function. Give AA a callback closure and call it when authenticateHandler returns.

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>()
}

Function that executes after multiple completion block has finished

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.

RxSwift: using rx_refreshing for uirefreshcontrol

I am using the UIRefreshControl + Variable binding to reload data.
It is working, however, the following feels wrong to me:
1) I know there is a rx_refreshing variable in the RXCocoa extension, but I am unable to get it to work in this context.
2) I am binding answers (which is a Variable of array) twice. Once when I load the view controller and again when the UIRefreshControl is refreshing.
3) The parts where I check for whether the UIRefreshControl is refreshing or not looks really awkward. It feels like it defeats the purpose of using reactive?
...
let answers: Variable<[Answer]> = Variable([])
override func viewDidLoad() {
loadAnswers()
.shareReplay(1)
.bindTo(answers)
.addDisposableTo(self.disposeBag)
setupRx()
}
func loadAnswers() -> Observable<[Answer]> {
return Network.rxArrayRequest(Spark.Answers)
}
func setupRx() {
rc.rx_controlEvent(.ValueChanged)
.map { _ in !self.rc.refreshing }
.filter { $0 == false }
.flatMapLatest { [unowned self] _ in
return self.loadAnswers()
}
.bindTo(answers)
.addDisposableTo(self.disposeBag)
rc.rx_controlEvent(.ValueChanged)
.map { _ in self.rc.refreshing }
.filter { $0 == true }
.subscribeNext { [unowned self] _ in
self.rc.endRefreshing()
}
.addDisposableTo(self.disposeBag)
}
...
So first of all, It's not actually working. It just seems to be working. In your code, you're actually not waiting for the network request to finish before you call rc.endRefreshing(). Instead, you're just making the network call and then immediately calling endRefreshing().
// `rc.rx_controlEvent(.ValueChanged)` only gets called once,
// when the user pulls down.
rc.rx_controlEvent(.ValueChanged) // user pulled down to refresh
.map { _ in !self.rc.refreshing } // !true -> false
.filter { $0 == false } // false == false
.flatMapLatest { [unowned self] _ in
return self.loadAnswers() // request answers
}
.bindTo(answers)
.addDisposableTo(self.disposeBag)
rc.rx_controlEvent(.ValueChanged) // user pulled down to refresh
.map { _ in self.rc.refreshing } // true -> true
.filter { $0 == true } // true == true
.subscribeNext { [unowned self] _ in
self.rc.endRefreshing() // end refreshing
}
.addDisposableTo(self.disposeBag)
To address concern 1, you're right, you can use rx_refreshing to turn off refreshing instead of endRefreshing().
To address concern 2, I don't think the Variable is necessary or useful, at least in this example. You could still use it though. Also, it's not necessary to loadAnswers() in two places.
To address concern 3, yea, you could be simplifying this a lot and using Rx a bit more.
Here's code that would actually work, use rx_refreshing, and simplify things a lot:
let initial = Observable<Void>.just(())
let refresh = rc.rx_controlEvent(.ValueChanged).map { _ in () }
let answers = Observable.of(initial, refresh)
.merge()
.flatMapLatest{ _ in self.loadAnswers() }
.shareReplayLatestWhileConnected()
answers
.map { _ in false }
.bindTo(rc.rx_refreshing)
.addDisposableTo(disposeBag)
// also use `answers` to bind to your data source, etc.

Resources