Search code examples
iostimerswiftunrecognized-selector

Unrecognized selector sent to instance in function call


I'm calling a function in a timer that throws the following error:

AppName.CollectionViewController lastActive]: unrecognized selector sent to instance

The app crashes and throws the exception right when the timer is scheduled to fire. Here's the timer and the function it calls:

       var timer = NSTimer.scheduledTimerWithTimeInterval(180, target: self, selector: Selector("lastActive:"), userInfo: nil, repeats: true)
        }

        func lastActive(){
            if PFUser.currentUser(){
                PFCloud.callFunctionInBackground("registerActivity", withParameters: [:], target: nil, selector: Selector("lastActive:"))
            }
        }

Is there another way to call the function in the timer? What do I need to put in the "selector:" parameter?

Thanks!

EDIT: Here's the whole class:

import UIKit

let reuseIdentifier = "Cell"

class LobbyCollectionViewController: UICollectionViewController, UICollectionViewDataSource {

    var lobbyData:NSMutableArray = NSMutableArray()


    override func viewDidAppear(animated: Bool) {

        if (!PFUser.currentUser()){
            var loginAlert:UIAlertController = UIAlertController(title: "Sign Up / Login", message: "Enter your login information and press the Log In button to log into the app. If you are new, enter in the desired login information and press the Sign Up button.", preferredStyle:UIAlertControllerStyle.Alert)

            loginAlert.addTextFieldWithConfigurationHandler({
                textfield in
                textfield.placeholder = "Username"
            })

            loginAlert.addTextFieldWithConfigurationHandler({
                textfield in
                textfield.placeholder = "Password"
                textfield.secureTextEntry = true
            })


            loginAlert.addAction(UIAlertAction(title: "Login", style: UIAlertActionStyle.Default, handler: {
                alertAction in
                let textFields:NSArray = loginAlert.textFields as NSArray
                let usernameTextField:UITextField = textFields.objectAtIndex(0) as UITextField
                let passwordTextField:UITextField = textFields.objectAtIndex(1) as UITextField

                PFUser.logInWithUsernameInBackground(usernameTextField.text, password: passwordTextField.text){
                    (user:PFUser!, error:NSError!)->Void in
                    if (user){
                        println("Login successful")
                    } else {
                        println("Login failed")
                    }
                }
                }))


            loginAlert.addAction(UIAlertAction(title: "Sign Up", style: UIAlertActionStyle.Default, handler: {
                alertAction in
                let textFields:NSArray = loginAlert.textFields as NSArray
                let usernameTextField:UITextField = textFields.objectAtIndex(0) as UITextField
                let passwordTextField:UITextField = textFields.objectAtIndex(1) as UITextField

                var user:PFUser = PFUser()
                user.username = usernameTextField.text
                user.password = passwordTextField.text

                user.signUpInBackgroundWithBlock{
                    (success:Bool!, error:NSError!)->Void in
                    if !error{
                        println("Sign Up successful")
                    }else{
                        let errorString = error.userInfo["error"] as NSString
                        println(errorString)
                    }

                }

            }))

            self.presentViewController(loginAlert, animated: true, completion: nil)
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        if PFUser.currentUser(){

            self.loadData()

            PFCloud.callFunctionInBackground("registerActivity", withParameters: [:], target: nil, selector: "block:")

            var timer = NSTimer.scheduledTimerWithTimeInterval(180, target: self, selector: Selector("lastActive:"), userInfo: nil, repeats: true)
            //(timeInterval: 180, target: self, selector: "registerActivity", userInfo: nil, repeats: true)
        }

        func lastActive(){
            if PFUser.currentUser(){
                PFCloud.callFunctionInBackground("registerActivity", withParameters: [:], target: nil, selector: "block:")
            }
        }


        // Uncomment the following line to preserve selection between presentations
        // self.clearsSelectionOnViewWillAppear = false

        // Register cell classes
        self.collectionView.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)

        // Do any additional setup after loading the view.
    }

    func loadData(){
        lobbyData.removeAllObjects()

        if PFUser.currentUser(){

            PFCloud.callFunctionInBackground("getOnlineUsers", withParameters: [:], target: nil, selector: "block:")

            func block(users: NSArray, error:NSError){
                if(error != nil){

                    self.lobbyData = users as NSMutableArray
                }
            }

            //var timer = NSTimer.scheduledTimerWithTimeInterval(180, target: self, selector: "block:", userInfo: nil, repeats: true)

        }

    }

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

    /*
    // MARK: - Navigation

    // In a storyboard-based application, you will often want to do a little preparation before navigation
    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
        // Get the new view controller using [segue destinationViewController].
        // Pass the selected object to the new view controller.
    }
    */

    // MARK: UICollectionViewDataSource

    override func numberOfSectionsInCollectionView(collectionView: UICollectionView!) -> Int {
        //#warning Incomplete method implementation -- Return the number of sections
        return 1
    }


    override func collectionView(collectionView: UICollectionView!, numberOfItemsInSection section: Int) -> Int {
        //#warning Incomplete method implementation -- Return the number of items in the section
        return lobbyData.count
    }

    override func collectionView(collectionView: UICollectionView!, cellForItemAtIndexPath indexPath: NSIndexPath!) -> UICollectionViewCell! {
        //let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as UICollectionViewCell

        // Configure the cell

        var cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as FightCollectionViewCell
        let battle:PFUser = self.lobbyData.objectAtIndex(indexPath!.row) as PFUser



        return cell
    }

And more detailed error:

[AppName.LobbyCollectionViewController lastActive:]: unrecognized selector sent to instance 0x7ffc60c34100
AppName[435:6742] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[AppName.LobbyCollectionViewController lastActive:]: unrecognized selector sent to instance 0x7ffc60c34100'
*** First throw call stack:
(
    0   CoreFoundation                      0x00000001117343e5 __exceptionPreprocess + 165
    1   libobjc.A.dylib                     0x0000000113247967 objc_exception_throw + 45
    2   CoreFoundation                      0x000000011173b4fd -[NSObject(NSObject) doesNotRecognizeSelector:] + 205
    3   CoreFoundation                      0x00000001116937ec ___forwarding___ + 988
    4   CoreFoundation                      0x0000000111693388 _CF_forwarding_prep_0 + 120
    5   Foundation                          0x0000000111b67e94 __NSFireTimer + 83
    6   CoreFoundation                      0x000000011169c4d4 __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ + 20
    7   CoreFoundation                      0x000000011169c095 __CFRunLoopDoTimer + 1045
    8   CoreFoundation                      0x000000011165f3cd __CFRunLoopRun + 1901
    9   CoreFoundation                      0x000000011165e9f6 CFRunLoopRunSpecific + 470
    10  GraphicsServices                    0x00000001153a69f0 GSEventRunModal + 161
    11  UIKit                               0x0000000111fbd990 UIApplicationMain + 1282
    12  AppName                           0x000000010fff808e top_level_code + 78
    13  AppName                           0x000000010fff80ca main + 42
    14  libdyld.dylib                       0x00000001137cf145 start + 1
    15  ???                                 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb) 

Solution

  • If you use a selector with a ':' at the end, it will look for a selector which takes a parameter. Try with "lastActive" or add a parameter for the sending context's 'self'.

    The function marked by the name given to the selector must be a member of the target. I.e. in the code above you specify the target as 'self' which refers to an instance of the class LobbyCollectionViewController. However, your function func lastActive() is embedded within the override func viewDidLoad() function - which means it is not a member of LobbyViewController (and is a very non typical way of coding, to have functions within functions, that are not closures). To fix this, refactor as follows:

    override func viewDidLoad() {
        super.viewDidLoad()
    
        if PFUser.currentUser(){
    
            self.loadData()
    
            PFCloud.callFunctionInBackground("registerActivity", withParameters: [:], target: nil, selector: "block:")
    
            var timer = NSTimer.scheduledTimerWithTimeInterval(180, target: self, selector: Selector("lastActive"), userInfo: nil, repeats: true)
            //(timeInterval: 180, target: self, selector: "registerActivity", userInfo: nil, repeats: true)
        }
    
        // Uncomment the following line to preserve selection between presentations
        // self.clearsSelectionOnViewWillAppear = false
    
        // Register cell classes
        self.collectionView.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
    
        // Do any additional setup after loading the view.
    }
    
    
    func lastActive(){
        if PFUser.currentUser(){
            PFCloud.callFunctionInBackground("registerActivity", withParameters: [:], target: nil, selector: "block:")
            }
    }
    

    Note that the function is now at the same level as viewDidLoad and that the selector is without the ':'. Hope that works for you!