Objective C classes within an iOS Swift-based dynamic framework - ios

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

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:
Mimic your Objective-C classes with Swift protocols (manual work needed)
In your swift code use these protocols
Expose internally these Swift protocols to Objective-C (manual work needed)
Adopt these protocols by ObjC classes
Create in Swift and expose in ObjC a factory, that will create instances of these protocols
Create in the factory methods, that will allow you to register concrete types, and expose them to ObjC
Register ObjC types from Objective-C code in the Swift factory
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

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.

Related

Swift and Objective-c framework exposes its internals

I’m trying to integrate Swift into an existing objective-c framework that has public, private and project files. In order for swift to access the project files, I added a modulemap that defines a new module (e.g MyFramework_Internal) by including all the project headers as explained here: http://nsomar.com/project-and-private-headers-in-a-swift-and-objective-c-framework/
That setup is sort of working but one thing I was surprised to see is that now a client can access the internal classes by importing MyFramework_Internal (#import MyFramework_Internal). Is there a way to hide the module since it's only needed by the framework itself?
The modulemap looks like this now:
module MyFramework_Internal {
header "Folder1/Baz.h"
header "Folder1/Folder2/Bar.h"
export *
}
The module file is intended for Objective-C, and Objective-C doesn't have the concept of internal. It only knows public (what's declared in the header files), and private (what's declared only in the implementation files).
Thus, anything that end's up in a module file will be publicly available.

How do I build an iOS Framework with the FIT C++ library

I am trying to create a Swift wrapper for the FIT C++ libFitSdkCppiOS.a library but don't really know how to set things up with the mix of C++, Objective-C and Swift code.
Here is what I have done so far:
1. Created a new target for the FITFramework
2. Copied the libFitSdkCppiOS.a library and the associated cpp header files into the targets folder in Xcode
3. Because you can't use a Bridging-Header file in Frameworks I am trying to figure out how what to do next.
I have seen a few posts about something called an umbrella header but have no idea what that is or what needs to be in it to get this to work. Can someone please explain step by step what I need to do to create this Swift Wrapper and package it up as a framework that can be used by other projects.
Is the umbrella header the main framework header file, in this case the one called FITFramework.h ?
If not how do I create an umbrella header file and where does it need to be?
What should be in the umbrella header file?
FITFramework.h
//
// FITFramework.h
// FITFramework
//
// Created by xxxx xxxxxxx on 7/6/18.
//
#import <UIKit/UIKit.h>
//! Project version number for FITFramework.
FOUNDATION_EXPORT double FITFrameworkVersionNumber;
//! Project version string for FITFramework.
FOUNDATION_EXPORT const unsigned char FITFrameworkVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import <FITFramework/PublicHeader.h>
EDIT:
1. How do I expose the Objective-C classes to Swift without using a Bridging-Header file ?
There are a few articles that mention the use of a module.map file but this seems to be to expose the C headers rather than the Objective-C headers to the Swift wrapper function
OK I figured it out - and it was hard to find any good or accurate guides. I will write it up in more detail elsewhere and add a link at some point.
In the meantime - assuming your framework is call XXXFramework - you need to:
create a XXXFrameworkPrivate subdirectory in the XXXFramework folder with a module.modulemap file in it
add the private headers to the module.modulemap file like so
module FitFrameworkPrivate {
header "../XXX.h"
header "../YYY.h"
header "../ZZZ.h"
export *
}
create a XXX.xcconfig file with the following line
SWIFT_INCLUDE_PATHS = $(SRCROOT)/XXXFramework/XXXFrameworkPrivate
set the project configuration to use this config file for both debug and release
add this line to your Swift classes
import XXXFrameworkPrivate
Basically this allows Swift classes to import the headers from the module.modulemap file instead of using a Bridging-Header file which can't be used in a Framework.
Watch out though - I have callbacks between the C++ classes, the Objective-C classes and the Swift classes and this creates a problem if your public class uses a protocol to communicate with one of the private Obj-C classes. To avoid that add another public Swift class that talks to the Swift wrapper and only have that one public.
If anyone has a better way of doing it please let me know.
Thanks a lot! This answer was very useful to me as I had similar issue (to integrate the c fit sdk instead of the c++ fit sdk into a swift framework for macOS and iOS apps)
I'll just add the follow step I had to do, in case it helps someone:
When using the framework in a app that used Pods, I wasn't able to include the "Private" framework (necessary because it contains the c constant converted to swift constant like FIT_MESG_XXX) and the xcconfig are set by the Pods framework. Editing the Pods xcconfig worked, but clearly bad.
Eventually, I figured out you can just create a new xconfig with a #include and use that in the external app where you want to use like so (this is separate from the xcconfig you need to build the framework)
#include "../Pods/Target Support Files/Pods-iOSorMac/Pods-iOSorMacOSApp.debug.xcconfig"
SWIFT_INCLUDE_PATHS="$(SRCROOT)/fit-sdk-swift/RZFitFile/sdk" "$(SRCROOT)/fit-sdk-swift/RZFitFile/src"

Missing Required Module - Custom Framework

I created a Cocoa Touch Framework, this framework contains mostly Objective-C codes, my goal is to keep these objective-c codes private, so I added an adapter class in Swift as a middleman between the consumer and my objective-c codes. To do this, I created module to import my objective-c classes and allow my swift class to access these objc classes. I used this guide and clang doc.
So with this I am able to access my objc codes inside my Swift class.
import PrivateObjc
public class SwiftClass{
public func createPersonClass() -> ObjcPerson {
return ObjcPerson()
}
}
The code snippet above works just fine, within my framework atleast. But when I try to use my framework in another project, I get this error:
Missing required module 'PrivateObjc'
I've seen similar posts here in SO but most of them are using Carthage or Cocoapods, some about CommonCrypto. This question is about a custom framework created from scratch. Can anyone enlighten me how to accomplish this? Thanks!

How to import private framework headers in a Swift framework?

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?
You need to modify framework A, So that it export a private module.
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 *
}
In the "Build Settings" of framework A target, search "Private Module Map File" line, and make that:
$(SRCROOT)/A/private.modulemap
Do not include private.modulemap file in "Compile Sources". That causes unnecessary warnings.
Clean and Build framework A target.
In framework B Swift files. you can import the private module like this:
import A
import A.Private
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
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.
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:
Each framework is in a separate project in the workspace
The project uses CocoaPods
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.

Mixed language framework

I have a framework (let's call it MyKit) written in Objective-C that I'm extending with some Swift classes. I'm trying to get my head around it using this documentation: https://developer.apple.com/library/ios/documentation/Swift/Conceptual/BuildingCocoaApps/MixandMatch.html#//apple_ref/doc/uid/TP40014216-CH10-XID_77
As far as I understand, I'm not supposed to have a bridging header class, and instead put all my includes in the umbrella header file (that I understand to be ).
So I write in MyKit.h:
#import <MyKit/ModelObjectA.h>
#import <MyKit/ModelObjectB.h>
#import <MyKit/ControllerObjectC.h>
I don't list ControllerObjectD.swift, even though it goes into here as well? Or should I include
#import <MyKit/ControllerObjectD-Swift.h>
?
ControllerObjectD uses ModelObjectA and ModelObjectB. Now that I don't have a bridge headerfile, I get compile errors in it because it cannot find these objects.
The documentation says "Swift will see every header you expose publicly in your umbrella header." and this is true when I import the framework into other projects, but the framework project cannot compile because it doesn't see it. I have turned on the "Define Modules" build setting.
Is there something I've misunderstood about the umbrella header, perhaps? Where can I say "hi project, this is the umbrella header file"? The framework compiles if I set the umbrella header file as bridging header, but that sounds like I've come back to the beginning this way?
Cheers
Nik
I believe your problem may be down to the Access Modifiers in your Swift class, however I've written a short guide and sample project to help you:
Sample project can be found here
There are two parts to having a mixed language framework:
Importing Objective-C into Swift
Importing Swift into Objective-C
1. Importing Objective-C into Swift
This is, for example, if you have an Objective-C class named Player that you want to add to a swift class called Game.
According to the documentation, you need to do these two steps to import the Player object into the Game object.
Under Build Settings, in Packaging, make sure the Defines Module setting for that framework target is set to Yes.
In your umbrella header file, import every Objective-C header you want to expose to Swift.
#import <Framework/Player.h>
Make sure your Player header file in Objective-C is marked for public target membership in the framework:
Following these steps, it's possible to import the Player Objective-C class into the Game Swift Class:
import UIKit
public class Game: NSObject {
public let player: Player
public init(player: Player) {
self.player = player
super.init();
}
}
2. Importing Swift into Objective-C
For importing the Swift Game class into the Player object, we can follow a similar procedure.
As before; Under Build Settings, in Packaging, make sure the Defines Module setting for that framework target is set to Yes.
Import the Swift code from that framework target into any Objective-C .m file within that framework target using this syntax and substituting the appropriate names:
#import <ProductName/ProductModuleName-Swift.h>
In my case, this works as:
#import <SwiftObjC/SwiftObjC-Swift.h>
and I assume, for you:
#import <MyKit/MyKit-Swift.h>
Therefore, make sure that all the properties, methods and classes you want to access are defined as public in your swift file or else they won't be visible to Objective-C .
I have uploaded my sample project showing how this all works on GitHub https://github.com/elliott-minns/SwiftObjCTestFramework
I hope this helps you with your problem.
I think you need to do bridging-header to access your obj-c code in swift.
More in the link
https://developer.apple.com/library/ios/documentation/Swift/Conceptual/BuildingCocoaApps/MixandMatch.html
so it is a file with your imports from obj-c transfering to swift. names are very important so care about that. hope it helps
p.s. also you need to add this header path to your project settings
From the Documentation at Apple: A Swift class must be a descendant of an Objective-C class to be accessible and usable in Objective-C. For more information about what you can access from Objective-C and how the Swift interface is imported, see Swift Type Compatibility.
So your public swift class needs to extend NSObject for example.

Resources