Unable to load module map in Swift Dynamic Framework

878 views Asked by At

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

enter image description here

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.

enter image description here

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!

1

There are 1 answers

0
Mecki On

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.