Is it possible?
Integrating RxSwift as XCFramework With CocoaPods
You can make the integration with a simple workaround
Hello!
A couple of weeks ago I tried to integrate the precompiled XCFrameworks that RxSwift provides since version 6 via CocoaPods. But, to my surprise, it was not possible to do it using CocoaPods. The only way was to integrate them manually.
I came up with a workaround to this limitation and thought that it will be a good idea to share it. Here I will explain it step by step.
Let’s begin!
Small Disclaimer First
This workaround was developed and tested under:
RxSwift 6.5.0
Xcode 13.2.1
CocoaPods 1.11.2
Context: Why Do I Care About This?
I work on a large-size project.
A major part of our project depends on RxSwift
and his buddies (RxCocoa
, RxBlocking
, and RxTest
). We do not plan to migrate the existing codebase to Combine
or async/await
.
We use CocoaPods as a dependency manager. We do not plan to migrate it to Swift Package Manager either.
Our project takes several minutes to build from scratch (for example our CI builds it from scratch every time). To reduce compilation time we’ve been replacing “normal” dependencies (dependencies where you have the source code and need to compile it) with the XCFrameworks provided by some dependencies.
Since version 6, RxSwift provides better support for XCFrameworks. And every new release now has attached all the needed XCFrameworks as well.
So we thought that It was a good idea to integrate them into our project, to save some compilation time. But unfortunately, at the time this post is written, the XCFrameworks are not distributed under CocoaPods.
But that was not going to stop us.
Workaround: The Idea
We need custom podspec
files for every dependency needed. Those podspecs
will be adjusted to get the vendored framework from RxSwift’s GitHub releases page.
The edited podspecs
can be stored locally on the project or hosted remotely on a different spec repository.
Then, all we need to do is to use those edited podspecs
in our Podfile.
And we are ready to go.
If you don’t want to go through the entire step by step here is a repository with two example projects that use the workaround: One project has the modified podspecs locally and the other uses the podspecs hosted remotely.
Interested only on the modified podspecs? You can find them here.
Workaround: The Step by Step
I will explain the changes step by step. I will use only RxSwift 6.5.0
in the example, to keep things simple. But the same changes apply to all the other Rx frameworks as well and any other version.
Changes on podspec file
We need to make several changes to the RxSwift.podspec
file. You can find the original file here. Those changes are:
- Change
s.version
. To avoid confusions we need to change the version of the dependency. Since I want to use the XCFramework for the 6.5.0 version then I change the version to6.5.0-xcframework
. - Change
s.source
. It needs to point to the zip file that contains the XCFramework. In this case, the source is changed to{ :http => "https://github.com/ReactiveX/RxSwift/releases/download/6.5.0/RxSwift..xcframework.zip" }
- Remove
s.source_files
ands.exclude_files
. We don’t want them. - Add
s.vendored_frameworks
with value"RxSwift.xcframework"
You can find the complete edited file here.
Changes on the Podfile
The last step is to modify the project’s Podfile. Here you have to make a choice:
A. If you choose to store the podspecs
locally, then you must change your dependency declaration to use the local podspec
. You can accomplish this with the :podspec
parameter. For example, if your custom podspecs
are stored inside a folder named RxSwiftSpecs
then the change will be:
pod 'RxSwift', :podspec => './RxSwiftSpecs/RxSwift.podspec'
You can see a project using this approach here. Check the Podfile and the local podspecs inside the
RxSwiftSpecs
folder.
B. If you choose to store your specs on a remote spec repository, then first you need to add the spec repository’s URL to the Podfile like:
source 'https://github.com/pmanuelli/RxSwiftSpecs.git'
And then specify the version that you set on the podspec:
pod 'RxSwift', '6.5.0-xcframework'
You can see a project using this approach here. In this case, check the Podfile and the remote spec repository here.
A Special Case: RxCocoa
The changes to RxSwift
, RxRelay
, RxBlocking
, and RxTest
were pretty straightforward.
But RxCocoa
was more challenging.
RxCocoa
As stated on the RxSwift’s XCFramework installation guide, if you want to import RxCocoa
then you also need to import also RxCocoaRuntime
. But when I checked the original RxCocoa.podspec
file, I found that RxCocoaRuntime
was not set as a dependency. That surprised me.
So given that RxCocoa
does not work without RxCocoaRuntime
one additional change to the podspec
is to add that dependency:
s.dependency 'RxCocoaRuntime', '6.5.0-xcframework'
The final RxCocoa.podspec
file is here.
Now all that is left is to apply the changes the RxCocoaRuntime.podspec
.
RxCocoaRuntime
But there is no RxCocoaRuntime.podspec
on RxSwift. I assume that since it’s not distributed under CocoaPods there is no reason for one.
But this workaround needs one.
I checked the RxCocoaRuntime
dependencies on the SPM manifest file and I found that it only depends on RxSwift. So, knowing its dependencies, creating the podspec
was easy.
The final RxCocoaRuntime.podspec
file is here.
One detail about this framework is that there is no
6.5.0
version of it. At the time of the writing, the lastest version is6.2.0
. I think that if there is no change to the framework then no new version is generated. That makes sense but is unfortunate for me because I prefer to have the same version for all my Rx dependencies.It’s totally OK to create a
6.2.0-framework
version if you like.But I prefer to name it as
6.5.0-framework
even though it uses the precompilated6.2.0
version. That way on the Podfile all my Rx dependencies have the same version.
We are Done!
After applying all the mentioned changes, the only thing left to do is to run pod install
and see the XCFrameworks appear on the project.
All that code that we had to compile on the left is now replaced by some nice XCFrameworks on the right.
What About Version Updates?
One negative point about this solution is that now the version update process is more complex than it was with the “normal” dependencies.
First, you need to create new podspecs
for the updated versions. You should change the version number on the podspec
and change the path to the zip, pointing to the new version.
The next step depends on where do you store the podspecs
.
If you store them locally you can just override the previous podspecs
. But if you hosted them on a remote spec repository you need to add them to the repository first and then update the version number on your Podfile.
Finally, run pod update
and you are done.
Conclusion
I have to admit that it was a little more extra work than I expected initially. But I’m pretty happy with the final result. We managed to finally use the XCFrameworks, integrating them via CocoaPods!
This way we avoid compiling these dependencies over and over (on our CI for example) and save some precious time. But it comes at the price of a more complex solution and a slightly more difficult version update process.
For me, totally worth the price!