How to use NSSet<Class> in Swift (exported as Set<NSObject>)? - objective-c

I have to fulfill a protocol requirement that is defined in Objective-C like this:
#protocol AProtocol <NSObject>
+ (NSSet<Class> * _Nullable)someClasses;
#end
I want to implement this protocol in a subclass written in Swift. I want to return a Set of classes of another Object. The class I want to return is defined like this:
class B: NSObject {}
The class that conforms to the protocol is defined like this:
class A: NSObject, AProtocol {
static func someClasses() -> Set<NSObject>? {
return [B.self]
}
}
Why is NSSet<Class> bridged to Set<NSObject> instead of Set?
This solution is crashing, how can I solve the problem?

NSSet<Class> is bridged to Set<NSObject> because AnyClass does not conform to Hashable which is a precondition for the ValueType of Set.
It can be solved with the following extension for NSObjectProtocol:
extension NSObjectProtocol where Self: NSObject {
static var objcClass: NSObject {
return (self as AnyObject) as! NSObject
}
}
This returns the class of the object casted as NSObject. It is necessary to cast it first to AnyObject because the type system of Swift is so strong that it would not compile or give a warning when directly casting a type to an instance type. In Objective-C this is fine because Class is also just an object. Since NSObject is implemented in Objective-C and the extension is just for NSObjectProtocol, this is save to use (even with the force cast).
Implementing the extension on NSObjectProtocol and not on NSObject itself brings the positive effect that it is not exported to Objective-C.

Related

iOS11 Swift 4 - how to check if Swift class conforms to protocol defined in Objective-C?

I have a legacy code base with code written in Objective-C. I'm adding a new class written in Swift which has to conform to existing protocols defined in Objective-C.
How can I make sure my Swift class correctly implements methods defined in Objective-C protocol?
//In Obj-C
#protocol OBJCLocationObserver <NSObject>
- (void)didUpdateLocationWithModel:(nullable Model *)locationModel
lastLocation:(CLLocationCoordinate2D)lastLocation;
#end
//In Swift
extension SwiftLocationManager : OBJCLocationObserver
{
public func didUpdateLocation(with model: Model?, lastLocation: CLLocationCoordinate2D) {
// How to verify this function signature is actually conforming to the Obj-C protocol and is not a new method?
}
}
[MyClass conformsToProtocol:#protocol(MyProtocol)];
According to Apple Docs you can use conformsToProtocol:which returns a Boolean value that indicates whether the receiver conforms to a given protocol.
Example
#protocol MyProtocol
- (void)helloWorld;
#end
#interface MyClass : NSObject <MyProtocol>
#end
Will be exposed as:
console.log(MyClass.conformsToProtocol(MyProtocol));
var instance = MyClass.alloc().init();
console.log(instance.conformsToProtocol(MyProtocol))
Make sure you #import your protocol definition file into the <ProjectName>-Bridging-Header.h file:
#import "OBJCLocationObserver.h"
And then you should see error messages if your signature does not match.
You can also use Xcode Auto Completion. Type:
public func didUpdateLocation
and Auto Complete suggests:
public func didUpdateLocation(withModel Model?, lastLocation: CLLocationCoordinate2D)
which is different than what you have and explains why it isn't working.
Here is another way to get the interface:
As #MartinR suggested on a comment to another question:
Go to the header file where the protocol is defined, and choose
"Generated Interface" from the "Related Items" popup in the top-left
corner. That will show you the exact Swift method signature that you
have to implement.

objc_getClass: load swift class inheriting NSObject

I'm trying to dynamically load a Swift class inheriting from NSObject using the objc runtime. (I'm trying to load the class from ObjC, not from Swift)
My Swift class:
#objc public class TestClass : NSObject {
#objc public func testMethod() -> String {
return "String"
}
}
According to Apple's documentation,
The #objc attribute makes your Swift API available in Objective-C and the Objective-C runtime
But the result of objc_getClass("TestClass") is (null).
Am I doing something wrong? Or is it not possible at all to load swift classes inheriting an ObjC class using the objc runtime?
You need to specify an Objective-C name for your class, not just include #objc:
#objc(TestClass) public class TestClass : NSObject {
#objc public func testMethod() -> String {
return "String"
}
}
NSClassFromString("TestClass") // TestClass.Type
objc_getClass("TestClass") // TestClass
Otherwise your class will not be registered with the Objective-C runtime, and calls like objc_getClass or NSClassFromString will return nil.
What if you try objc_getClass("YourAppName.TestClass")? Most likely the module name is prepended. You can verify the exact name which is used behind the scenes by using NSStringFromClass([TestClass class])

Pass #protocol type in Swift

I have an Objective-C method which takes Protocol* type as parameter.
How can I invoke this method in Swift.
Example:
// In Objective-C
#protocol AProtocol <NSObject>
#end
#interface MyClass : NSObject
+ (id)proxyWithProtocol:(Protocol*)protocol;
#end
// I can call this method with a protocol as parameter
[MyClass proxyWithProtocol:#protocol(AProtocol)];
If I want to use MyClass in Swift by bridging. How can I pass a protocol defined in Objective-C to proxyWithProtocol method. Can I even pass a protocol defined in Swift to this method?
You would pass the Objective-C protocol in like so:
MyClass.proxyWithProtocol(AProtocol)
If you wanted to pass in a Swift protocol, you would have to expose that protocol to Objective-C:
#objc protocol MyProtocol {
func someGreatFunc()
}
// ...
MyClass.proxyWithProtocol(MyProtocol)
In Swift 3, depending on the way the class is bridged to Swift, your function might look like this:
MyClass.proxy(with: AProtocol)
MyClass.proxy(with: MyProtocol)
Although the compiler isn't happy with the location of "with" and may complain.

Swift equivalent of id<MyProtocol>?

The question is in the title. In Objective-C, if I want to have a property (like a delegate) that HAS to adhere to a certain protocol it can be defined like so:
#property (weak) id<MyDelegate> delegate;
How can I do this in Swift?
A protocol is a type, so you can use it as a declared variable type. To use weak, you must wrap the type as an Optional. So you would say:
weak var delegate : MyDelegate?
But in order for this to work, MyDelegate must be an #objc or class protocol, in order to guarantee that the adopter is a class (not a struct or enum, as they cannot be weak).
I think the exact oposite is:
weak var delegate: protocol<MyDelegate>?
I prefer this old, objc, style over the swift syntax because in swift first is the base class and then all the adopted protocols. This may be confusing in case your protocol does not have "Delegate" suffix as you won't know whether DataAdoption(for example) is super class or a protocol.
Use the protocol like a type, so:
weak var delegate:MyDelegate?
It is also good to know the equivalent for the Objective-C id<MyProtocolName> in the method declaration in Swift is protocol<MyProtocolName>. For Example:
// Objective-C
-void myMethodWithSome:(id <MyProtocolName>)param {
// ...
}
// Swift
func myMethodWithSome(param: protocol<MyProtocolName>) {
//...
}
Update for method declarations
Objective-C
-void myMethodWithSome:(id <MyProtocolName>)param {
// ...
}
Swift
func myMethodWithSome(param: MyProtocolName) {
//...
}

Implement Objective c protocol in Swift

I have this protocol in a objective c class:
#protocol YTManagerDelegate <NSObject>
#required
- (void)uploadProgressPercentage:(int)percentage;
#end
...
and a swift class connected to it:
class YTShare: UIViewController, YTManagerDelegate{
func uploadProgressPercentage(percentage:Int?){
println(percentage)
}
...
I receive the error: type YTShare does not conform to protocol YTShareDelegate, I have probably write incorrectly the swift function so the obj class don't find it. How I can write it correctly?
There are two errors in the delegate method
func uploadProgressPercentage(percentage:Int?){
println(percentage)
}
The parameter must not be an optional, and the C type int is mapped to Swift
as CInt (an alias for Int32):
func uploadProgressPercentage(percentage:CInt){
println(percentage)
}
Alternatively, you could use NSInteger in the Objective-C protocol, which is
mapped to Int in Swift. This would be a 32-bit or 64-bit integer, depending
on the architecture, whereas int/CInt is always 32-bit.