Invoking a static swift function of an extended class from ObjC++ [duplicate] - swift

This question already has answers here:
Objective-C Category Causing unrecognized selector
(8 answers)
Closed 5 months ago.
I have a swift class containing a lot of methods. I've split it into multiple classes using the extension keyword.
Taking an example,
#objc
class SwiftModule : NSObject {
#objc
static func Method1 (_ pArg:UnsafeMutablePointer<NSString>) -> Void {
// Does something
}
#objc
static func Method2 (_ pArg:UnsafeMutablePointer<NSString>) -> Void {
// Does something
}
}
#objc
extension SwiftModule {
#objc
static func Method3 (_ pArg:UnsafeMutablePointer<NSString>) -> Void {
// Does something
}
}
Now, I have to invoke this method from C++ using the ObjC++ wrapper. I have the following in a .mm file,
// Include the generated swift interface header to invoke swift methods from C++
#include "GeneratedSwiftInterfaceHeader.hpp"
bool CppModule::InvokeMethod1 () {
NSString * string;
[SwiftModule Method1:&string];
if ([string length] == 0)
return false;
// Do something with string
return true;
}
bool CppModule::InvokeMethod2 () {
NSString * string;
[SwiftModule Method2:&string];
if ([string length] == 0)
return false;
// Do something with string
return true;
}
bool CppModule::InvokeMethod3 () {
NSString * string;
[SwiftModule Method3:&string];
if ([string length] == 0)
return false;
// Do something with string
return true;
}
I'm able to invoke Method1 and Method2. But Method3 failed with the following error
ibc++abi: terminating with uncaught exception of type NSException
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[SwiftModule Method3:]: unrecognized selector sent to class 0x107343398'
terminating with uncaught exception of type NSException
(lldb)
This means that Method3 is not recognised as part of SwiftModule class, right? Why? Where did I go wrong?
I have seen a few posts (this and this) which showcase a similar problem, but it still doesn't work.
I'm using swift 5.0 and Xcode 13.4.

When discussing the problem in details in the comments section, it turned out that the Objective-C interfaces generated were part of a static library which the final project is linked against.
There is a known issue with Objective-C categories inside of static libraries, where the categories methods implementation are not "glued" to their symbolic names by linker. In order to get it round you pass -ObjC flag to the linker in the application (not library) target build settings. It will make the linker to load all symbols of static libraries found in both Objective-C classes and categories:

Related

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.

Xcode project keeps crashing after converting to Swift 4, "Class implemented in both..."

I recently converted an Xcode project to Swift 4. I fixed all the syntax errors. However, my app now crashes, after outputting an error message which starts
Class MPExportableArtworkProperties is implemented in both...
I have been looking around S/O but all I find is that its related to MapBox which I don't even have in my project/podfile. Can it have something to do with the regular map-kit? It crashes the app when I open the tab with my map, so I'm guessing that's where the problem is.
Full error message, formatted for readability:
objc[24634]: Class MPExportableArtworkProperties is implemented in both
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/MediaPlaybackCore.framework/MediaPlaybackCore
(0x126b1b108) and
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/MediaPlayer.framework/MediaPlayer
(0x1258107d0).
One of the two will be used. Which one is undefined.
This is the plowerthingscontroller saying that you are logged in
libc++abi.dylib: terminating with uncaught exception of type
NSException
I had this exact error, and in the end found it was due to something completely different: a circular dependency. Consider:
class DependencyManager {
static let shared = DependencyManager()
let aDependency: SomeDependency
init() {
aDependency = SomeDependency()
}
func resolve() -> SomeProtocol {
// Create the actual class
return 0
}
}
where SomeDependency requires SomeProtocol
class SomeDependency {
let x: SomeProtocol
// Me trying to be clever and auto-inject the dependency
init(x: SomeProtocol = DependencyManager.shared.resolve()) {
self.x = x
}
}
So when you first access DependencyManager.shared it tries instantiate SomeDependency, which requires DependencyManager.shared to already be instantiated.
I have no idea why MPExportableArtworkProperties is mentioned in the error, however cleaning up the code fixed the issue for me.

XCSourceEditorCommandInvocation swift extension causes "unrecognized selector sent to instance" exception

When I try and add swift extensions to classes in XcodeKit (framework for adding Xcode extensions) the compiler is happy to build without any errors, but when the code runs I get the following exception:
-[XCSourceEditorCommandInvocation test]: unrecognized selector sent to instance 0x7fc60543f2b0
Below is sample code which can reproduce the exception:
class SourceEditorCommand: NSObject, XCSourceEditorCommand {
func perform(with invocation: XCSourceEditorCommandInvocation, completionHandler: #escaping (Error?) -> Void ) -> Void {
// Call extension method
invocation.test() // <--- Exception thrown here
completionHandler(nil)
}
}
extension XCSourceEditorCommandInvocation {
func test() {
print("it works!")
}
}
I've extended ObjC classes in swift in the past without any issues so I'm a bit stuck here.
I've tried:
adding #objc before the method declaration.
adding public to the extension and method.
I'm not extending a class cluster so it's likely not this question.
I'm not extending a protocol so it's likely not this question.
Neither Objective-C categories nor Swift extensions are supported on the classes or types in XcodeKit.framework at this time.

hash() and description() not allowed in Xcode 6.3 for NSObject

In my Swift library EVCloudKitDao I do a lot with reflection. Because of that I have set my base class of my data objects to NSObject. Now after the upgrade to Xcode 6.3 I get an error on the 2 functions for getting the hash and the description of the object. The description function was a nice to have, but i do need the hash to make my objects working with a Set.
Here is the code that I have
public class EVCloudKitDataObject : NSObject, NSCoding, Printable, Hashable, Equatable {
public func hash() -> Int {
return self.hashValue
}
public func description() -> String {
return EVReflection.description(self)
}
}
The errors that I get is:
/Users/evermeer/Desktop/dev/GitHub/EVCloudKitDao/AppMessage/AppMessage/CloudKit/EVCloudKitDataObject.swift:106:17:
Method 'hash()' with Objective-C selector 'hash' conflicts with getter
for 'hash' from superclass 'NSObject' with the same Objective-C
selector
/Users/evermeer/Desktop/dev/GitHub/EVCloudKitDao/AppMessage/AppMessage/CloudKit/EVCloudKitDataObject.swift:86:17:
Method 'description()' with Objective-C selector 'description'
conflicts with getter for 'description' from superclass 'NSObject'
with the same Objective-C selector
Does anyone know how I could solve this?
You can not use override.
As the error says, in both cases there's a naming conflict between a property and a method. The most obvious way to fix is by turning your 2 methods into properties:
public override var hash: Int {
return self.hashValue
}
public override var description: String {
return EVReflection.description(self)
}
which can also be written as:
public override var hash:Int {
get {
return self.hashValue
}
}
public override var description : String {
get {
return EVReflection.description(self)
}
}
The reason why it worked in the previous version is most likely because of this:
Swift now detects discrepancies between overloading and overriding in the Swift type system and the effective behavior seen via the Objective-C runtime.
Read more in the release notes (search for 18391046 and 18383574)

Call a method from a String in Swift

Calling a method from its name (in a String format) can be sometimes useful.
In Swift it is recomended to change behavior and to use closures to do something "dynamically", so for example you can have a dictionary of functions, with the name as the key, and the implementation as the value.
However, sometimes you want to simply know "how to do it", and this is the reason of this question.
So, how to call dynamically a Swift method starting from it's name as string?
In Objective C it was simple:
[self performSelector:NSSelectorFromString(#"aSelector")];
But performSelector is banned in Swift
Is there any alternative?
In Swift, you should use closures and change your approach.
However, if you want to use performSelector to dynamically call a method given only it's String signature, altough it's not supported natively, I've found how to do it.
It is possible to create a C alternative to performSelector that:
works even on native swift classes (non objective-c)
takes a selector from string
However it's not so straightforward to implement a complete version of it, and it's necessary to create the method in C.
in C we have dlsym(), a function that returns a pointer to a function given the char symbol.
Well, reading this interesting post:
http://www.eswick.com/2014/06/inside-swift/
I've learned a lot of interesting things about swift.
Swift instance methods are plain functions with a specific signature, like this
_TFC14FirstSwiftTest12ASampleClass13aTestFunctionfS0_FT_CSo8NSString
where the "self" value is passed as the last parameter
in short you can call it directly from the c side without any kind of bridging, it is sufficient to rebuild the correct function signature.
In the signature above, there is the name of the project (FirstSwiftTest) and the lenght (14), the name of the class (ASampleClass) and the lenght (12), the name of the function (aTestFunction) and the lenght (13), then other values as the return type ecc ecc. For other details look at the previous link
The function above, is the representation of this:
class ASampleClass
{
func aTestFunction() -> NSString
{
println("called correctly")
return NSString(string: "test")
}
}
Well, on the c side, I was able to create this function
#include <stdio.h>
#include <dlfcn.h>
typedef struct objc_object *id;
id _performMethod(id stringMethod, id onObject)
{
// ...
// here the code (to be created) to translate stringMethod in _TFC14FirstSwiftTest12ASampleClass13aTestFunctionfS0_FT_CSo8NSString
// ...
id (*functionImplementation)(id);
*(void **) (&functionImplementation) = dlsym(RTLD_DEFAULT, "_TFC14FirstSwiftTest12ASampleClass13aTestFunctionfS0_FT_CSo8NSString");
char *error;
if ((error = dlerror()) != NULL) {
printf("Method not found \n");
} else {
return functionImplementation(onObject); // <--- call the function
}
return NULL
}
And then called it on the swift side
let sampleClassInstance = ASampleClass()
println(_performMethod("aTestFunction", sampleClassInstance))
The function resulted in these statement printed on the log:
called correctly
test
So it should be not so difficult to create a _performMethod() alternative in C that:
creates automatically the function signature (since it seems to have a logic :-)
manages different return value types and parameters
EDIT
In Swift 2 (and maybe in Beta3, I didn't try) It seems that performSelector() is permitted (and you can call it only on NSObject subclasses). Examining the binary, It seems that now Swift creates static functions that can be specifically called by performSelector.
I created this class
class TestClass: NSObject {
func test() -> Void {
print("Hello");
}
}
let test = TestClass()
let aSel : Selector = NSSelectorFromString("test")
test.performSelector(aSel)
and now in the binary I find
000000010026d830 t __TToFC7Perform9TestClass4testfT_T_
At this time, I don't understand well the reasons behind this, but I'll investigate further
You could call a method from a String this way:
let foo = <some NSObject subclass instance>
let selectorName = "name"
foo.perform(Selector(selectorName))
It is available only when your foo class is subclass of NSObject
swift3 version
class MyClass:NSObject
{
required public override init() { print("Hi!") }
public func test(){
print("This is Test")
}
public class func static_test(){
print("This is Static Test")
}
}
if let c: NSObject.Type = NSClassFromString("TestPerformSelector.MyClass") as? NSObject.Type{
let c_tmp = c.init()
c_tmp.perform(Selector("test"))
c.perform(Selector("static_test"))
}