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?

1 Like

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).

1 Like

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.

1 Like

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

8 Likes

@DeskA With the setup that you mentioned private header classes are automatically being accessible even without import MyFramework.Private in the client project.

Is that what you are experiencing too?. I noticed that all the private headers being placed in the PrivateHeaders folder of the framework build but I am not sure those classes are accessible without an explicit import of MyFramework.Private.

Also when I follow the above instructions, I am getting error @import of module ‘MyFramework.Private’ in implementation of ‘’MyFramework”; use #import from MyFramework-Swift.h file, have you seen that error before?. Am I doing something wrong here?.

Is that in ObjC or Swift?

@DeskA I am not sure whether your question is about my first question or second but here is some more context,

  1. My framework's client project is in Swift, and in that client project all the private headers are being exposed even without the import MyFramework.Private. At this moment I am not worried about this issue. It would be nice to have a solution for this though.

  2. The error @import of module ‘MyFramework.Private’ in implementation of ‘’MyFramework” is from MyFramework-Swift.h (which is auto-generated by Xcode). I am using this file to use newly written swift code in existing objective-c within the framework itself. Basically due to the nature of our large framework project, we need to import the swift code into objective-c and objective-c code into swift and we are trying to gradually convert code over to swift.

    • For importing swift code into objective-c we are using this MyFramework-Swift.h
    • For importing objective-c into swift I am trying to use the private submodule approach that you mentioned and running into this error in the following sequence.
      • I think the compiler is trying to compile an objective-c file which has in import like #import <MyFramework/MyFramework-Swift.h>
      • And in that auto generated file it has @import MyFramework.Private, which is causing this error.

Here is a stack overflow question describing this exact problem. Unfortunately the accepted answer in there suggests avoiding private modules and making every objective c file public.

Is there a chance you are importing private headers in one of the public header files in your ObjC framework?

hey @DeskA! This is great. Do you have any tips on how to set up the Linter rule? That's the only part I'm kind of stuck on.

Thanks so much!

We simply have a regex for import OurFramework.Private and friends :)

A working github project is here GitHub - danieleggert/mixed-swift-objc-framework: Shows how to mix Swift and Objective-C code inside a framework target that allows import of private headers