iOS - How to remove implicit imports? - ios

In my team, we develop several frameworks and in one of the apps integrating said frameworks, they want to enforce the imports of FooBar framework to a specific folder and throw a compile time warning if someone tries to import FooBar outside that folder.
I created a swiftlint rule to detect imports of FooBar outside the folder and it turns out that it also detects some implicit imports that should not be there and throws a warning from my lint rule. Even if the file doesn't explictly import FooBar, it seems to have access to it.
Note: FooBar is an static framework that has both ObjC and swift support and the app project also uses both ObjC and swift to access the framework's functionality.
How can I get rid of implicit imports in an iOS project?
Here's the rule I created
oneplayer_import:
exclude: "somepath"
regex: "^#?import .*FooBar.*$"
message: "FooBar frameworks should not be imported outside somepath"
severity: warning

Related

XCFramework with other 3rd party dependencies

Problem
I am developing a Framework (let's name it MyFramework) which I want to use in a host iOS application. MyFramework depends on several other 3rd party libraries (Alamofire, ZIPFoundation, etc.) which I have added as Swift Package Manager dependencies in the MyFramework project.
After creating MyFramework.xcframework and adding it to the host iOS application, I get a compilation error saying No such module 'Alamomfire'. As suggested in this thread I have added Alamofire as SPM dependency in the host iOS Application as well, which resolves the compilation error. But during runtime I get a console message saying Class _TtC9Alamofire21CompositeEventMonitor is implemented in both <HOME>/Library/Developer/Xcode/DerivedData/DevSample-frwzbtlatwjedobzfossixhbtork/Build/Products/Debug-iphonesimulator/MyFramework.framework/MyFramework (0x10346eb98) and <HOME>/Library/Developer/CoreSimulator/Devices/D17DA122-DB76-4447-B8FD-14FC8405A856/data/Containers/Bundle/Application/F68CBE06-CC7F-40AA-ACA8-2210048CA495/DevSample.app/DevSample (0x102ed31f8). One of the two will be used. Which one is undefined.
Questions
Is this expected behavior? As mention in this Apple Doc creating Umbrella Frameworks is discouraged. How to get rid of those runtime warnings/errors then?
#Nominalista I have created a similar app-and-modules setup in this way: I created the app, and the framework within the same directory, and then I dragged this framework's .xcodeproj inside my app. Inside the framework, I have added Swift packages as Alamofire, ZIPFoundation. I can use either of these 2 transitive dependencies both from the framework itself, and the app target as well with a simple ie, "import Alamofire".
I am using Xcode 12.5 and I am explicitly adding Alamofire as Swift package both to the framework and the app, I am not getting "..is implemented in both..." warnings. So can I ask you how your project is being setup?

Swift classes marked with #objc not being added to "-Swift.h" header in mixed Objective-C/Swift framework

I am attempting to convert an old statically linked library to a framework. With mixed swift and objective c in the static library, all headers are generated correctly. However, switching to a framework target and adding swift files, marked with the #objc header, the class is not added to the -Swift.h header. I can import the header, but swift classes are not found. This is in Xcode 10.2 and attempted using both Swift 4.2 and 5.
Are there any particular settings in XCode that will affect the generation of the *-Swift.h header in a mixed Objective C/Swift framework target?
I had a similar issue. In my case it was a known issue in Xcode 10.2:
https://developer.apple.com/documentation/xcode_release_notes/xcode_10_2_release_notes
If you’re building a framework containing Swift code and using lipo to create a binary that supports both device and simulator platforms, you must also combine the generated Framework-Swift.h headers for each platform to create a header that supports both device and simulator platforms. (48635615)
...
In my case all I had to do was to update the Carthage to the newest version 0.33.0
The problem appears to be a combination of Apple's new build system, the expectations they set when compiling and the number of inter-dependencies in the project setup.
The new build system runs the Swift compilations in parallel. When having multiple library/framework dependencies that are mixed Objective C and Swift, the compiler appears to not generate the -Swift.h files on time. In static libraries, the -Swift.h files appear to be generated at the end of the Swift Compilation process, meaning they are not generated quickly enough to be used by the Objective C files when the Objective C compilation occurs. When generating a framework, it appears that the Compiler generates the header at the beginning of the compilation process and the Swift files are not fully compiled and the -Swift.h file does not generate appropriately with the Objective C class interfaces and protocols.
What this means ends up meaning is that we can not rely on the "target dependencies" to build the dependent projects correctly.
So how can we build our .framework of mixed Objective C and -Swift.h without a ton of manual scripting.
Here are the tricks I discovered that will work.
Use the old build system. When using the new build system there is an error when it attempts to merge the module map for the static library file saying that it can not find the *-Swift.h file whether or not it exists.
Create your framework by making it a wrapper around the static library by:
Giving them both the same product name and module name.
Add a single .Swift file to the framework build so that it has something to compile and will link the swift libraries.
link to the static library in the framework.
Add all necessary headers to the public headers of the framework.
Add all public headers to the umbrella header.
Use a Run script phase to copy the *-Swift.h file from the static library build to the framework product post compile.
For any public headers that include the *-Swift.h, you may need to post process the header and replace the *-Swift.h import with the appropriate framework import ie . This would not be recommended due to possible cyclical imports in the umbrella header.
When running a clean, build the framework target first manually, before building the application target.
Below is an example script for copying the *-Swift.h file post build.
header_file="${TARGET_TEMP_DIR}/../${PRODUCT_MODULE_NAME}.build/DerivedSources/${PRODUCT_MODULE_NAME}-Swift.h"
header_dir="${BUILT_PRODUCTS_DIR}/${PUBLIC_HEADERS_FOLDER_PATH}"
mkdir -p "$DIR"
echo "copying $header_file $header_dir"
cp -f "$FILE" "$DIR"
UPDATED
Marking all modules Swift compilation mode to "Whole Module" appears to have a positive affect on this issue, but I have not fully tested it.

How to package module.modulemap into CocoaTouch Swift Framework?

I am trying to create a CocoaTouch Swift Framework which I can then distribute to 3rd parties for them to use, and I can build it and use it ok on my machine.
The issues arise when I give it to other people.
My framework uses C code, which I make available for use with Swift within my framework via a module.modulemap file as such:
And I am able to use such module in a Swift file, within the framework, ok, as such:
However, when I build the framework and give it to other people to use, their compiler complains that the Minzip module is missing with the message Missing required module 'Minizip'.
Having investigated, it seems like I'd have to make the users of my framework modify their Swift Compiler - Search Paths -> Import Paths the same way I have to do it to have my compiler find the module.modulemap.
Is there a way that I can build the framework so that I don't have to do this?

Xcode Framework Modules

I'm having an issue implementing modules for my particular build configuration. I have a bunch of dynamic frameworks that I would like to add module map files to so that they can be naturally imported into Swift without having to use Objective-C Bridging Header files. The issue that I'm having is as follows:
I have multiple modular frameworks:
Framework.framework (base framework, required by all components)
Framework.Component1.framework
Framework.Component2.framework
... etc
I would like to be able to write a module map for each framework that uses this dot notation for the module naming (for backwards compatibility, among other reasons) but Xcode isn't allowing me to name whole modules with the dot syntax since the dot syntax is reserved for Submodules.
I've tried creating an umbrella framework that contains all of different components and writing a single module map for that, but given that the frameworks are dynamic this necessarily bloats the final binary size if the user isn't using all of the frameworks inside of the umbrella framework.
Are there any solutions that would allow me to use this dot syntax while having separate module map files for each of the frameworks?
Thanks!
Go with the umbrella framework strategy.
Then, for submodules that are "optional" in your umbrella framework (maybe even all or most of them) set the submodule declaration to explicit. This means the consumer must import the submodule explicitly in order for it to be loaded. This is what I've done in the past to import a very large C library into Clang's module system and it worked well enough.
The docs are okay, but not great. However, they do go over this case pretty well: https://clang.llvm.org/docs/Modules.html#submodule-declaration

How to create & configure a (mix & match) framework as module?

I've read the "Using Swift with Cocoa & Objective-C" a few times, and I've tried every possible combination of project type (framework, static lib & Swift first, Obj-C first, the lot) but I simply can not get modules to work. I've spent more than 2 hours on this, and it shouldn't be that difficult. Chosing a module name and setting Defines Module to 'yes' doesn't do it.
My goal: to create a module that makes the Apple System Logging service available in Swift code. I've got an objective-c wrapper library lying around that I thought I'd make available as a module to import into my Swift framework, but so far the only luck I've had is it exposing only one of the many classes publically in Swift code. The rest simply aren't available. It's also exposed the 'asl.h' C functions, which is cool, but I have no idea how that happened. I tried creating several sparce frameworks/static libs with the sole goal of making 'asl.h' functions available in Swift. If someone knows how to do that (for any C headers that are part of iOS) without the need to create an Objective-C framework as a module to import into Swift, I'd love to hear about it. Or even just as a framework to make any c headers available in Swift that would be awesome.
So! I create a workspace with an app project in it, and then I create a framework project at the same root level in the workspace as the app project. On the framwork target I set the Product Module Name and Defines Module to 'Yes'. I update my My App's Scheme build target to include the framwork above the app target. I ensure the Link Binary With Libraries build phase contains the framework. Build the app target after adding an 'import MyFramework' line in a Swift file and boom. Failure.
Obviously I've tried clean builds, deleting Derived Data for all projects, hopping one leg and trashing everything and starting again. Someone, anyone, had success with modules yet?

Resources