Swift for iOS - 2 for loops run at the same time? - swift

I have two objects where I need to update their UI at the same time. I have a for loop for one, and after that another for loop. Each iteration in the for loop I have a short delay so that for elements in the object I am making a UI change... one after the other - not seemingly all at once.
func update(value: Int){
var delay: Double = 0.05
// first loop
for i in 0...value {
delayWithSeconds(delay) {
//do something with object 1
}
delay = delay + 0.05
}
var delay2: Double = 0.05
// second loop
for i in 0...value {
delayWithSeconds(delay2) {
//do something with object 2
}
delay2 = delay2 + 0.05
}
}
// Utility
func delayWithSeconds(_ seconds: Double, completion: #escaping () -> ()) {
DispatchQueue.main.asyncAfter(deadline: .now() + seconds) {
completion()
}
}
I have tried wrapping each for loop with DispatchQueue.main.async and it didn't make a difference. In short - I would like to run both for loops at the same time (or perceived as such). These are on the UI thread.

I tried this and it seemed to work out quite well. It does exactly what I want it to do (at least visually they seem to run at the same time).
let concurrentQueue = DispatchQueue(label: "net.ericd.hello", attributes: .concurrent)
concurrentQueue.async {
//my loop with delay here for object 1.
}
concurrentQueue.async {
//my separate loop with delay here for object 2.
}

We can use it when we want execute different arrays at the same time:
using this Generic Function
zip(_:_:)
Here i took 2 array:
var arrOfInt = ["1","2","3"]
var arrOfIntString = ["one","two","three"]
for (intNum, intString) in zip(arrOfInt, arrOfIntString) {
print("Int:\(intNum), String:\(intString)")
}

Related

How to cancel an Asynchronous function in Swift

In swift, what is the common practice to cancel an aync execution?
Using this example, which execute the closure asynchronously,
what is the way to cancel the async function?
func getSumOf(array:[Int], handler: #escaping ((Int)->Void)) {
//step 2
var sum: Int = 0
for value in array {
sum += value
}
//step 3
Globals.delay(0.3, closure: {
handler(sum)
})
}
func doSomething() {
//setp 1
self.getSumOf(array: [16,756,442,6,23]) { [weak self](sum) in
print(sum)
//step 4, finishing the execution
}
}
//Here we are calling the closure with the delay of 0.3 seconds
//It will print the sumof all the passed numbers.
Unfortunately, there is no generalized answer to this question as it depends entirely upon your asynchronous implementation.
Let's imagine that your delay was the typical naive implementation:
static func delay(_ timeInterval: TimeInterval, closure: #escaping () -> Void) {
DispatchQueue.main.asyncAfter(deadline: .now() + timeInterval) {
closure()
}
}
That's not going to be cancelable.
However you can redefine it to use DispatchWorkItem. This is cancelable:
#discardableResult
static func delay(_ timeInterval: TimeInterval, closure: #escaping () -> Void) -> DispatchWorkItem {
let task = DispatchWorkItem {
closure()
}
DispatchQueue.main.asyncAfter(deadline: .now() + timeInterval, execute: task)
return task
}
By making it return a #discardableResult, that means that you can use it like before, but if you want to cancel it, grab the result and pass it along. E.g., you can define your asynchronous sum routine to use this pattern, too:
#discardableResult
func sum(of array: [Int], handler: #escaping (Int) -> Void) -> DispatchWorkItem {
let sum = array.reduce(0, +)
return Globals.delay(3) {
handler(sum)
}
}
Now, doSomething can, if it wants, capture the returned value and use it to cancel the asynchronously scheduled task:
func doSomething() {
var task = sum(of: [16, 756, 442, 6, 23]) { sum in
print(Date(), sum)
}
...
task.cancel()
}
You can also implement the delay with a Timer:
#discardableResult
static func delay(_ timeInterval: TimeInterval, closure: #escaping () -> Void) -> Timer {
Timer.scheduledTimer(withTimeInterval: timeInterval, repeats: false) { _ in
closure()
}
}
And
#discardableResult
func sum(of array: [Int], handler: #escaping (Int) -> Void) -> Timer {
let sum = array.reduce(0, +)
return Globals.delay(3) {
handler(sum)
}
}
But this time, you'd invalidate the timer:
func doSomething() {
weak var timer = sum(of: [16, 756, 442, 6, 23]) { sum in
print(Date(), sum)
}
...
timer?.invalidate()
}
It must be noted that the above scenarios are unique to simple “delay” scenarios. This is not a general purpose solution for stopping asynchronous processes. For example, if the asynchronous tasks consists of some time consuming for loop, the above is insufficient.
For example, let's say you are doing something really complicated calculation in a for loop (e.g. processing the pixels of an image, processing frames of a video, etc.). In that case, because there is no preemptive cancelation, you'd need to manually check to see if the DispatchWorkItem or the Operation has been canceled by checking their respective isCancelled properties.
For example, let's consider an operation to sum all primes less than 1 million:
class SumPrimes: Operation {
override func main() {
var sum = 0
for i in 1 ..< 1_000_000 {
if isPrime(i) {
sum += i
}
}
print(Date(), sum)
}
func isPrime(_ value: Int) -> Bool { ... } // this is slow
}
(Obviously, this isn't an efficient way to solve the “sum of primes less than x” problem, but it just an example for illustrative purposes.)
And
let queue = OperationQueue()
let operation = SumPrimes()
queue.addOperation(operation)
We're not going to be able to cancel that. Once it starts, there’s no stopping it.
But we can make it cancelable by adding a check for isCancelled in our loop:
class SumPrimes: Operation {
override func main() {
var sum = 0
for i in 1 ..< 1_000_000 {
if isCancelled { return }
if isPrime(i) {
sum += i
}
}
print(Date(), sum)
}
func isPrime(_ value: Int) -> Bool { ... }
}
And
let queue = OperationQueue()
let operation = SumPrimes()
queue.addOperation(operation)
...
operation.cancel()
Bottom line, if it’s something other than a simple delay, and you want it to be cancelable, you have to integrate this into your code that can be run asynchronously.
Using this example..., what is the way to cancel the async function?
Using that example, there is no such way. The only way to avoid printing the sum is for self to go out existence some time in the 0.3 seconds immediately after the call.
(There are ways to make a cancellable timer, but the timer you've made, assuming that it's the delay I think it is, is not cancellable.)
I don't know your algorithm but first I have suggestions for some points.
If you want to delay, do it outside of getSumOf function for adapt Single Responsibility.
Use built-in reduce function to sum items in array in better and more efficient way.
You can use DispatchWorkItem to build a cancellable task. So you can remove getSumOf function and edit doSomething function like below.
let yourArray = [16,756,442,6,23]
let workItem = DispatchWorkItem {
// Your async code goes in here
let sum = yourArray.reduce(0, +)
print(sum)
}
// Execute the work item after 0.3 second
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3, execute: workItem)
// You can cancel the work item if you no longer need it
workItem.cancel()
You can also look into OperationQueue for advanced use.

Can't find a way to implement a wait function in my Swift code

I can't find a way to implement a wait function, I'm using swiftforwindows and no examples online have been able to solve it so far. It's Swift 4.2
The class is basically an array that when a function is called each index on the array gets a constant value deducted. the tick function is what is being called. I'm new to Swift.
class resProj {
var list = [1,1,1,1]
var projReq = [100,200,300,50]
var completed = false
func tick(){
for count in 0..<projReq.count{
if projReq[count] <= list[count]{
projReq[count] = 0
}
else if projReq[count] > list[count]{
projReq[count] -= list[count]
}
}
print(projReq)
}
init(
mathsP mathsIn: Int,
scienceP sciecnceIn: Int,
enginerP enginerIn: Int,
businessP businessIn: Int) {
self.list [0] = mathsIn
self.list [1] = sciecnceIn
self.list [2] = enginerIn
self.list [3] = businessIn
}
}
var spaceElev = resProj(
mathsP: 10,
scienceP: 20,
enginerP: 30,
businessP: 5)
var x = false
while x == false{
//wait function here pls//
print("tick", terminator:"?")
let y = readLine()
if y == "y"{
spaceElev.tick()
}
else{
print("gotta put y")
}
var templist = spaceElev.projReq
var templistcount = 0
templistcount = templist.count
for loop in 0..<templistcount{
if templist[loop] == 0{
templistcount -= 1
}
}
if templistcount == 0 {
x = true
print("project completed")
}
}
}
Where it says //wait function here pls// I would like to make the program wait for 1 second.
There are a lot of way to do this but most common way is create a completion function. For example:
func doSth(_ someParameter: String, _ completion: ()->()) {
print(someParameter)
// After your code is finish call completion
completion()
}
And when you call (there is two way to call):
doSth("Done") {
print("You can be sure that this block will work after your func finish")
}
or you can simply create another func and send it as a parameter.
You can also use DispatchQueue:
DispatchQueue.main.asyncAfter(deadline: .now()+1) {
// put your func here...
}
You can simple use the UNIX-Functin func sleep(_: UInt32) -> UInt32.
In your case use sleep(1) to wait one second.
You could use Grand Central Dispatch or perform.
GCD solution:
let delayInSeconds = 1
DispatchQueue.main.asyncAfter(deadline: .now() + delayInSeconds) {
print("tick", terminator:"?")
}
If you want to learn more about Grand Central Dispatch (GCD) I suggest you read through this:
Grand Central Dispatch - Wikipedia
Grand Central Dispatch Tutorial - Ray Wenderlich
Perform solution:
Create a function like this:
#objc func delayedFunc() {
//write the code here that you want to execute with a one second delay
}
Then call this where you want the delayed function to execute:
let delayInSeconds = 1
perform(#selector(delayedFunc), with: nil, afterDelay: delayInSeconds)
You can use the RunLoop class:
func wait(for interval: TimeInterval) {
RunLoop.current.run(until: Date() + interval)
}

calling function from an array with time delay

Let's say I have this array of functions:
lazy var funcArray = [firstFunc, secondFunc, thirdFunc, ....n+Func, ...Inifinit number of Func........]
How would you call them one after the other with a 2.5 second delay?
I have tried without success many things including this while loop:
while n < funcArray.count {
funcArray[n]()
DispatchQueue.main.asyncAfter(deadline: .now() + 2.5) {
n = n +1
}
}
i write small code for better understanding.
initialize count and funcTimer variable and created static function array .
var count:Int = 0
var funcTimer = Timer()
let funcArray:Array = [somefunc(),somefunc(),somefunc()]
After that add these line in appropriate place
funcTimer = Timer.scheduledTimer(timeInterval: 2.5, target: self, selector: (#selector(ViewController.scheduleArrayFunctions)), userInfo: nil, repeats: true)
#objc func scheduleArrayFunctions(funcarray:[ String])
{
if count < self.funcArray.count {
//this if is interesting checking both static function when it check it call the somefunc method
if self.funcArray[count] == ViewController.somefunc(){
print("equel")
}
self.count += 1
}
else
{
funcTimer.invalidate()
}
}
func somefunc()
{
print("hello world")
}
Hope so this will work for you.
I think this will work. Follow these steps.
Declare a variable count as zero.
Schedule a timer with time interval 2.5 and repeat to true.
Now call the function from the array with index as count inside the timer callback block.
Check if the count is less than array.count.
Increment the count.
Otherwise stop the timer.
I did a function to function loop
probably bad coding but...it works
n = 0
self.funcArray[n]()
n = n + 1
timerAction()
func timerAction() {
let when = DispatchTime.now() + 2.5
DispatchQueue.main.asyncAfter(deadline: when) {
self.funcArray[self.n]()
self.n = self.n + 1
if self.n < self.funcArray.count {
self.timerAction2()
}
}
}
func timerAction2() {
let when = DispatchTime.now() + 2.5
DispatchQueue.main.asyncAfter(deadline: when) {
self.funcArray[self.n]()
}
if self.n < self.funcArray.count {
self.timerAction()
}
}

Testing a Timer in Xcode with XCTest

I have a function that does not need to be called any more than every 10 secs. Every time I invoke the function, I reset the timer to 10 secs.
class MyClass {
var timer:Timer?
func resetTimer() {
self.timer?.invalidate()
self.timer = Timer.scheduledTimer(withTimeInterval: 10.0, repeats: false) {
(timer) -> Void in
self.performAction()
}
}
func performAction() {
// perform action, then
self.resetTimer()
}
}
I would like to test that calling performAction() manually resets the timer to 10 secs, but I can't seem to find any good way to do it. Stubbing resetTimer() feels like the test wouldn't really be telling me enough about the functionality. Am I missing something?
XCTest:
func testTimerResets() {
let myObject = MyClass()
myObject.resetTimer()
myObject.performAction()
// Test that my timer has been reset.
}
Thanks!
If you want to wait for the timer to fire, you'll still need to use expectations (or Xcode 9's new asynchronous testing API).
The question is what precisely you're trying to test. You presumably don't want to just test that the timer fired, but rather you want to test what the timer's handler is actually doing. (Presumably you have a timer in order to perform something meaningful, so that's what we should be testing.)
WWDC 2017 video Engineering for Testability offers a nice framework to be thinking about how to design code for unit tests , which need:
control over inputs;
visibility to outputs; and
no hidden state.
So, what are the inputs to your test? And, more importantly, what is the output. What assertions do you want to test for in your unit test?
The video also shows a few practical examples of how one might refactor code to achieve this structure through judicious use of:
protocols and parameterization; and
separating logic and effects.
It's hard to advise further without knowing what the timer is actually doing. Perhaps you can edit your question and clarify.
Good that you found a solution, but answering the question in title;
To test if timer actually works (i.e. runs and calls callback), we can do something like:
import XCTest
#testable import MyApp
class MyClassTest: XCTestCase {
func testStartTimer_shouldTriggerCallbackOnTime() throws {
let exp = expectation(description: "Wait for timer to complete")
// Dummy.
let instance: MyClass! = MyClass()
instance.delay = 2000; // Mili-sec equal 2 seconds.
instance.callback = { _ in
exp.fulfill();
}
// Actual test.
instance.startTimer();
// With pause till completed (sleeps 5 seconds maximum,
// else resumes as soon as "exp.fulfill()" is called).
if XCTWaiter.wait(for: [exp], timeout: 5.0) != .completed {
XCTFail("Timer didn't finish in time.")
}
}
}
When having a class like:
public class MyClass {
public var delay: Int = 0;
public var callback: ((timer: Timer) -> Void)?
public func startTimer() {
let myTimer = Timer(timeInterval: Double(self.delay) / 1000.0, repeats: false) {
[weak self] timer in
guard let that = self else {
return
}
that.callback?(timer)
}
RunLoop.main.add(myTimer, forMode: .common)
}
}
First, I would say I don't know how your object was working when you don't any member called refreshTimer.
class MyClass {
private var timer:Timer?
public var starting:Int = -1 // to keep track of starting time of execution
public var ending:Int = -1 // to keep track of ending time
init() {}
func invoke() {
// timer would be executed every 10s
timer = Timer.scheduledTimer(timeInterval: 10.0, target: self, selector: #selector(performAction), userInfo: nil, repeats: true)
starting = getSeconds()
print("time init:: \(starting) second")
}
#objc func performAction() {
print("performing action ... ")
/*
say that the starting time was 55s, after 10s, we would get 05 seconds, which is correct. However for testing purpose if we get a number from 1 to 9 we'll add 60s. This analogy works because ending depends on starting time
*/
ending = (1...9).contains(getSeconds()) ? getSeconds() + 60 : getSeconds()
print("time end:: \(ending) seconds")
resetTimer()
}
private func resetTimer() {
print("timer is been reseted")
timer?.invalidate()
invoke()
}
private func getSeconds()-> Int {
let seconds = Calendar.current.component(.second, from: Date())
return seconds
}
public func fullStop() {
print("Full Stop here")
timer?.invalidate()
}
}
Testing (explanation in the comments)
let testObj = MyClass()
// at init both starting && ending should be -1
XCTAssertEqual(testObj.starting, -1)
XCTAssertEqual(testObj.ending, -1)
testObj.invoke()
// after invoking, the first member to be changed is starting
let startTime = testObj.starting
XCTAssertNotEqual(startTime, -1)
/*
- at first run, ending is still -1
- let's for wait 10 seconds
- you should use async method, XCTWaiter and expectation here
- this is just to give you a perspective or way of structuring your solution
*/
DispatchQueue.main.asyncAfter(deadline: .now() + 10 ) {
let startTimeCopy = startTime
let endingTime = testObj.ending
XCTAssertNotEqual(endingTime, -1)
// take the difference between start and end
let diff = endingTime - startTime
print("diff \(diff)")
// no matter the time, diff should be 10
XCTAssertEqual(diff, 10)
testObj.fullStop()
}
this is not the best of way of doing it, however it gives you view or a flow on how you should achieve this :)
I ended up storing the original Timer's fireDate, then checking to see that after the action was performed the new fireDate was set to something later than the original fireDate.
func testTimerResets() {
let myObject = MyClass()
myObject.resetTimer()
let oldFireDate = myObject.timer!.fireDate
myObject.performAction()
// If timer did not reset, these will be equal
XCTAssertGreaterThan(myObject.timer!.fireDate, oldFireDate)
}

Swift - SpriteKit - Update SKAction.waitForDuration while running forever

I am trying to update the waitForDuration action in a sequence that is running forever.
override func didMoveToView(view: SKView) {
//code
runAction(SKAction.repeatActionForever(runSeq()))
}
func runSeq() -> SKAction{
var difficulty: CGFloat = 0.75
let updateAction = SKAction.runBlock({
self.runCount++
self.runCount %= 4
println(self.runCount)
if self.runCount == 0 {
difficulty -= 0.1
}
if self.children.count > 51{
println("You loose")
}else{
self.scoreLabel.text = String(format: "Score: %i", self.score)
}
})
let createAntAction = SKAction.runBlock({self.createAnt()})
var wait = SKAction.waitForDuration(NSTimeInterval(difficulty))
let seq = SKAction.sequence([createAntAction, wait, updateAction])
return seq
}
difficulty is the value that I want, and every 4 loops I want it to decrease by a certain amount. Currently, the starting value remains the same, and doesn't change, even though I am changing the difficulty value, and recalling the sequence of actions every time.
This is happening because wait is already established as .75, and nothing is changing that. SKAction.waitForDuration(NSTimeInterval(difficulty)) is not looking for a reference to difficulty, only a value, so you need to rethink how you want to code this.