Swift: test for expiration / invalidation of NSTimer seems to fail - timer

Using Swift, I'm having difficulty testing for the expiration / invalidation of an NSTimer.
In particular, why does it appear that a test like while timer1 != nil { } or while timer1?.valid { } does not escape the null loop after it has been seemingly clearly invalidated and set to nil directly.
I have reviewed several of the questions relating to NSTimer already, and unless there is a version in Obj-C that I simply am not recognizing as the same issue due to my poor comprehension of ObjC properly, I don't believe this is covered (directly).
Please note, I'm not looking to directly "fix" the code, I'm looking for comprehension.
// ViewController.swift
// NSTimerTest
import UIKit
class ViewController: UIViewController {
var z:Int = 0
var timer1:NSTimer? = nil
#IBOutlet var lblMessage: UILabel
#IBOutlet var lbl2: UILabel
#IBAction func btnPress(sender: AnyObject) {
doBtnPress()
}
override func viewDidLoad() {
super.viewDidLoad()
z = 0
lblMessage.text = "Timer was called \(z) times."
lbl2.text = "waiting for countdown"
}
func doBtnPress(){
lblMessage.text = "doBtnPress!!"
timer1 = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "doTimedAction", userInfo: nil, repeats: true)
// All of the below infinitely loop
// while timer1?.valid { } // <=== never escapes, also depreciated in IOS 8.0 ???
// while timer1 { } // <==== never escapes
// while timer1 != nil { } // <==== never escapes
// while z >= 0 { } // <==== test counter instead, but no use
lbl2.text = "timer has been invalidated"
}
func doTimedAction(){
lblMessage.text = "Timer was called \(++z) times."
if z >= 10 {
timer1?.invalidate()
timer1 = nil
z = -1
}
}
}
As you can see, I have two labels -- one that is simply updated with the number of times the NSTimer has been invoked, and the other that hold be updated after I invalidate the timer.
With the testing lines commented out as noted above, things work as I expect. The label indicating the number of timer invocations updates each second (or so) and the immediate but factually incorrect display of the statement that "Timer has been invalidated" )
However, it my expectation that de-commenting any of the three lines that follow should allow the timer to run through 10 iterations, and then be invalidated, allowing the "Timer invalidated" message to display. Instead, however, I end up stuck infinitely in the empty loop, as if the condition(s) never come true. The UI does not update at all.
Instead of testing the timer, I also tried testing the counter z, but this does not work either, leading me to believe there is something more elemental at play that I am not understanding. For example, using while z >= 0 { println("z is \(z)")} in the location results in z is 0 -- the reason the loop is infinite. But again, if I comment out the line, z does in fact clearly increment, as the label DOES change, reflecting the cane in the counter z.
Again, I'm looking more to understand WHY things are failing (i.e., what I am failing to comprehend here). I understand, for example, I could make this "work" by having the doTimedAction() func directly update the label --- but that would not help me understand why my tests fail.

Whilst you are in the loop, you are blocking the timer from firing, as both the timer and loop are on the same thread.
Check out the documentation on Run Loops, and also this post on creating an NSTimer on a background thread.

Related

For-loop isn't returning the items in an array in the correct order

I have an array of Integers that I need to loop through that then make a network request for further information and with the returned information populate an array of a new object I have created.
I want the returned data to come back in the same order in which the array provides it in, however it is coming back in a different order and am assuming it might have something to do with the network request.
I'm new(ish) to development, so the answer may be very obvious, but I'm really at a dead end with what to do next.
I've tried adding delays to the network request on each loop, i've tried calling .sort() on the array to ensure the array stays in the correct order
var tacticalCoverIdArray = [Int]()
var savedTacticalCoverData = [Covers]()
for coverID in tacticalCoverIdArray {
performGetRequestForSpecificCovers(coverID: coverID, targetURL: targetURL, completion: { (data, HTTPSatusCode, error) -> Void in
if HTTPSatusCode == 200 && error == nil {
do {
if coverID != 0 {
let decodedJSON = try JSONDecoder().decode([Covers].self, from: data)
savedTacticalCoverData.append(decodedJSON[0])
} else {
let data = Covers(id: 0, game: 0, image_id: "")
savedTacticalCoverData.append(data)
}
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(1000), execute: {
saveTacticalCoverData()
})
} catch let error {
print("Error decoding JSON: \(error)")
}
} else {
print("HTTP status code error: \(HTTPSatusCode)")
print("Error loading data: \(String(describing: error))")
}
})
}
When putting a print statement under the very first declaration of the for-loop (i.e: print(coverID) the return is what I would expect, in which it loops through each integer and then returns them in order.
However, as soon as I put the same print statement under the 'performGetRequestForSpecificCovers' method, the coverID array is not in the order that it should be in, and therefore I get my returned values in an incorrect order when I append them to my 'savedTacticalCoverData' array.
Your hunch about the network requests having an impact on the ordering seems correct.
What I'm guessing is happening here is that when you are looping over tacticalCoverIdArray and calling performGetRequestForSpecificCovers(), that loop doesn't wait for that network request to complete and for the completion block to get called. It continues with the next iteration. Effectively, you are sending tacticalCoverIdArray.count network requests in parallel. Those completion blocks get called much later, long after the outer loop is complete, and most likely even on a different thread.
The most basic, and worst, option is to use DispatchSemaphore to hold up the outer loop until the completion block is called. You'd create a semaphore, call semaphore.signal() inside the completion handler, and call semaphore.wait() at the end of every loop iteration. The problem with this approach is that you will wait for each network request to complete before proceeding to the next one. Also, you will tie up the thread that is executing the first outer loop, and threads are a finite resource, so it's not a good idea to waste them.
A better option is to dispatch all requests at once, and handle the out-of-order responses. This will complete much faster than serially dispatching them, unless you encounter some kind of limitations with dispatching so many network requests in parallel. Instead of savedTacticalCoverData being an array, perhaps it could be a dictionary, where the key is the index of the outer loop, and the value is what you're trying to save? Each time the completion handler gets called, you could check whether or not the dictionary is full and you've accumulated all of the responses you want, and only then proceed with the final "everything is done" action, presumably saveTacticalCoverData().
You'll have to be careful to get your multithreading right. Unless performGetRequestForSpecificCovers() uses only one callback queue, and it's the same queue that this function is running on, you might get called on different threads. If that's the case, I would recommend making a new DispatchQueue and always operating on your dictionary only from that queue, to ensure consistency when those completion blocks come in on random threads. Something like this:
class MyClass {
var tacticalCoverIdArray = [Int]()
var savedTacticalCoverData = [Int: Covers]()
var queue = DispatchQueue(label: "Class Internal Queue")
func myFunc() {
// ... fill in the blanks here
for (index, coverID) in tacticalCoverIdArray.enumerated() {
performGetRequestForSpecificCovers(coverID: coverID, targetURL: targetURL, completion: { (data, HTTPSatusCode, error) -> Void in
if HTTPSatusCode == 200 && error == nil {
do {
queue.async {
if coverID != 0 {
let decodedJSON = try JSONDecoder().decode([Covers].self, from: data)
self.savedTacticalCoverData[index] = decodedJSON[0]
} else {
let data = Covers(id: 0, game: 0, image_id: "")
self.savedTacticalCoverData[index] = data
}
if self.savedTacticalCoverData.count == self.tacticalCoverIdArray.count {
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(1000), execute: {
self.saveTacticalCoverData()
})
}
}
} catch let error {
print("Error decoding JSON: \(error)")
}
} else {
print("HTTP status code error: \(HTTPSatusCode)")
print("Error loading data: \(String(describing: error))")
}
})
}
}
}

_.each wait till boolean condition has been met to loop around

How can I achieve a loop like this:
foobar.each(function (model, j) {
// asynchrounous call etc. {in here bool get set to true}
// outside all asynchronous calls
// wait till bool is true, without stopping anything else except the loop to the top of
the _.each
})
I asked a similar question yesterday. But it got marked as a duplicate when it wasn't the same case. Their solution did not achieve the same thing. Also generator functions were suggested which looked like it would work. But I can't use them with ecmascript 5
I've tried busy loops and set time out but they don't seem to work either
I've also tried this:
goto();
function goto() {
if (foo === true) {
//return true; /*I've tried with and without the return because the loops
doesn't need a return*/
} else {
goto();
}
}
What happens with the goto() method is it breaks. Giving me the right results for the first iterations then execution seems to stop altogether. 'foo' always gets set to true in normal execution though.
What you could do is implement a foreach yourself, where you execute your condition, and then on success callback go to the next item (but meanwhile the rest of the code will keep running.
var iteration = 0 //count the iteration of your asynchronous process
//start looping
loop(iteration)
function loop(iteration){
var model = foobar[iteration];
//exit your loop when all iterations have finished (assuming all foobar items are not undefined)
if (foobar[iteration] === undefined){
return;
}
//do what you want
//on success callback
iteration++;
loop(iteration);
//end success callback
}

Removing data from array correctly

I am facing the problem that I fill up an array with data and when I want to remove it later, even though I call the removeAll() the array still is not empty.
Better see my code for a more clear view on the problem
Updated new code
#objc func textFieldDidChange() {
doSearch()
if let commentText = commentTextField.text , !commentText.isEmpty {
sendButton.setTitleColor(UIColor.blue, for: UIControlState.normal)
sendButton.isEnabled = true
return
}
sendButton.setTitleColor(UIColor.lightGray, for: UIControlState.normal)
sendButton.isEnabled = false
}
func doSearch() {
let caption = commentTextField.text
let words = caption?.components(separatedBy: CharacterSet.whitespacesAndNewlines)
self.usersSuggestion.removeAll()
for var word in words! {
self.usersSuggestion.removeAll()
if word.hasPrefix("#") {
word = word.trimmingCharacters(in: CharacterSet.punctuationCharacters)
self.isCellSelected = true
self.usersSuggestion.removeAll()
API.User.suggestUsers(withText: word, completion: { (user) in
print("closure", word)
self.usersSuggestion.append(user)
self.tableView.reloadData()
print("#", self.usersSuggestion.count)
})
self.usersSuggestion.removeAll()
tableView.reloadData()
} else {
self.isCellSelected = false
self.usersSuggestion.removeAll()
tableView.reloadData()
}
self.usersSuggestion.removeAll()
tableView.reloadData()
}
}
I am facing the problem here that the array, even though I call multiple times the removeAll() method, never gets back to 0. When I have 2 user, link one, the next time I write an # I get the 2 users+ the linked user (so him twice). I can do it infinitely, having like 100 userSuggestions with just 2 existing users.
photos
You are printing the count immediately after inserting an element in the array. This will alway give a count of 1.
// count was zero
self.hashTags.insert(hashTag, at: 0) // adding one
print("# = ", self.hashTags.count) // count is now 1
Given that the API.user... function uses a completion handler, I would assume that this happens in a separate thread or at least asynchronously so you could event get into situations where the UI shows empty and suddenly shows one.
You may want to structure your code differently to better convey the separation between requesting data and displaying the (asynchronously obtained) results. Embedded completion handlers tend be misleading and give the impression that execution will happen in their visually organized sequence.

Rx Swift simple timer not working

I have this RxSwift code in swift 3
let bag:DisposeBag = DisposeBag()
var sig:Observable<Int>!
sig = Observable<Int>.interval(1.0, scheduler: MainScheduler.instance)
sig.subscribe(onNext: { (milsec) in
print("Mil: \(milsec)")
}).addDisposableTo(bag)
i run this code when button tapped, but its not print anything on console.
DisposeBag will dispose of your subscription once it goes out of scope. In this instance, it'll be right after the call to subscribe, and it explains why you don't see anything printed to the console.
Move the definition of dispose bag to the class creating the subscription and everything should work fine.
class MyViewController: UIViewController {
let bag:DisposeBag = DisposeBag()
dynamic func onButtonTapped() {
var sig:Observable<Int>!
sig = Observable<Int>.interval(1.0, scheduler: MainScheduler.instance)
sig.subscribe(onNext: { (sec) in
print("Sec: \(sec)")
}).addDisposableTo(bag)
}
}
On a side note, interval expects an interval in seconds, so it will only tick every seconds as oposed to milliseconds.

Adding values to Array in Swift Class but Array claims it is empty when tested

I am trying to make an array of integers with data I have stored on an online database (Parse). I know for a fact that I am receiving the data and that my variable is storing that data. I also know for a fact that the data value is being added as when I print out the size of the array under my append line, the size constantly increases until it reaches the size of my database (90). However, when I print the size of the array at the end of the method or in my constructor, I get a value of 0. I have a feeling that this problem is happening because of the "self" keyword but I am not sure how to get around it. My end goal is store all the values from the database into an array that I create and can globally access.
Here is the code:
import Foundation
class DataLoader
{
var allData: [Int] = []
init()
{
allData = []
generateAllData()
println(allData.count)
}
private func generateAllData()
{
var tempVal: Int = 0
var tempArray: [Int] = []
Parse.setApplicationId("CENSORED FOR PRIVACY REASONS", clientKey: "CENSORED FOR PRIVACY REASONS")
var query = PFQuery(className: "Snapshot")
query.selectKeys(["objectID"])
query.findObjectsInBackgroundWithBlock
{
(objects: [AnyObject]!, error: NSError!) -> Void in
if error == nil
{
for obj in objects
{
var temp: String = obj.objectId
var newQuery = PFQuery(className: "Data")
newQuery.getObjectInBackgroundWithId(temp)
{
(dataValue: PFObject!, error: NSError!) -> Void in
if error == nil && dataValue != nil
{
tempVal = dataValue["Year"].integerValue
}
else
{
println(error)
}
}
self.allData.append(tempVal)
println(self.allData.count)
}
}
}
println(allData.count)
}
}
The problem is that the println's at the end of the method and the end of the init are happening while the background operation is still getting the data.
When you call "query.findObjectsInBackgroundWithBlock", it will go off into the background and get the data. But that does not block the next line from being called and it does not stop the function returning.
I suggest you add a callback or delegate method so that the rest of your code knows when the data has completely downloaded.
And you use of "self" is correct. Inside a block, you have to provide that context.
Update:
Adding more info about blocks and callbacks.
When you are using a block, the code after the block continues on without waiting for the block to complete. So a good way to know when the block has finished it to send a callback function as one of the parameters to the function containing the block. Once the block has completed, it can use the callback function to report back.
As a shorter example than your code, have a look at this animation function:
doSomeAnimation() // call the function below
func doSomeAnimation() {
println("Starting doSomeAnimation")
UIView.animateWithDuration(2.0,
animations: { () -> Void in
// animate something here
self.alphaButton.alpha = 1.0
}) { (animationDone) -> Void in
// animation over
println("Animation complete")
}
println("Ending doSomeAnimation")
}
You will see it contains 3 println() statements and the order in which they appear may surprise you:
Starting doSomeAnimation
Ending doSomeAnimation
Animation complete
So the doSomeAnimation() function is over long before the animation has completed. In this trivial example, that doesn't matter, but what if you need to know before the rest of your code could proceed?
One solution is to send a callback function to the function with the block. Here is the previous example with a callback in place:
doSomeAnimation() { (complete : Bool) in
println("Animation callback")
}
func doSomeAnimation(callback : ( Bool ) -> Void) {
println("Starting doSomeAnimation")
UIView.animateWithDuration(2.0,
animations: { () -> Void in
// animate something here
self.alphaButton.alpha = 1.0
}) { (animationDone) -> Void in
// animation over
println("Animation complete")
callback(animationDone)
}
println("Ending doSomeAnimation")
}
So the doSomeAnimation() function is called, but it is provided with a function as its parameter. It performs the animation and when the animation has finished, it uses this supplied callback function to report back to the caller.
And now the println() statements show as follows:
Starting doSomeAnimation
Ending doSomeAnimation
Animation complete
Animation callback
Hopefully this makes it clearer, both what the issue is an a possible solution.

Resources