Is it possible?

Integrating RxSwift as XCFramework With CocoaPods

You can make the integration with a simple workaround

Pablo Manuelli
Better Programming
Published in
6 min readJan 24, 2022

--

RxSwift + XCFrameworks + CocoaPods

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 to 6.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 and s.exclude_files. We don’t want them.
  • Add s.vendored_frameworkswith 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 is 6.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 precompilated 6.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!

Once again, here is the example project that uses this workaround with both approaches for storing the podspecs.

If you only want the modified podspec files for version 6.5.0 you can find them here.

--

--