Search code examples
swiftbooleanselectornstimer

Using an Timer to switch a Boolean value?


I'm relatively new to coding and I'm trying to switch a boolean value by with Timer. However, I keep getting an error. Here's my code:

var Display = true

var BoolTimer = Timer.scheduledTimer(timeInterval: 5.0, target: self, selector: #selector(ThirdViewController.SwitchBool), userInfo: nil, repeats: true)

@objc func SwitchBool()
{
    if Display == true{
        Display = false
        print(Display)
    } else {
        Display = true
        print(Display)
    }
}

I get this error when the timer runs out:

[_SwiftValue SwitchBool]: unrecognized selector sent to instance 0x604000646ed0
2018-02-13 10:55:35.664486+1300 HC 1[12286:466779] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[_SwiftValue SwitchBool]: unrecognized selector sent to instance 0x604000646ed0'
libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb)

Can someone help me understand why I am getting this error and how to fix it?


Solution

  • If you run this code, what would you expect the output to be?

    import Foundation
    
    class C: NSObject {
        var foo = type(of: self)
    }
    
    print(C().foo)
    

    You're expecting to see C, right? But instead you get:

    (C) -> () -> C
    

    The reason for this is that when you use self in a property's default value, self refers to a closure which is created as part of the initialization process, not to the object itself. The reason for this is that the property default values are computed before the object's init function has completed, and you can't use self before the object has completed initialization. So, Objective-C is trying to send the SwitchBool method to a closure, which of course doesn't support that method.

    To fix this, just make the property lazy; this will cause the property to be initialized sometime after the object itself has been initialized, which will make it possible to use self and have it actually refer to your object, as you can see from the following test:

    import Foundation
    
    class C: NSObject {
        lazy var foo = type(of: self)
    }
    
    print(C().foo)
    

    which outputs:

    C
    

    EDIT: If the object in question is a view controller, initializing the timer to nil and creating it in viewDidLoad, as suggested by @vacawama, is also a good approach to take. Basically you just need to make sure you create the timer at some point after the object has completed its initialization.