Tuple as function parameter gives "Unrecognized selector sent..." - swift

I have a function declared like this:
func rspGetCategories(_ response: (Int, [String:Any])) {
I try to call it like this:
self.perform(act, with: (tag, outjson))
Where:
act = Selector(("rspGetCategories:"))
tag = 1
outjson = ["status":"ServerError"]
I just get an "unrecognized selector sent...". What am I missing here?
Full error message:
2018-07-18 11:20:15.852755+0200 Appname[8071:4529543] -[Appname.ViewController rspGetCategories:]: unrecognized selector sent to instance 0x10380be00
2018-07-18 11:20:15.853361+0200 Appname[8071:4529543] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Appname.ViewController rspGetCategories:]: unrecognized selector sent to instance 0x10380be00'
*** First throw call stack:
(0x18418ed8c 0x1833485ec 0x18419c098 0x18e27edb0 0x1841945c8 0x18407a41c 0x1020f5dfc 0x1020ebc3c 0x102f811dc 0x102f8119c 0x102f85d2c 0x184137070 0x184134bc8 0x184054da8 0x186039020 0x18e071758 0x1020fec34 0x183ae5fc0)
libc++abi.dylib: terminating with uncaught exception of type NSException

As the selector method should be representable by Objective-C so with #objc, we can not pass tuple as parameter for a method otherwise compiler will throw this error,
Method cannot be marked #objc because the type of the parameter cannot
be represented in Objective-C
One possible solution is as below,
#objc func rspGetCategories(_ response: [String: Any]) {
print("Tag: \(response["tag"] as! Int) Status:\(response["status"] as! String)")
}
And construct the response as below,
let selector = #selector(rspGetCategories(_:))
let tag = 1
let response: [String: Any] = ["tag": tag,
"status": "ServerError"]
self.perform(selector, with: response)
Another solution is to pass two parameters instead of tuple. As below,
#objc func rspGetCategories(_ tag: NSNumber, response: [String: Any]) {
print("\(tag.intValue) \(response)")
}
let selector = #selector(rspGetCategories(_:response:))
let tag = 1
let response = ["status": "ServerError"]
self.perform(selector, with: tag, with: response)
Remember that selector method argument type needs to be a reference type.

Split your tuple into two parameters like
#objc func rspGetCategories(_ response: Int, dict: [String: Any]) {
and change the selector
let act = #selector(Test.rspGetCategories(_:dict:))
Test is the name of my class, replace it with your class name

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 "?").

Snapshot.value crashed on setValueForKeys

func observeMessages(){
let ref = Database.database().reference().child("messages")
ref.observe(.childAdded, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject]{
let message = Message()
print(dictionary)
message.setValuesForKeys(dictionary)
self.messages.append(message)
DispatchQueue.main.async { self.tableView.reloadData() }
}
}, withCancel: nil)
}
This is part of my Firebase&swift project. I keep crashing every time I try to call the message.setValuesForKeys(dictionary).
The error message from the console is
Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key toId
I checked the dictionary and it has the data as I want. I don't know what else I can check. I tried to change the "snapshot.value" to "snapshot.children.allobjects" but with that change I cannot access to the data inside my dictionary.
My first guess is one of your firebase nodes has a key that doesn't exist in your class
Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key toId
^^^^
This is a good clue.
Check through the nodes in your firebase and ensure they all have a key toId and that your class also has a toId property.
Mismatching keys will cause this issue.
Also, if you want to use setObjectForValue, the Message object should inherit from NSObject (which is key-value coding compliant)
Objects typically adopt key-value coding when they inherit from
NSObject (directly or indirectly), which both adopts the
NSKeyValueCoding protocol and provides a default implementation for
the essential methods
If you've checked to ensure your keys all match up then it could be how the Message object is defined. It should look something like this, ensuring it's an NSObject and the keys (properties) start with #objc
class Message: NSObject {
#objc var name = ""
#objc var toId = ""
}
My last suggestion is make your code Swifty and don't rely on NSObject.
class Message {
var name = ""
var toId = ""
func initMessageWithSnap(aSnapshot: Snapshot) {
//desconstuct the snapshot and assign the vars
}
}

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?
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.

Realm Swift crashing with uncaught exception when trying to get object

I am using RealmSwift in my MacOS/OSX application on OSX 10.12.3 and Realm crashes with uncaught exception when I try to get an object from the database.
Here's the code snippet that crashes on the get object func:
private var database: Realm!
init(some_var: String) {
var configuration = Realm.Configuration()
configuration.fileURL = configuration.fileURL!.deletingLastPathComponent().appendingPathComponent("\(some_var).realm")
do {
debugPrint("Inside init: Current thread \(Thread.current)")
self.database = try Realm(configuration: configuration)
} catch {
debugPrint("realmInit: Can't create realm database.")
}
}
func getObject<T: Object, K>(with primaryKey: K) -> T? {
debugPrint("Inside getObject: Current thread \(Thread.current)")
return self.database.object(ofType: T.self, forPrimaryKey: primaryKey) // THIS LINE THROWS EXCEPTION
}
I get a crash with an uncaught exception:
"Inside init: Current thread <NSThread: 0x600000075700>{number = 5, name = (null)}"
"Inside getObject: Current thread <NSThread: 0x600000075700>{number = 5, name = (null)}"
libc++abi.dylib: terminating with uncaught exception of type NSException
At first I thought it was a threading issue, but you can see that I init the Realm and getObject on the same thread.
Any help would be appreciated please?
Turns out the issue was with the return type being an optional generic.
After a conversation with one of the contributors to the Realm library on github, it sounded like it could potentially be a RealmSwift bug.
Issue has been raised here https://github.com/realm/realm-cocoa/issues/4951
Workaround is to implement a function that returns a Bool if the object exists, and then return a non-optional generic when it does, while create a new object if it doesn't exist.