Challenges of Generating Universal iOS Frameworks for Both iPhone and Simulator
In 2020, we were faced with a recurring challenge of generating frameworks for iOS that could work on both the iPhone and the simulator. The process was tedious and time-consuming, taking up to 4 hours of manual effort every time we needed to create a universal framework for a bank's requirement. We had to generate frameworks for both the simulator and iPhoneOS, use the lipo command line utility to merge the executables into one fat binary and update the header file to make the headers available for both the simulator and iPhoneOS. Moreover, these frameworks were not Xcode agnostic, meaning they needed to be created for every Xcode version that banks were using. This made setting up continuous integration and continuous deployment (CI/CD) for iOS extremely necessary.
Automating iOS Framework Creation
In 2021, we decided to tackle the challenge by developing an automation script to remove the manual effort required Read More. The script automated everything that one had to do manually, reducing the human effort of 4 hours to just 30 minutes, without any human involvement. However, Xcode 13 was released in 2021, which deprecated the fat binaries that we used to create with universal support. This made it necessary to adopt the use of XCFrameworks, a new way of packaging binaries that was introduced at WWDC 2019. Although Apple had slowly started deprecating the use of fat binaries starting with Xcode 12, they had added a check for all the apps uploaded to the app store to ensure they did not contain any code related to the simulator. In the upload phase, we had to add a script to remove the simulator binary from the fat binary. With Xcode 13, even the usage of fat binaries in a project was completely deprecated. The only way to have universal support was to create XCFrameworks.
Overcoming XCFramework Limitations for Legacy iOS Versions
We tried creating XCFrameworks for our codebase, but we ran into a blocker. We could not generate XCFrameworks for minimum iOS 9 support since XCFramework support was robustly available for iOS 13+. We connected with developers at Apple during WWDC 2022 and explained our case. Although we did not get any significant input to help our case, we decided to debug the issue by going error by error to figure out what was restricting us from creating XCFrameworks. This helped us identify many critical issues, such as clashes with class names present in different projects in the workspace and clashes in class names and project names. The biggest issue was subclassing a class imported from another project. We then refactored the entire codebase for every project that had such classes. We also enabled ABI stability for our projects, and we were successful in creating XCFrameworks for our SDK with minimum iOS 9 support.
Streamlining iOS CI/CD Pipeline with Xcode-Agnostic XCFrameworks
With the XCFrameworks generated and ABI stability in place, our frameworks became Xcode agnostic. Once compiled on one Xcode, they would be compatible with any future Xcode. This would also lower the requirements for banks to come to us for a release whenever they had to upgrade their Xcode. However, we still needed to run the script manually. We also had Flutter integrated into our codebase, which made our iOS codebase tightly coupled with the Flutter codebase. This meant that to build the iOS codebase, we first had to build the Flutter project. This limited us from setting up a CI/CD pipeline. We then refactored the codebase to make it independent and buildable without anything related to Flutter. We also made CoreAdapter, one of our projects in the workspace that helps in redirecting various flows in our SDK, independent of any other project. This enabled us to create a GitHub workflow for setting up our CI/CD pipeline. Finally, in April 2023, we achieved our iOS CI/CD. From four hours of manual effort for every bank to just three clicks, triggering the CI/CD pipeline, specifying the GitHub branch, release tag and the Xcode version they want to compile. The pipeline generates XCFrameworks for the SDK and uploads it to our Azure blob, ready to share.
Conclusion
In conclusion, our journey to achieving a seamless CI/CD pipeline for our iOS codebase was challenging, but we persisted in our efforts to find a solution. The process involved identifying errors, debugging, refactoring, and creating an independent codebase that enabled us to create a Github workflow for our CI/CD pipeline. While it was challenging, the benefits of having a streamlined process, saving time and effort, and increasing efficiency made it all worthwhile. We learned that determination and perseverance can lead to success, and that a willingness to learn and adapt to new technologies and processes is essential in the ever-changing world of software development.