Swift Objective C interoperability, Static Libraries, Modulemap etc…
Here we will discuss all kind of aspects which we face in regular life in iOS development with respect to Swift Objective C interoperability.
We will discuss all kind of combination and how to solve them with end-to-end coding.
Case 1:
Swift and Objective C code resides in same App Target. How the Objective C code is used in Swift classes?
Case 2:
Swift and Objective C code resides in same App Target. How the Swift Code is used in Objective C classes?
Case 3:
Create a Swift static Library. How to use Swift Static Library in App Target?
Case 4:
Create a Objective C static Library. How to use Objective C static Library in App Target?
Case 5:
How a Swift Static Library is used in another Swift Static Library and Objective C static Library?
Case 6:
How an Objective C static Library is used in another Swift Static Library and Objective C Static Library?
The overall project is published in my Project. Definitely, you should check this to relate your understanding:
Let’s solve the problems:
Case 1:
Swift and Objective C code resides in same App Target. How the Objective C code is used in Swift classes?
We can use Objective C code in Swift code using Bridging-Header file when both Swift code and Objective Code resides in same App Target.
Step 1.1:
Create a Project with Swift and try to add Objective C file (vice-versa). Xcode will ask to create a <ProjectName>-Bridging-Header.h automatically. It also updates “Objective-C Bridging Header” field in Build setting automatically.
OR
You can create bridging-header.h file manually.
- Create a header file
- name it <ProjectName>-Bridging-Header.h (you can provide any other name instead of ProjectName, there is no restriction, but update the same in build setting)
- update the “Objective-C Bridging Header” field in Build setting manually with that header file.
Step 1.2:
- add the Objective C header files in <ProjectName>-Bridging-Header.h to get access in swift files.
- Now we can use the classes defined inside Bridging-Header.h in swift file
Example:
// use Objective C function - App Target in swift filelet appTargetObjcFile = ObjCAppTarget()appTargetObjcFile.testObjectiveCAppTarget()
Case 2:
Swift and Objective C code resides in same App Target. How the Swift Code is used in Objective C classes?
Step 2.1:
- The compiler generates <ProductName>-Swift.h file in /Build folder under /DerivedData directory.
Step 2.2:
- Xcode automatically update the same information “Objective C Generated Interface Header Name” in Build Settings of App Target.
Step 2.3:
- If we do not set anything in “Header Search Paths” in Build Settings of App Target, Xcode by default refers the /<ProjectName>.build under /Build directory to retrieve the *-Swift.h file. But it is not recommended.
- We provide “Header Search Paths” to retrieve the header files.
- Add a run script to copy the *-Swift.h file from /DerivedData directory to new “Header Search Paths” directory
# Type a script or drag a script file from your workspace to insert its path.# target directory is Project root directorytarget_dir=${SRCROOT}/include/# Ensure the target include path existsmkdir -p ${target_dir}# Copy any file that looks like a Swift generated header to the include pathcp ${DERIVED_SOURCES_DIR}/*-Swift.h ${target_dir}
Step 2.4:
Now we can use *-Swift.h file in Objective C code to access the swift file. Refer ObjCAppTarget.m file in the project.
// MARK:- import App Target Swift header/*This is <AppTarget>-Swift.h file which is used to include swift files into objective c code.Those swift files are belonging to App Target*/#import "PlayWithLibraries-Swift.h"...@implementation ObjCAppTarget...//MARK: swift app target function-(void) testSwiftAppTargetFunction {NSLog(@"Invoke App Target Swift function ::: caller App Target Objective C function ");SwiftAppTarget *obj = [[SwiftAppTarget alloc] init];[obj test];}@end
Case 3:
Create a Swift static Library. How to use Swift Static Library in App Target?
I am not going too much details with Static Library and Dynamic Libraries/framework, their pros and cons etc. You can easily find it out in internet.
Our Objective:
Here we will create a Swift Static Library and consume in App Target Swift file and Objective C file.
Let’s do it.
Step 3.1: Create a swift static library
- Select Project → Click on + button at the bottom → choose a template for new target → Static Library
OR
File → New → Target → Static Library
- Give a Product name and choose language — Swift
Step 3.2: Add Swift Class files into Library and build:
- It should be @objc and open and inheriting from NSObject class
- open definition helps the class to be exposed outside world and added in <ProductName>-Swift.h file
- @objc definition helps the swift file to be accessible in Objective C file
- Example: SwiftLibraryOne static library
import Foundation/*the class file must be "open" so that it will be added to SwiftLibararyOne-Swift.h header filewhich will be used in Objective C file.*/@objc open class SwiftOneFileA: NSObject {@objc public func testOne() { print("Swift One Library - File A - testOne function")}}
Here is the SwiftLibraryOne-Swift.h header file:
- add a “Run Script” to copy the SwiftLibraryOne-Swift.h from /DerivedData directory to ProjectDirectory/include folder
- After building the Swift Static Library generates:
1. lib<ProductName>.a // library file2. .swiftmodule file and .swiftdoc file for devices and simulator which helps App Target's/Other library's Swift file to identify exposed APIs.3. <ProductName>-Swift.h file which helps APP Target's / Other Libraries Objective C file to identify exposed APIs.
Copy all the above files and use in App Target /other Libraries.
Step 3.3: Let’s set up App Target to use Swift Static Library:
- Add Dependency
Goto App Target →Build Phases → Dependencies → click on + → add target (example: “SwiftLibraryOne” )
- Link Binary with Libraries
Goto App Target → Build Phases → Link Binary with Libraries → click on + → add library file (example: libSwiftLibraryOne.a )
- As we provided “Header Search Paths” (App Target → Build setting → Search Paths → Header Search Paths) to ${SRCROOT}/include/ which should include the Objective-C header file (example: SwiftLibraryOne-Swift.h) of Swift Library. This file will be used in App Target Objective C file to import Swift functionality.
- Similarly, we can provide the “Library Search Paths” (Build settings → Search Paths → Library Search Paths) for retrieving the library files (.a files / .dylib files). In our application, we keep “Library Search Paths” empty to retrieve the libraries from Products directory.
Step 3.4: Use Swift Static Library in App Target Swift file:
- Simply use “import SwiftLibraryOne” in swift file.
- It actually refers x86_64.swiftmodule file (for device build) and x86_64-apple-ios-simulator.swiftmodule (for simulator build) which is equivalent to Objective C header file (in binary file format).
Step 3.5: Use Swift Static Library in App Target Objective C file:
- The generated header “<ProductName>-Swift.h” is used in Objective C.
- use “run script” in Swift Static Library to copy the generated “*-Swift.h” and copy into Project file (example: $(SRCROOT)/include). Use the same path in “Header Search Paths” of App Target to refer “<ProductName>-Swift.h”
Case 4:
Created an Objective C static Library. How to use Objective C static Library in App Target?
Step 4.1: Create Objective C static library:
- Select Project → Click on + button at the bottom → choose a template for new target → Static Library
- Give a Product name and choose language — Objective C
- Add couple of Objective C files (.h and .m)
Step 4.2: Create a module.modulemap:
- As Bridging-Header can help us in App Target and App Test Target, not in static library or dynamic libraries to use the Objective C / C APIs into Swift classes, modulemap can help us here.
- Modulemap improves access to API of libraries by replacing the textual preprocessor inclusion(#include) model with more robust & semantic model.
- Modulemap describes the mapping between headers and modules
- Create an empty file & rename it module.modulemap and add it to the Static Objective C library.
- Enable modulemap: “Defines Module” = “YES”
(select Objective C library → Build Settings → Packing → Defines Module)
- Give the modulemap path: “Module Map File” = “$(SRCROOT)/ObjectiveLibraryOne/module.modulemap”
(select Objective C static library → Build Settings →Packing → Module Map File)
Step 4.3: Expose APIs in module.modulemap
- Let’s define what are the APIs we want to expose through modulemap.
module ObjectiveLibraryOne {umbrella header "ObjectiveLibraryOne.h"export *explicit module SubModule {header "ObjCOneFileA.h"}}
- Define an Umbrella header of that Objective C Static Library which includes all the other header files which we want to expose outside world. (For Example: <LibraryName>.h is Umbrella header)
#import <PublicHeader1.h>
#import <PublicHeader2.h>
// ...
#import <SomeDependentLibrary/PublicHeaderN.h>
- export * includes all the submodules defined in that module.
- We can make explicit submodule which is really good in terms of memory optimisation and use import module.submodule from caller class. (Example: import ObjectiveLibraryOne.SubModule)
- add module.modulemap, umbrella header and public header files into product directory using “select ObjectiveC static library → Build Phases → Copy Files”
- Select “Objective C static library” target and build(Command + B) and check the product directory which must contains .a file, include folder (contains module.modulemap, umbrella header, public headers)
Step 4.4: Configure App Target to consume Objective C static library:
This is exactly similar process what we did for adding a swift static library.
- add dependencies of App Target
- Link binaries with Libraries
- As we are using module.modulemap, no need to copy Objective C header file into App Target’s “Header Search Paths”, App Target refers the /include/$(Product-Name) sub-directory under Product Directory to get the information about this Objective C static library.
Step 4.5: Consume Objective C static library functionality in App Target Objective C file:
- Use in Objc .m file
@import <moduleName> OR @import <moduleName.subModuleName>
(see the Example: ObjCAppTarget.m file: @import ObjectiveLibraryOne;)
Step 4.6: Consume Objective C static library functionality in App Target swift file:
- Use in .swift file
import <moduleName> ORimport <moduleName.subModule>
(See the example: import ObjectiveLibraryOne in ViewController.swift)
Case 5:
How a Swift Static Library is used in another Swift Static Library and Objective C static Library?
- Create a new Swift Static Library (Example: SwiftStaticLibraryThree)
- Create a swift file and add some functionality. The file should be defined @objc — it can be imported to Objective C file, open — the class can be exposed outside
@objc open class SwiftStaticLibraryThree: NSObject { ... }
- Once we build the Swift Static Library, in Build directory it generates: .a file, .swiftmodule file, .swiftdoc file and *-Swift.h file which are useful for other static library to use the functionality.
- Intermediate.noIndex folder under Build directory contains *-Swift.h file:
- Products folder under Build directory contains binary library .a file, .swiftmodule file & .swiftdoc file
- What is .swiftdoc file?
It’s a documentation file which describes about the public classes, functions etc. defined inside the open classes. If you provide “///” comments while defining the classes and functions, it will become part of .swiftdoc file and be visible in description section from callers.
Example: Give some description in SwiftStaticLibraryThree class which belongs to SwiftStaticLibraryThree static Library and I use this library in SwiftLibraryFour swift file:
- to make *-Swift.h available at Project level, use a “run script” (Build Phases → add “Run Script”) :
# target directory is Project root directorytarget_dir=${SRCROOT}/include/# Ensure the target include path existsmkdir -p ${target_dir}# Copy any file that looks like a Swift generated header to the include pathcp ${DERIVED_SOURCES_DIR}/*-Swift.h ${target_dir}
Now *-Swift.h is available to /ProjectDirectory/include which should be the “Header Search Paths” for dependent Objective C static libraries.
Step 5.1: Use a Swift Static Library into another Swift Static Library:
- Select Target Swift Static Library (Example: SwiftLibraryFour)
- add dependencies (Example: add SwiftStaticLibraryThree )
- use import <libraryName> in swift file ( .swiftmodule is used here internally to import the library. Example: import SwiftStaticLibraryThree.)
Step 5.2: Use a Swift Static Library into another Objective C static Library:
- select Target Objective C static library (Example: ObjCLibraryFour)
- add dependencies (Example: add SwiftStaticLibaryThree)
- update “Header Search Paths” where *-Swift.h file resides
- import generated *-Swift.h header file to use swift functionality.
#import "SwiftStaticLibraryThree-Swift.h"...-(void) test {SwiftStaticLibraryThree *obj = [[SwiftStaticLibraryThree alloc] init];NSInteger add = [obj addWithA:5 b:3];NSLog(@"%zd", add);NSInteger subtract = [obj subtractionWithA:10 b:2];NSLog(@"%zd", subtract);}
...
Case 6:
How an Objective C static Library is used in another Swift Static Library and Objective C Static Library?
- Objective C static library exposes the APIs with module.modulemap
Step 6.1: Use Objective C static Library into Swift static Library
- Select Swift Static Library. Goto Build Phases → Add Dependencies (the Objective C static Library)
- Link Binary with Libraries — if Objective C static library is generated outside, not belonging to same project files.
- use import <modulName> in swift file and use the functionality.
(Example: Refer SwiftStaticLibraryTwo Static Library which is dependent on ObjectiveLibraryOne and using it’s functionality)
- As the Objective C static library and Swift static library belongs to same project, so no need to copy library .a file, modulemap, umbrella header and public headers. But if we are using an objective c library created outside, copy those files into defined “Header Search Path” and “Library Search Paths” of Swift static library.
Step 6.2: Use Objective C static library(Say ObjCOne) into another Objective C static Library (Say ObjCTwo)
- It will be exactly similar step of above. Select Objective C static Library ObjCTwo and add ObjCOne as a depenecies. (Build Phases → Dependencies → add the library)
- Link Binary with Libraries — if ObjCTwo is generated outside, not belonging to same project files.
- use @import <moduleName> in Objective C implementation file.
(Example: ObjCLibraryTwo.m file which imports ObjectiveLibraryOne)
So, we learn how to use static libraries in all kind of combinations. Check the source code for better understanding. Thank you so much :-) :-)
A Person who feels appreciated will always do more than what is expected.