I'm working on a Swift dynamic framework which has some Objective-C code in it. I need to use some legacy Objective-C code without exposing the code to it. I came to know that I can use a module map to avoid adding the headers as public in the framework. I have been using this link about how to create a module map. The module map I created looks something like this.
framework module Xyz {
umbrella header "Xyz.h"
export *
module * { export * }
}
framework module Module1{
header "Module1.h"
}
framework module Module2{
header "Module2.h"
}
.... Rest of the modules
After this, I tried importing the module in the Swift file, but I didn't get auto-complete. I wrote the module name anyways and tried to build it. I got an error in the Swift file saying "Unable to build Objective-C module". In the module map file, I was seeing the error as "Unable to find the header". This was the error
So I tried adding the path $(SRCROOT)/ProjectName/
to the Include Path
of the build settings. After which it was unable to find the umbrella header.
I scoured most of StackOverflow and Google, but I couldn't find anything that helped me. This is my first time working with module maps and I'm stuck here not knowing what to do further to resolve the issue. Would be great if someone can point out what I'm missing out or if you can suggest some other source which can help me out. Thanks!
If you don't want Objective-C symbols to be exposed from a library, all you need to do is passing the flag -fvisibility=hidden
to the compiler, then all non-static symbols are only available within the library itself but not added to the final symbol table when the library is getting linked and without adding them to the symbol table, they cannot be directly accessed from outside.
This setting even exists in Xcode, it's called Symbols Hidden by Default
and in xcconfig files it's named GCC_SYMBOLS_PRIVATE_EXTERN
(GCC as it's an old setting that already existed when Xcode still used GCC instead of Clang). It's a boolean value and when set to YES, Xcode passes -fvisibility=hidden
to the compiler.
If you then want to expose a single symbol, e.g. a specific class, you can do that using an attribute annotation:
__attribute__((visibility("default")))
@interface MyClass : NSObject
Now the symbol for MyClass
is added to the symbol table again.
Note that this has zero effect on Swift code. Whether Swift symbols are added to the symbol table depends on their access modifier. If they are public
or open
, they are added to the symbol table. The default for Swift symbols is module
and that's the same as tagging a symbol __attribute__((visibility("hidden")))
in Obj-C (which -fvisibility=hidden
does per default for all symbols unless otherwise specified). Finally there's private
and fileprivate
in Swift, which is the similar to static
in C, which does not even generate a symbol at all, so it's not even possible to reference it from outside the current translation unit.
Note that even without symbols, Objective-C classes can be accessed via the Objective-C runtime, as every Obj-C class must be registered with the runtime to be used as normal Obj-C class (module maps cannot prevent that either), so you can still do things like
[[NSClassFromString(@"MyClass") alloc] init];
Visibility won't prevent that from working, as you are not accessing the class by its symbol but by it's name as a string, however if you really don't need to ever access a class from outside the library, you can bypass the Objective-C runtime altogether.
__attribute__((objc_direct_members))
__attribute__((objc_subclassing_restricted))
@interface MyClass : NSObject
Now your class is not using the Objective-C runtime at all and all calls to methods are in fact direct calls, meaning they don't use the Obj-C runtime dispatch, they directly call the implementation like a C function call (which also makes method calls a lot faster). So even if someone would know the name of your class and the name of the methods and their signature, he could not call use any of that from outside.
Direct dispatch is something Apple has introduced quite a a while ago. E.g. if you don't use this attribute on a public interface
__attribute__((visibility("default")))
@interface ClassWithPublicInterface : NSObject
but you add this to the implementation
__attribute__((objc_direct_members))
@implementation ClassWithPublicInterface
the following happens:
All methods defined by the interface are publicly available and dispatched using the Obj-C runtime. However, all methods only declared in the implementation are private, directly dispatched and cannot be accessed from outside the class.
E.g.
__attribute__((objc_direct_members))
@implementation ClassWithPublicInterface
- (int)add:(int)a to:(int)b { return a + b; }
@end
Assuming that you did not declare the add:to:
method in the public header, this method in fact is exactly as if you had written
__attribute__((objc_direct_members))
@implementation ClassWithPublicInterface
static int add(int a, int b) { return a + b; }
@end
despite being a method and allowing you access to instance variables of the object. There is no way to access this implementation from outside and calling it is as fast as calling a static C method.
Note however, that direct dispatch methods cannot be overridden by subclasses. If you try that, you won't get an error message but your overridden method is simply never called. This prevents accidentally overriding methods of your parent class in Obj-C (as you can override methods not exposed in the header, so you don't even know that you are overriding a method) but it can also lead to subtle and hard to trace bugs.
That's why I added __attribute__((objc_subclassing_restricted))
to the sample above, which makes a class final
in Obj-C. Any attempt to subclass this class will directly result in a compiler error. Also knowing that this class is never subclassed allows for some extra compiler optimizations to be performed.
Last but not least, you can mark single properties and methods as direct:
@interface MixedClass : NSObject
@property int indirect;
@property(direct) int direct;
- (int)processDataIndirect:(NSData *)data;
- (int)processDataDirect:(NSData *)data __attribute__((objc_direct));
@end
Why would you want to do that? As I said above, a direct call is as fast as a C function call, an indirect one is several times slower, as it must perform a lookup into a method lookup table (which is a hashtable, keys are method names, values are method pointers) first, this is how Obj-C allows dynamic dispatches and overriding methods at runtime. The downside is, direct calls cannot be overridden and are not visible outside the current module (e.g. outside the current framework/library/binary).
By the way: Your calling code does not need to know or care if methods/properties are direct or not, your code looks the same in both cases, it's just that the compiler will generate different code for either case.