1

I have a project that bridges Swift and Objective-C both ways, and:

I have a @objc protocol defined in a Swift file:

@objc protocol Plugin {
    @objc func onDevicePackageReceived(np: NetworkPackage) -> Bool
}

All the classes that implement this protocol are also defined in Swift, such as:

@objc class Ping : NSObject, Plugin {
    
    @objc func onDevicePackageReceived(np: NetworkPackage) -> Bool {
        if (np._Type == PACKAGE_TYPE_PING) {
            connectedDevicesViewModel.showPingAlert()
            return true
        }
        return false
    }
    
    @objc func sendPing(deviceId: String) -> Void {
        let np: NetworkPackage = NetworkPackage(type: PACKAGE_TYPE_PING)
        let device: Device = backgroundService._devices[deviceId] as! Device
        device.send(np, tag: Int(PACKAGE_TAG_PING))
    }
}

I've imported the automatically generated bridging header file into the Objective-C .m file that I would like to use the Plugin interface in:

#import "My_App_Name-Swift.h"

The bridging header is definitely working fine since other bridged Swift code are working.

I've also forward declared the protocol in the Objective-C .h header file:

@protocol Plugin;

Now, in the Objective-C .m files, I've got a NSMutableDictionary, _plugins with values that are objects of those classes defined in Swift that implement the Plugin protocol. I want to iterate through each of those objects and call the onDevicePackageReceived(np: NetworkPackage) function as defined in the protocol, and I plan on doing that by confirming the objects to the Plugin protocol which has that function implemented.

for (Plugin* plugin in [_plugins allValues]) {
    [plugin onDevicePackageReceived:np];
}

But it keeps throwing the error Use of undeclared identifier 'Plugin'.

I checked the automatically generated header file and it does appear to have bridged it correctly:

SWIFT_PROTOCOL("_TtP16KDE_Connect_Test6Plugin_")
@protocol Plugin
- (BOOL)onDevicePackageReceivedWithNp:(NetworkPackage * _Nonnull)np        SWIFT_WARN_UNUSED_RESULT;
@end

For further reference, the Dictionary plugins used to be declared in Swift, where the same for loop in Swift works just fine:

for plugin in plugins.values {
    (plugin as! Plugin).onDevicePackageReceived(np: np)
}

So essentially, plugins has now be moved to Objective-C, and I'm trying to replicate this exact for loop in Object-C, with the only difference being that the protocol Plugin and all of the classes that implement it are all declared in Swift, so they need to be properly imported to Objective-C.

Lucas W
  • 125
  • 1
  • 10
  • Protocols from swift need to be explicit derived from NSObject. At least in projects i tried same it helped me out. Protocols without are of type Anyobject. Which is similar but not same as id. – Ol Sen Sep 05 '21 at 01:23

2 Answers2

1

This syntax isn't correct for protocols in ObjC:

for (Plugin* plugin in [_plugins allValues]) {

There are no pointers to protocol existentials. ObjC doesn't have existential types like Swift (i.e. a box that contains a type that conforms to a protocol).

I believe you mean:

for (id<Plugin> plugin in [_plugins allValues]) {

This is "an arbitrary ObjC object (id) that conforms to Plugin."

See Working with Protocols in Programming with Objective-C for details on ObjC protocol syntax.

See Protocols as Types in The Swift Programming Language for details on Swift protocol existentials (which are not the same thing as the protocol itself).

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • "ObjC doesn't have existential types like Swift (i.e. a box that contains a type that conforms to a protocol)." Just to check my understanding, that's because only Objective C classes can conform to protocols, and so there's no need to wrap values in existential containers to store their type metadata. All ObjC objects point to their class, which has their conformance information. Is that right? – Alexander Sep 04 '21 at 22:05
  • 1
    @Alexander Partially, though there are a lot of related reasons, most of which are "ObjC has dynamic dispatch." Yes, protocol conformance is on class types, but that doesn't really matter. I can always stick a non-Plugin into an `NSArray` (ObjC protocols are "suggestions"). And if I never call a Plugin method, it won't matter. And even if I *do* call a Plugin method, the object can respond to it anyway with dynamic method resolution and then it's still legal. In Swift, protocols create structural requirements for static dispatch. In ObjC, they're just "I promise to respond at runtime." – Rob Napier Sep 05 '21 at 15:16
  • It's also because all ObjC objects are the same "size." They're implemented as a pointer (possibly tagged, but always a pointer). Conforming types in Swift may be arbitrary sizes.So you wouldn't know how much space to reserve for them. Consider an array of "things that conform to P". What's the stride? In ObjC it's *always* the word size (not to be confused with NSArray; I just mean things like a sequence of objects on the stack). In Swift, a custom struct that contains just a UInt8 only requires one byte of memory. And structs larger than a word may still be on the stack. – Rob Napier Sep 05 '21 at 15:23
1

Another option if you don't want write objc code is create swift extension for your objc class.
For example if it's called LegacyPluginHolder, add new file LegacyPluginHolder+Extensions.swift and create extension:

extension LegacyPluginHolder {

    @objc func notifyAllPlugins(package: NetworkPackage) {
        // Your swift code moves here
    }
}

In LegacyPluginHolder.m you can use extension like this:

[self notifyAllPluginsWithPackage:np]
h8wait
  • 21
  • 5