Using an Timer to switch a Boolean value? - swift

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?

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.

Related

Run method on class knowing only its name

how can I launch method knowing its name (as String)
// key.rawValue.firstUppercased is `ApiAddress`
let result = preferencesRepository.perform(Selector("get\(key.rawValue.firstUppercased)"))
where preferencesRepository has method getApiAddress() and conforms to NSObject
public class RealPreferencesRepository: NSObject {
func getApiAddress() -> String
// ...
I have fatal error:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException',
reason: '-[PreferencesModule.RealPreferencesRepository getApiAddress]:
unrecognized selector sent to instance 0x600000090a70'
thanks for help in advance
You need to prefix the getApiAddress() method with #objc attribute.
Also, since the return value of the perform method is Unmanaged, you need to use takeRetainedValue() to convert the return value.
public class RealPreferencesRepository: NSObject {
#objc func getApiAddress() -> String {
return "success"
}
}
let preferencesRepository = RealPreferencesRepository()
let result = preferencesRepository.perform(Selector("getApiAddress"))
let value = result?.takeRetainedValue() as! String
print(value)
// => success

Crashes when calling delegate method inside URLSession closure

I created class named FileTransferManager which manages upload/download task using URLSession.
Because of code's length, I created a gist of my code. : https://gist.github.com/Cyanide7523/eb2f7a743459055e13b8568a51f644f3
And I created delegate protocol to recognize the transfer result.
This is a sample usage of this class :
class SampleViewController: UIViewController, FileTransferDelegate{
let fileMan = FileTransferManager()
fileMan.delegate = self
fileMan.download( /* Some parameters */ )
func fileTransferManager(_ sender: FileTransferManager, didSucceedDownload data: Data, ...) {
print("Download Succeed!")
}
}
But when FileTransferManager calls delegate functions, App always crashes with message "unrecognized selector sent to instance" and I can't figure out why does this crashes.
+++ Error logs
2018-06-27 14:31:57.851160+0900 Project[1428:2194695] -[Project.InitialViewController fileTransferManagerWithSender:willDownload:at:]: unrecognized selector sent to instance 0x10207a0e0
2018-06-27 14:31:57.851783+0900 Project[1428:2194695] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Project.InitialViewController fileTransferManagerWithSender:willDownload:at:]: unrecognized selector sent to instance 0x10207a0e0'
*** First throw call stack:
(0x184d1ad8c 0x183ed45ec 0x184d28098 0x18ee0adb0 0x184d202d4 0x184c0641c 0x1003974b0 0x100399094 0x100396d8c 0x1852a9e4c 0x1852c2b6c 0x185742e88 0x1856848d0 0x185683cac 0x101ec119c 0x101ecd7cc 0x101ec119c 0x101ecd7cc 0x101ecd6b0 0x185744750 0x101ec119c 0x101ece454 0x101eccd44 0x101ed27c8 0x101ed2500 0x18493ffac 0x18493fb08)
libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb)
Do you implement #objc optional func fileTransferManager(_ sender: FileTransferManager, willDownload contentID: String, at room: String) method in InitialViewController? Also, make your FileTransferManager delegate reference "weak" and remove all force-unwrap's when you call delegate methods (just replace "!" by "?").

making a blank(non nil) tviRoom object for delegate function testing

I am creating a test case, which tests if the delegate function didDisconnectwithError can be called, for TVIroom class. for that I need to pass the delegate function a blankempty TVI room object. However the delegate function does not accept nil values as inputs, and forced unwrapping is not allowed.
How do I pass a TVIroom object to the delegate for testing, if it can't be nil?
here is my code so far:
func testDisconnectCalled_usedWhileSwitching_AndwhileExplicitlyDisconnecting() {
let delegate = RoomTestsDelegate()
let room: TVIRoom? = nil
let error: Error? = nil
delegate.room(room, didDisconnectWithError: error)
expect(delegate.notifiedAboutDidDisconnect).toEventually(beTrue(), timeout: 1)
}
If you check out the class reference of TVIRoom, it says that it is not recommended to be initialized by developers.
You could, however, create a mocked class similar to TVIRoom and set the delegate appropriately.

Selector to method inside singleton

This sounds like a stupid question, but I have been trying to find a solution for hours now, and I still don't know what to do. I am using Swift 3.0, and I am having an issue calling a method inside a singleton class from a selector inside another class. My singleton class is as follows:
class Singleton : NSObject {
static let sharedInstance = Singleton()
private override init() {} // defeats instantiation
func myAction() {
// do something useful...
}
}
Then, here is the class from which I am calling the method contained in the Singleton:
class StatusBarPresenter {
func addItemsToMenu(menu: NSMenu) {
...
menu.insertItem(withTitle: "Disconnect this network",
action: #selector(Singleton.sharedInstance.myAction),
keyEquivalent: "D", at: 4)
...
}
}
Xcode doesn't complain about the code... it compiles without any errors or warnings, but the selector doesn't work. The UIMenuItem that I add to the menu is disabled, which means that the selector is not working. If the selector instead calls a method inside the class, everything works fine just as usual. This is a screenshot of what I am getting:
Thanks to Martin R. for pointing out that in my code I was not setting an explicit target for the UIMenuItem, leading to it being nil and ultimately self.
The following line added to the addItemsToMenu function after the call to insertItem solves the problem:
menu.item(at: 4)?.target = Singleton.sharedInstance

NSSetUncaughtExceptionHandler not firing in Swift 3?

I'm trying to catch all NSExceptions in a Swift3 program - have been following this Stack Overflow answer
So I've got this:
override func viewDidLoad() {
super.viewDidLoad()
NSSetUncaughtExceptionHandler { exception in
print("EXCEPTION CAUGHT HERE....")
print(exception)
print(exception.callStackSymbols)
}
// force NSException
let array = NSArray()
_ = array.object(at: 99)
}
but the exception's not being called
Instead I get this output:
2016-09-04 14:49:14.252 catchAllErrorsTest[8126:164827] Failed to set
(contentViewController) user defined inspected property on (NSWindow):
*** -[__NSArray0 objectAtIndex:]: index 99 beyond bounds for empty NSArray
I also tried putting the exception into AppDelegate (in applicationWillFinishLaunching and applicationDidFinishLaunching), but same result
I did as follows and works for me in Swift 3.
Define your NSSetUncaughtExceptionHandler as a constant outside any method declaration in your AppDelegate:
let uncaughtExceptionHandler : Void = NSSetUncaughtExceptionHandler { exception in
NSLog("Name:" + exception.name.rawValue)
if exception.reason == nil
{
NSLog("Reason: nil")
}
else
{
NSLog("Reason:" + exception.reason!)
}
}
This fires at any uncaught exception as this constant is evaluated as soon as your App Delegate instance loads i.e. at app launch.
Note: the Void type declaration removes a compiler warning for a Void inferred type that "could be unexpected" ;-) .