Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Objective C classes within an iOS Swift-based dynamic framework

Tags:

Situation:

I've got an iOS dyanmic framework written in Swift. I've also got a bunch of classes written in Objective C that I would to use within my Swift classes (some are public, some are private). However, I would like the Objective C classes to not be exposed to projects using my framework.


What I have tried:

Umbrella Header

From what I understand, I should import using #import header.h in my umbrella header file, which is usually FrameworkName.h, and then make sure all the Objective C header files that I wish to include in my Swift classes are to marked as "Public", under Build Phases -> Headers.

Doing this, however, automatically exposes the project using my framework to all the private Objective C classes that the framework uses.

Module Mapping (with separate module)

Because of this, I've looked into using module mapping, which is documented here. I've looked at posts by other users such as this and this, as well as this Github repo.

I successfully got the following working:

//SharedClasses/module.modulemap
module SharedClasses {
}

//SharedClasses/module.private.modulemap
module SharedClasses.Private {
    header "header.h"
    export *
}

The problem is that in my project (that has this framework imported), this:

import Framework
import Framework.SharedClasses

is allowed, and subsequently the "hidden" Objective C classes are exposed. Perhaps this is just how modules work? Is there a way to make them truly private?

Module Mapping (with framework private module)

Also, I've tried creating a module.private.modulemap file at the root of my framework with the following contents:

explicit module Framework.Private {
    header "header.h"
    export *
}

and then linking it my target's build settings under MODULEMAP_PRIVATE_FILE. However, when I do import Framework.Private in my framework's Swift classes a compiler error is thrown:

"No such module 'Framework.Private'

I don't understand why this error is occurring.

Module Mapping (with private header)

I noticed that in the Clang docs, a private specifier is mentioned:

A header with the private specifier may not be included from outside the module itself.

My understanding is that since all the Swift classes in my framework are already part of the module Framework, if I create a module.modulemap file with the following:

framework module Framework {
    umbrella header "Framework.h"

    private header "header.h"

    export *
    module * { export * }
}

then everything should be working! The Objective C headers are only accessible within the module (i.e. the framework's Swift classes), and aren't exposed to any project using the framework. Cool, but it doesn't work...the compiler just doesn't recognise the Objective C classes. No other errors are thrown, but you can't use the headers, so it's like you didn't include the headers in the first place. WHY? And what is the private specifier for then?


Soo, reiterating my original question:

Is there any way to import Objective C headers for use in Swift classes, within an iOS dynamic framework, while keeping them private and not accessible from any project using said framework?

Thanks for reading, and sorry for the long post. It's been a long day (and night)...

like image 391
Jason Avatar asked Feb 21 '16 12:02

Jason


2 Answers

The quick answer is you can't :) Even if you declare "private" modulemap, it can be always imported by your framework users. Please note, that usually, it is not a concern, especially with open source. You just say "this is an internal module, don't use it".

But (there is always but) - you can have behavior, that effectively works the same - allows you to use your Objective-C classes withing same framework target, without making them public. It works in closed source setup - I have a dynamic framework shipped to external parties, that has a purpose of revealing as less about its construction as possible.

The case a bit too complex to paste everything here. I'm adding a link to my article about the topic. That's a general idea:

  1. Mimic your Objective-C classes with Swift protocols (manual work needed)
  2. In your swift code use these protocols
  3. Expose internally these Swift protocols to Objective-C (manual work needed)
  4. Adopt these protocols by ObjC classes
  5. Create in Swift and expose in ObjC a factory, that will create instances of these protocols
  6. Create in the factory methods, that will allow you to register concrete types, and expose them to ObjC
  7. Register ObjC types from Objective-C code in the Swift factory
  8. Use the factory to instantiate ObjC classes, without actually exposing ObjC to Swift (hurray ;) )

Creating Swift framework with private Objective-C members. The Good, the Bad, and the Ugly

Github example project

like image 154
Andrzej Michnia Avatar answered Oct 13 '22 17:10

Andrzej Michnia


You can find some method from this link.

Just create Folder (e.g.PrivateA) with module.modulemap where include all object headers you need to use in your Swift class. e.g.

module $(ModuleName)Private {
    header "header.h"
    export *
}

in Swift, you can use: import $(ModuleName)Private

so, default module exclude these headers when using import $(ModuleName).

From my experiment, test project can also import $(ModuleName)Private, but not found any useful code.

Try this way anyway.

like image 35
Jenus Dong Avatar answered Oct 13 '22 17:10

Jenus Dong