Mixing Swift and Objective-C in a framework and private headers

We're having an Objective-C framework that exposes a bunch of classes and also has private helper functions in categories. For example:

  • MyObject.h:
#import <Foundation/Foundation.h>
@interface MyObject : NSObject {
- (void)publicMethod; 
}
  • MyObject+Private.h:
#import "MyObject.h"
@interface MyObject (Private) {
- (void)internalOnlyMethod;
}
  • MyFramework.h:
#import <MyObject.h>

The private functionality is only used inside of the framework and we don't want to expose it publicly. Currently, we do that by having MyObject.h be a public header and inside the umbrella header for the framework and MyObject+Private.h be a private or project header. This works fine in Objective-C.

We're currently trying to add some swift to this framework: this works, except that our swift code doesn't have access to the internal/private functionality, since they are not exposed thru the umbrella header.

The common suggestion seems to be using a private module map, but this is very under-documented. We cannot get it to work without the swift compiler spitting our errors:

framework module MyFramework {
	umbrella header "MyFramework.h"

	export *
	module * { export * }

	explicit module Private {
		header "MyObject+Private.h"
	}
}

Unfortunately, the swift compiler complains about not being able to find the filer for #import "MyObject.h" .

Anyone got this to work? Are we doing some wrong?

What about bridging headers, if the Swift code is part of the framework?

I think the module map only plays a role when importing a framework. If your Swift code is importing MyFramework, then it is a consumer of MyFramework, and one needs to look at whether the Swift code should be using the private methods. If the Swift code is part of the framework, I think you need to use the bridging headers to make Objective-C code callable by Swift.

Bridging Headers are unsupported inside frameworks unfortunately. (The swift code is part of the framework).

I've been banging my head against this as well. We want to start adding Swift extensions to our Obj-C classes, but if any of our extensions require access to private functions, we can't call them.

There are other issues this creates too beyond Obj-C. If you have a private C (or eventually C++ function) you also can't use a bridging header to get at those functions. This means even if we rewrite entirely in Swift we're going to hit some core libraries we can't call.

I haven't gotten module maps to work either, and I'd be curious if anyone got them to work successfully. Most cases I've read online that got them working found that anything in the module map got added to the public framework interface anyway, defeating the purpose. The private module map just gets added as a dependency for the public one.

It's a pretty serious issue, especially if you want to start migrating a framework piecemeal to Swift or add functionality based on Swift only API like Combine.

We've also had some success declaring protocols with our private functions within our Swift code on AnyObject, and then casting objects to call into private functions (bleh) to call from Swift into Obj-C. To call from Obj-C into Swift, I just handwrite a private header representing the Swift API. While annoying, that seems to work without a lot of mess, as long as you keep the handwritten header and API in sync.

Hello,

Is there any update regarding this?. Anyone know how to make this work?.

We finally had success in Xcode 11 using:

  • add a custom MyFramework.modulemap
framework module MyFramework {
	umbrella header "MyFramework.h"
	export *

	explicit module Private {
           header "MyPrivateHeader.h"
           header "MyPrivateOtherHeader.h"
        }
}
  • Mark all private headers as "Private", instead of "Project" inside your framework
  • In your swift file, you can now import MyFramework.Private
  • To stop users of the framework from importing private stuff, we added a linter rule that errors on import MyFramework.Private while not being inside the framework project
  • Be aware that for this approach to work, you must import headers using < > brackets instead of quotes inside the (Obj-)C files in your framework. So, #import <MyFramework/MyPublicHeader.h> and #import <MyFramework/MyPrivateHeader.h>

We also tried this with Xcode 10, but that would either crash the compiler or result in a barrage of errors

Terms of Service

Privacy Policy

Cookie Policy