How to create Swift import submodules - ios

Currently we have modularized our Swift project, using multiple targets. The targets compile to .framework files that are dependencies of the higher targets. We have a Common module target, a few Product module targets, and the original App target. Common will hold very low level code, shared libs, and any specific code that may be shared by multiple product targets.
So the App will do:
import Common
import Product1
import Product2
And Product1 target code might do:
import Common
Generally the flow is Common -> Product* -> App, where Common will never reference the other direction.
Unfortunately Common has grown large, and has a lot of code that may not be needed for every import.
What I'd like to be able to achieve in Swift, is something like this:
import Common.API
import Common.PurchaseCheckout
I've looked into module-maps, which allow for setting up the import submodules, but it is all focused around objective-c headers, and not Swift. Is there a swift variation?
The specific need is that we want to create a light-weight SDK that only uses some of the features, and as soon as we "import Common", it bloats the SDK, and it adds dependencies on quite a few unrelated cocoa-pods also. We can include files in the SDK target file-by-file, but were hoping modularization would make things simpler.
EDIT
It appears I can use such things as:
import class Common.FileDownloadAPI
import class Common.FileUploadAPI
possibly in my code to create the impression of a partial import. However, modulemaps seem to be a way in objective-c to do this for you, creating the submodule. I do not want to have to write 100 class imports to import half of the 200 files that a submodule might do in one import. So still looking for ideas.

Related

Creating a Framework with a Modularized Structure

I've recently been working on a framework to encapsulate the network layer of all the apps at my company, I've got the base structure ready and then I wanted to add the rest of the components into separated modules, to avoid importing everything whenever using the framework. For example, I noticed something like this in the MaterialComponents framework:
pod 'MaterialComponents/TextControls+FilledTextAreas'
The pod name has a directory format and in code, the import looks like this:
import MaterialComponents.MaterialTextControls_FilledTextFields
Any idea how to achieve this structure?
Thanks!

#testable import moduleName not importing everything

I have a project that is mixed Obj-C and Swift and I am having some issues getting my unit tests to work.
I'm using the #testable import moduleName directive to import my files, however it does not appear to be importing all the files. I have full access to pretty much all of my Obj-C models, manager classes etc. but none of the view controllers (95% of which are in Obj-C) are available from within the XCTestCase, along with all of my Swift files.
I have imported #testable import ModuleName at the top of the XCTestCase,
I have also edited the target to enable testability, but the only way I can access these files seems to be if I set the files target membership manually, which if I have understood everything correctly, I should not need to do.
Any suggestions?
Yeah I just also went from the same problem about the Unit testing with the project having both Objective-C & Swift languages.
So basically what i found was
You have to add the respective file and all the required files to Test target. And also required to add them to the Bridging_Header to access those files
Moreover the reason behind using the
#testable
is to test methods internal .... methods.
#testable import moduleName
This is used for the purpose of visibility of methods, i.e like internal methods can now be visible in unitTest but atill private methods won’t.

Make all objective-c files available in swift

Recently all started using swift with my objective-c project. I used below post to make my objective-c files available in my swift files: https://stackoverflow.com/a/24005242/1939409
but it seems that for any class that I want to be available in swift files I should import it in my Bridging-Header. Is there any shorter and simple way to make all of my objective-c files available in the swift files too? I have hundreds files and from now I want to code my project with swift. I want to be able to use my old codes and also proceed with swift. It do not seems good to import all of them in the Bridging-Header.
If you have hundreds of existing headers that you potentially want to make available from Objective-C to Swift, you may want to look at dividing your project into multiple targets: all public headers in a framework will naturally be available to a Swift based dependent target as well. Of course you'll need to put together an umbrella header in that case that will list all public headers of that framework, but clearly declaring a public interface tends to be a worthwhile effort anyway for a project whose source code files range in the hundreds of source code files / headers.

Is it possible to use "ModuleName-Swift.h" in a pod or static library?

To access swift files from objective-c, you need to import the ModuleName-Swift.h header, as described here, where "ModuleName" is the Product Module Name (and typically the project name).
Unless I'm missing something, this isn't very good for code reuse. If I make an obj-c class that uses some swift code, my import for the swift class will include the project name. If I want to reuse that class in another project, I'll have to change that import so it includes the project name for the new project.
So if I want to make my class part of a library that can be used by any project, what am I supposed to do?

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.

Resources