I have a Framework with mixed Objective-C and Swift classes and i’d like to communicate between them keeping the Swift interface internal (without making Swift classes and methods public). Is there a way to achieve that?
P.S. Please pay attention that since it’s a Framework project, it’s not an option to use the bridging header solution, because it’s not supported for frameworks.
3
Answers
A solution, although hacky, is that you could mark the private swift methods with an Objective-C decorator
@objc
, and then call them from Objective-C usingperformSelector
.If you don’t want to make your Objective-C headers public to use in Swift, you have to create a
module.map
file at the root of your project and add all your internal/private headers:Then, in your
SWIFT_INCLUDE_PATHS
build settings add$(SRCROOT)/FrameWorkProjectName
so that this module can be found by Swift classes.To use the Objective-C code of the said module you have to import
FrameWorkProjectName_Internal
in your Swift file.To use internal Swift classes in your Objective-C code, first you mark the classes/methods with
@objc
/@objcMembers
. Since you don’t want to make your Swift interface public you can either useperformSelector
to send messages OR you can manually create a header containing all your Swift interface definitions. For example, define type symbol name or selector name in your swift file with@objc
attribute:and then define the swift type interface in your header file:
When defining type symbol names make sure that it doesn’t conflict with other names in your or any other external module. To avoid this try to include your framework name in symbol name.
Be advised, that
SWIFT_CLASS
macro is generated as part of{TargetName}-Swift.h
header.Some Clarifications:
These are some clarification points answering some comments:
Yes for the framework it is called umbrella header, but you can achieve the same effect of bridging header in app targets, the only difference being the headers have to be public to be included here. And the side effect being the declarations in these headers would be exposed to consumers of your library.
While this point doesn’t address your question specifically, I put it as a workaround if you think the actual action is quite cumbersome and requires a lot of manual work for your time constraints.
Swift generated header is used to expose your Swift interfaces to Objective-C. They basically achieve the opposite of what bridging headers do. So in short,
Bridging header => Expose Objective-C declarations to Swift in app target.
Umbrella header => Expose Objective-C declarations to Swift in framework target and targets consuming the framework.
Swift generated header => Expose Swift interfaces to Objective-C (for frameworks declarations has to be public)
For more detail, you can go through official docs for importing objective-c into swift and importing swift into objective-c.
These also I put as workarounds if you think the actual action is quite cumbersome and requires a lot of manual work for your time constraints.
You are correct, unfortunately, I missed that. Thanks for adding this point.
There is actually not that much magic around Swift and Objective-C interoperability. First, let’s take a look at what the official documentation says:
The automagic part merely generates Objective-C interfaces for us. Yes, the generated part for
internal
interfaces happens only if we use the bridging header and Xcode doesn’t support bridging headers for framework targets. However the documentation explicitly says that internal interfaces are still accessible to Objective-C runtime (emphasis mine):It means, that while generated headers are
public
/open
only, nothing prevents you from writing the same interfaces manually and use them for internal classes.Consider the following swift class:
All parts of it are exposed to the Objective-C runtime somehow, it’s just unknown how exactly. Since nobody is going to generate this guidance we can just describe it ourselves. Here is a header file
TDWInternalClass.h
which has this description:The problem here is that when Objective-C classes out of Swift interfaces are generated, their names are mangled, so
TDWInternalClass
interface would have hard time finding theInternalClass
implementation. However it’s possible to change the runtime name for the Swift declaration by providing argument to@objc
attribute:As we deal with a Framework target, don’t forget to add this manually implemented header to the
Header
section of theBuild Phases
(I use theProject
section to avoid exposing this header to the client code):And we are done. The internal Swift class implementation is now accessible to project’s own Objective-C classes through the
TDWInternalClass
interface, and at the same time thisInternalClass
is not accessible by the client code of the framework: