Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

cancelPreviousPerformRequests does not seem to work in Swift 3.0

Tags:

ios

swift3

As the title states, for some reason, the following (simplified) code is not working:

extension InputView: {

    func updateTable(text: String) {
            NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(loadPlaces(text:)), object: nil)
          //NSObject.cancelPreviousPerformRequests(withTarget: self)

            self.perform(#selector(loadPlaces(text:)), with: text, afterDelay: 0.5)

            prevSearch = inputField.text!;
        }

    //Private wrapper function
    @objc private func loadPlaces(text: String) {
        print("loading results for: \(text)")
       // locator?.searchTextHasChanged(text: text)
    }
}

I call updateTable every time a user edits a UITextField, which calls localPlaces which calls a function that queries google's online places API (commented out). Unfortunately, the print line in loadPlaces is called after every single call to updateTable. From my visual inspection, it seems there is in fact a delay to the print statements, however, the old calls do not cancel. I've tried looking on a lot of StackOverflow threads but I couldn't find anything updated for Swift 3. Am I calling something incorrectly?

PS. If I instead use the commented out, single-argument, cancelPreviousPerformRequests. It works for some reason.


Edit: I have been able to replicate this error in a separate project. So I'm 100% sure that the above code is wrong. If you would like to replicate this error, open up a new iOS project and paste the following code into the default ViewController:

class InputView: UIView {

    func updateTable(text: String) {
        NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(loadPlaces(text:)), object: nil)

        self.perform(#selector(loadPlaces(text:)), with: text, afterDelay: 0.5)

    }

    //Private wrapper function
    @objc private func loadPlaces(text: String) {
        print("loading results for: \(text)")
        // locator?.searchTextHasChanged(text: text)
    }
}




class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        let input = InputView()

        for i in 0..<200 {
            input.updateTable(text: "Call \(i)")
        }
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }


}
like image 501
QuantumHoneybees Avatar asked Dec 13 '22 23:12

QuantumHoneybees


1 Answers

The explanation in Duncan C's answer is not appropriate for this case.

In the reference of cancelPreviousPerformRequests(withTarget:selector:object:):

Discussion

All perform requests are canceled that have the same target as aTarget, argument as anArgument, and selector as aSelector.

So, when you have a line like:

<aTarget>.perform(<aSelector>, with: <anArgument>, afterDelay: someDelay)

You can cancel it with:

NSObject.cancelPreviousPerformRequests(withTarget: <aTarget>, selector: <aSelector>, object: <anArgument>)

only when all 3 things aTarget, aSelector and anArgument match.


Please try something like this and check what you see:

class InputView: UIView {
    
    var lastPerformArgument: NSString? = nil
    
    func updateTable(text: String) {
        NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(loadPlaces(text:)), object: lastPerformArgument)

        lastPerformArgument = text as NSString
        self.perform(#selector(loadPlaces(text:)), with: lastPerformArgument, afterDelay: 0.5)
        
    }
    
    //Private wrapper function
    @objc private func loadPlaces(text: String) {
        print("loading results for: \(text)")
        // locator?.searchTextHasChanged(text: text)
    }
}
like image 113
OOPer Avatar answered Jan 30 '23 14:01

OOPer