Run method on class knowing only its name - swift

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

Related

Cannot Dynamically Set Enum in Swift with Reflection

I am attempting to dynamically clone a class object in Swift using reflection and the setValue function. This class contains an enum property that is backed with an Int, which is causing this dynamic reflection to crash:
#objc enum Status : Int {
case green
case yellow
case red
}
#objc class State : NSObject {
#objc var status : Status
init(_ status: Status) {
self.status = status
}
}
func testReflectiveClone() {
let state1 = State(.green)
let state2 = State(.yellow)
let state1Mirror = Mirror(reflecting: state1)
for property in state1Mirror.children.enumerated() {
let label = property.element.label
state2.setValue(property.element.value, forKey: label!) //crashes here
}
}
This test function is throwing the following error in XCode:
-[__SwiftValue longLongValue]: unrecognized selector sent to instance 0x600001e5a340 (NSInvalidArgumentException)
Is it even possible to dynamically set enum values? What modification would I need to make to get this to work?
You can add rawValue to avoid the runtime error.
state2.setValue(property.element.value.rawValue, forKey: label!)
Please check the definition of setValue.
open func setValue(_ value: Any?, forKey key: String)
The enum value you passed will be transferred to internal class named "__SwiftValue", the compiler found that "status" was an Int enum type and tried to convert the value to Swift.Int with "longlongvalue", so the runtime error happened.

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

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

uncaught exception 'NSInvalidArgumentException', reason: '-[SubResultComponent setDate:]: unrecognized selector sent to instance 0x60000046b640'

I have an error to which I am not sure on how to fix.
Here is there error
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[SubResultComponent setDate:]: unrecognized selector sent to instance 0x60000046b640'
Here is the SubResultComponent
extension SubResultComponent {
#nonobjc public class func fetchRequest() -> NSFetchRequest<SubResultComponent> {
return NSFetchRequest<SubResultComponent>(entityName: "SubResultComponent")
}
#NSManaged public var date: String?
#NSManaged public var price: NSDecimalNumber?
}
This is where I Call set date
func updateUI() {
guard rateLog != nil else {
return
}
// Update cell UI
self.dateLabel.text = rateLog?.date
self.priceLabel.text = numberFormatter.string(from: (rateLog?.price)!)
}
#NSManaged acts similar to the objective c version of dynamic; it is a promise to the compiler that the object will have those properties at runtime. It does not actually make those properties. For this to work these properties need to be setup correctly in the core data model file. Using the editor's "Create NSManagedObject Subclass..." option greatly helps in keeping you code and the model in sync.

RLMException: Object type does not match RLMArray type

I have a simple object:
class MyObject : Object {
dynamic var dummyField: String!;
}
and another object which inherits from MyObject:
class MyOtherObject : MyObject {
dynamic var anotherDummyField: String!;
}
Now I do the following. I have a Realm List of MyObject and I create an instance of MyOtherObject and try to save it inside the list:
class Operator {
internal var myObjects: List<MyObject>!;
internal var myObject: MyObject!;
func operate() {
self.myObject = MyOtherObject();
self.myObject.dummyField = "dummy field";
self.myObject.anotherDummyField = "another dummy field";
self.myObjects = List<MyObject>();
self.myObjects.append(myObject); // crash!
}
}
It crashes with the error:
Terminating app due to uncaught exception 'RLMException', reason: 'Object type 'MyOtherObject' does not match RLMArray type 'MyObject'.'
Since MyOtherObject is a subclass of MyObject I cannot understand why the app is crashing at this point with this error message.
Realm Lists are not covarient. You can only store objects of exactly the declared type in them.
self.myObjects = List<MyObject>();
This is not a proper value
self.myObjects = List<Needs actual object here not class>();

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