27
votes

I have an Objective-C framework (framework A) that exposes some public and some private headers. The public headers are also declared in the framework's umbrella header. I have a second Swift framework (framework B) that links with the Objective-C framework.

Now, if I want to import the public headers of A in B I simply need to do an import A.

But how do I go about importing the private headers?

I know a bridging header is not an option since that's not supported for frameworks. Do I need to somehow create a separate umbrella header for the private headers?

4

4 Answers

34
votes

You need to modify framework A, So that it export a private module.

  1. Create a private module map file in A project. This would be something like this:

    A/private.modulemap:

    explicit module A.Private {
    
        // Here is the list of your private headers.
        header "Private1.h"
        header "Private2.h"
    
        export *
    }
    
  2. In the "Build Settings" of framework A target, search "Private Module Map File" line, and make that:

    $(SRCROOT)/A/private.modulemap
    
  3. Do not include private.modulemap file in "Compile Sources". That causes unnecessary warnings.

  4. Clean and Build framework A target.

  5. In framework B Swift files. you can import the private module like this:

    import A
    import A.Private
    
7
votes

It is some time since this question was posted. The accepted answer is very good, and as far as I'm concerned it is a common approach.

The problem is, it is not really "private". You can do this inside your framework to access the "private" part:

// Framework A Swift file
import A.Private

But If you use framework A in an app (or you ship it to your client), he can still do:

// Client App Swift file
import A
import A.Private

// access "private" framework methods and classes

I was trying to resolve that, as I had recently a situation when I needed to hide it from users (closed source framework) - I just could not let anyone access it, as it was a threat to SDK integrity.

I found a solution to that problem, but a bit too complex to paste it here as a whole.

I made a post about it no medium. Maybe it will help someone checking that problem, that's the link to my article:

https://medium.com/@amichnia_31596/creating-swift-framework-with-private-objective-c-members-the-good-the-bad-and-the-ugly-4d726386644b

5
votes

As noticed by Andrzej Michnia in his answer the problem with "private module map" solution is that it is not really completely private and those "private" headers still can be seen by someone as they are still included in our framework. If someone opens compiled framework with such "private" module he will still see all .h files that you hidden.

If we need to hide some objective-c headers in our swift framework completely from other users then another possible method to do that will be just to make them public and remove them from our framework after building the framework manually or with a bash script.

You could create a separate header file for example "InternalHeaders.h" where you import all headers that you do not want to expose. Then import this InternalHeaders.h in public umbrella header of your framework. Make all headers public so that you can compile everything. After you build your framework simply remove "import InternalHeaders.h" from public umbrella header and remove all headers that you do not want to expose manually or with a bash script or in run script build phase and thats it.

Still not a perfect solution but in some cases it might be much easier and faster that writing protocols in swift to match every objective-c interface as proposed in other answer.

1
votes

My situation may be particular to my setup, but I'll offer it here, in case it helps someone else. I also have an Objective-C framework (framework A) with private headers that I need to use in a Swift framework (framework B) that links it. Some additional details:

  1. Each framework is in a separate project in the workspace

  2. The project uses CocoaPods

  3. The podspec defines the following dependence relationship between the two frameworks:

    s.subspec 'FrameworkA' do |cs|
        cs.vendored_frameworks = "lib/FrameworkA.framework"
    end
    
    s.subspec 'FrameworkB' do |ts|
        ts.dependency 'FrameworkA'
        ts.vendored_frameworks = "lib/FrameworkB.framework"
    end
    

The solution offered by @rintaro works great for me when running in Xcode, but once the Pod is deployed, FrameworkB is unable to find the private headers using the paths in the private modulemap that lives in FrameworkA. What worked for me was to use a relative path to the PrivateHeaders dir in the private modulemap:

module FrameworkA_Private {
    header "../FrameworkA.framework/PrivateHeaders/Private.h"
    export *
}

This works in Xcode and in the final product installed using CocoaPods. It's a little bit hacky, since it references a folder in the final build product, and I wouldn't be surprised if there's some other way to tell CocoaPods how to preserve these paths, but whatever it is, I haven't found it. This solves the problem for now.