If a Swift extension is used to add overloaded methods to an Objective-C class, it appears to only call the first method, producing unexpected behaviour or a crash at runtime. An Extension on a Swift class (as opposed to Objective-C) works correctly. I would like to establish if there's any workarounds to this and confirm that this is a bug that I should report to Apple. Given that Objective-C does not support overloaded methods while Swift does, I can imagine that mixing the two is a recipe for problems.
The classes getting the extension are both empty except for the Objective-C class having an init function. ObjClass is declared within .h and .m files and imported using the Swift bridging header. The original SwiftClass is defined within the code sample along with the Swift extensions:
extension ObjClass {
func log(param: Int32) { NSLog("ObjClass->Int32: " + String(param)) }
func log(param: String) { NSLog("ObjClass->String: " + param) }
func log(param: ObjClass) { NSLog("ObjClass->ObjClass") }
}
class SwiftClass {
}
extension SwiftClass {
func log(param: Int32) { NSLog("SwiftClass->Int32: " + String(param)) }
func log(param: String) { NSLog("SwiftClass->String: " + param) }
func log(param: ObjClass) { NSLog("SwiftClass->ObjClass") }
}
Now call the overloaded methods with a string, int and object:
var objClass = ObjClass()
objClass.log(10)
objClass.log("string")
objClass.log(objClass)
var swiftClass = SwiftClass()
swiftClass.log(10)
swiftClass.log("string")
swiftClass.log(objClass)
For ObjClass, all the method calls are made to the first method with type Int32. Through use of COpaquePointer, I can see this is related to the object pointer but slightly different.
ObjClass->Int32: 10
ObjClass->Int32: 1881422800
ObjClass->Int32: 1879122512
Swift Extension on a Swift class works as expected:
SwiftClass->Int32: 10
SwiftClass->String: string
SwiftClass->ObjClass
Hopefully this can be supported at some point. Until then, I believe it should fail at compile time rather than give unexpected behaviour or a crash. Does anyone have a workaround to suggest for this?
I know it's calling the first method as if I swap the ObjClass extension methods, like so:
extension ObjClass {
func log(param: String) { NSLog("ObjClass->String: " + param) }
func log(param: Int32) { NSLog("ObjClass->Int32: " + String(param)) }
func log(param: ObjClass) { NSLog("ObjClass->ObjClass") }
}
Then the call to objClass.log(str) works fine but the call to objClass.log(10) produces:
main(1): EXC_BAD_ACCESS (code=1, address=0xa)
ObjC doesn't support method overloading. When you extend an ObjC class, the interface to your code is the ObjC runtime—even if you're calling your Swift code in that class from other Swift code—so your class' interface can't use features of Swift that aren't also present in ObjC.
As you've noted, this should probably be a compile error. I'd recommend filing a bug about that, especially since you've already put together a handy test case.
Related
I have a class that needs to pass a Protocol to an Obj-C function. I have a constructor that takes the Protocol, but as the class is a generic that also takes the same protocol, I thought I could optimise it. However, if I try to use the generic value when calling the function, it fails to compile. I've tried various combinations of ".self" and ".Type" and ".Protocol", both in the code and in the generic argument, and nothing works. Is there any way to achieve this?
This is a Playground project to show the problem.
import Foundation
#objc protocol TestProtocol {
}
class Test<P> {
init() {
test(p: P.self) // Fails to compile with: Cannot convert value of type 'P.Type' to expected argument type 'Protocol'
test(p: TestProtocol.self) // Compiles
}
func test(p: Protocol) {
}
}
let c = Test<TestProtocol>()
I want to integrate a Swift class into an UIViewController class.
I think I made all settings correct. I have a class rURLTask:NSObject with a function:
#objc public func primer() {
print("primer")
}
In my .swift file and can call it from my Objective-C-class with:
[URLTask primer];
and it prints nicely.
Another function is:
#objc public func ladeURL(url: URL?) {
print("loadURL")
}
but this one I cannot call from Objective-C. I try to write:
NSURL* testURL = [NSURL URLWithString:#"http://www.google.com"];
[URLTask ladeURL:testURL];
I get the error:
No visible #interface for 'rURLTask' declares the selector 'ladeURL:'
I think there is a very basic mistake. Using Objective-C in Swift 3 in another project worked well.
The reason you cannot call
#objc public func ladeURL(url: URL?) {
print("loadURL")
}
by saying
[URLTask ladeURL:testURL];
is that ladeURL: is not the name of this method as far as Objective-C is concerned. That is what the compiler means when it says that "No visible #interface for 'rURLTask' declares the selector ladeURL:".
Its name as far as Objective-C is concerned is ladeURLWithUrl:. That is because you have exposed the url: parameter name as an external label.
If it was important to you to be able to say
[URLTask ladeURL:testURL];
in Objective-C, you could have declared ladeURL like this:
#objc public func ladeURL(_ url: URL?) {
print("loadURL")
}
See the underscore? That hides the external label of the parameter.
Another solution, allowing you to keep the url: external label, would be to declare the Objective-C name as part of the objc attribution:
#objc(ladeURL:) public func ladeURL(url: URL?) {
print("loadURL")
}
That says to the compiler: "I know you would like to translate the name of this method into Objective-C as ladeURLwithUrl:, but don't; translate it as ladeURL: instead."
When you import the class to OC , the name of the method written in swift is translated concatenated with the withParameterType , as prime
method
#objc public func primer() {
print("primer")
}
has no parameters it can be called like this
[URLTask primer];
but this
#objc public func ladeURL(url: URL?) {
print("loadURL")
}
is translated to
[URLTask ladeURLWithUrl:<#NSURL#>];
I am creating a swift framework. In that one class is like this as shown below.
import Foundation
#objc public class classA: NSObject {
public override init (){
super.init();
}
/**
Singleton intance is returned.
*/
public class var sharedInstance: classA {
struct Static {
static let instance = popeye();
}
return Static.instance
}
}
Now when i add this framework into a Objective c project and try to access "sharedInstance" i get this error.
Property 'sharedInstance' not found on object of type ClassA.
Fix it Replace 'sharedInstance' with 'sharedInstance'
But even if i try use Fix it, this issue isnt solved.
NOTE: This issue doesn't happen when i integrate this framework with a swift project!!!
I AM STUCK.. :(
I tried to reproduce your problem. At first the syntax highlighter in Xcode flagged the same error in Objective-C that you mentioned, but the code actually was built and ran fine.
However, there is a cleaner way of doing this. In your code you are using a computed type property, which is evaluated every time you access it! You work around this by introducing the struct Static, where you essentially do what could be done in classA itself, like this:
/**
Singleton intance is returned.
*/
public static var sharedInstance: classA = popeye()
Here we used a stored type property, which is a recommended way to implement singletons, see here:
https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/AdoptingCocoaDesignPatterns.html
And here is some documentation on different kinds of properties:
https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Properties.html
Finally i was able to fix this with a minor change !! :)
Swift framework code
#objc class SingletonTest: NSObject {
// swiftSharedInstance is not accessible from ObjC
class var swiftSharedInstance: SingletonTest {
struct Singleton {
static let instance = SingletonTest()
}
return Singleton.instance
}
// the sharedInstance class method can be reached from ObjC
class func sharedInstance() -> SingletonTest {
return SingletonTest.swiftSharedInstance
}
// Some testing
func testTheSingleton() -> String {
return "Hello World"
}
}
Objective C parent project code
SingletonTest *aTest = [SingletonTest sharedInstance];
NSLog(#"Singleton says: %#", [aTest testTheSingleton]);
When trying to use the Menu class below from my Objective-C code, I'm only able to access foobar (or any other function that does not have the Selector type in the argument list), while the addItemWithName function is unavailable (I have checked the autogenerated swift to objective-c file and the function is not listed in the Menu interface there). How can I make this work?
public class Menu : UIView {
public func foobar() {
// implementation
}
public func addItemWithName(itemName: String, target: AnyObject?, action: Selector?) {
// implementation
}
}
The problem type isn't Selector, it's Selector?. You can't express Optional<Selector> in ObjC because SEL is not an object type.
Using PromiseKit 2.0 with Swift 1.2, I'm trying to use a PMKPromise that was created in Objective C from Swift.
Objective C code:
#interface FooTest : NSObject
+ (PMKPromise *)promise;
#end
Swift code (I've tried a number of variations, none of which work. This one is closest to the example given at http://promisekit.org/PromiseKit-2.0-Released/):
FooTest.promise().then { (obj: AnyObject?) in
self.obj = obj
}
Compiler error: Cannot invoke 'then' with an argument list of type '((AnyObject?) -> _)'
This doesn't work either:
FooTest.promise().then { (obj: AnyObject?) -> AnyPromise in
return AnyPromise()
}
Similar error: "Cannot invoke 'then' with an argument list of type '((AnyObject?) -> AnyPromise)'"
There are two different promise classes in PromiseKit, one for Swift (Promise<T>) and one for ObjC (AnyPromise). The Swift ones are generic and Objective-C cannot see generic classes, so this is why there are two.
If Foo.promise() is meant to be used in both ObjC and Swift then you are doing the right thing. If however you only intend to use this promise in Swift then I suggest rewriting it as a Promise<T>.
To use an Objective-C AnyPromise (PMKPromise is a deprecated alias for AnyPromise: prefer AnyPromise) in Swift code you must splice it into a an existing chain.
someSwiftPromise().then { _ -> AnyPromise in
return someAnyPromise()
}.then { (obj: AnyObject?) -> Void in
//…
}
There should be a way to start from an AnyPromise, probably I will add this later today:
someAnyPromise().then { (obj: AnyObject?) -> Void in
//…
}
Expect a 2.1 update. [edit: 2.1 pushed with the above then added]