Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Zero-Downtime Migration: Moving a Massive, Hist...

Avatar for kagemiku kagemiku
April 14, 2026

Zero-Downtime Migration: Moving a Massive, Historic iOS App from CocoaPods to SPM and Tuist without Stopping Feature Delivery

Presentation slide used in try! Swift Tokyo 2026 (https://tryswift.jp/en/)
---
To ensure business continuity ahead of the 2026 CocoaPods sunset, our mobile team migrated external dependencies and the multi-module project generation process of a 14-year-old iOS application with over 500,000 lines of mixed Objective-C and Swift code. The project involved decoupling 60+ external dependencies and 70+ internal modules from a legacy CocoaPods setup that was deeply intertwined with the app’s structure. In just six months, a foundation team of two engineers achieved a “zero-downtime” transition to Tuist and SPM without disrupting the ongoing feature delivery of the wider development team. This session explores the strategy for large-scale automation, solutions for complex dependency graphs, and the custom “guard rail” tools that protected the production environment during this high-risk shift.

Avatar for kagemiku

kagemiku

April 14, 2026

More Decks by kagemiku

Other Decks in Technology

Transcript

  1. try! Swift Tokyo 2026 April Zero-Downtime Migration:
 Moving a massive,

    historic iOS app from CocoaPods to SPM and Tuist without stopping feature delivery Akira Fukunaga Mobile Foundation Team @ SmartNews
  2. The Longest Title 😅 “Zero-Downtime Migration: moving a massive, historic

    iOS app from CocoaPods to SPM and Tuist without stopping feature delivery” 4
  3. The Longest Title 😅 “Zero-Downtime Migration: moving a massive, historic

    iOS app from CocoaPods to SPM and Tuist without stopping feature delivery” • Dealing with a legacy iOS app that supports a massive userbase 5
  4. The Longest Title 😅 “Zero-Downtime Migration: moving a massive, historic

    iOS app from CocoaPods to SPM and Tuist without stopping feature delivery” • Dealing with a legacy iOS app that supports a massive userbase • Assessing the risks of a deeply-rooted CocoaPods setup 6
  5. The Longest Title 😅 “Zero-Downtime Migration: moving a massive, historic

    iOS app from CocoaPods to SPM and Tuist without stopping feature delivery” • Dealing with a legacy iOS app that supports a massive userbase • Assessing the risks of a deeply-rooted CocoaPods setup • Migrating to Swift Package Manager + Tuist 7
  6. The Longest Title 😅 “Zero-Downtime Migration: moving a massive, historic

    iOS app from CocoaPods to SPM and Tuist without stopping feature delivery” • Dealing with a legacy iOS app that supports a massive userbase • Assessing the risks of a deeply-rooted CocoaPods setup • Migrating to Swift Package Manager + Tuist, in a safe and robust way, without ever disrupting the feature delivery cycle 8
  7. iOS App 13 AS-IS | News app with the largest

    userbase in Japan, also available in the US #1
  8. iOS App 14 AS-IS | News app with the largest

    userbase in Japan, also available in the US #1 year-old historic app lines of code (Swift + Obj-C) external libraries internal modules 14 500k+ 60+ 70+
  9. iOS App 15 AS-IS | News app with the largest

    userbase in Japan, also available in the US #1 year-old historic app external libraries managed by CocoaPods internal modules managed by CocoaPods 14 500k+ 60+ 70+ lines of code (Swift + Obj-C)
  10. iOS App 16 AS-IS | News app with the largest

    userbase in Japan, also available in the US #1 14 year-old historic app 500k+ 60+ external libraries managed by CocoaPods 70+ internal modules managed by CocoaPods + Workspace and projects were generated via CocoaPods lines of code (Swift + Obj-C)
  11. Problem: CocoaPods in 2026 • Already in maintenance mode •

    CocoaPods Trunk will be in read-only mode from December 2026 And… • Many new libraries do not support CocoaPods • Several existing libraries are also starting to drop CocoaPods support https://blog.cocoapods.org/CocoaPods-Specs-Repo/ https://blog.cocoapods.org/CocoaPods-Support-Plans/ 18 AS-IS |
  12. Solution: Run Away from CocoaPods! 🏃💨 Decision to start migrating

    from CocoaPods in August 2025 Started as an engineer-driven project with only 2 engineers Akira Fukunaga Mobile Foundation Team @ SmartNews Otávio Lima Mobile Foundation Team @ SmartNews 21 AS-IS |
  13. 🧩 Dependency manager 🛠 Project management tool 🏠 Host tool

    for supplemental tasks • Privacy Manifest fi les generation • Software license declaration • Internal module scaffolding 26 TO-BE | Before: Our CocoaPods Setup
  14. 🧩 Dependency manager 🛠 🏠 • Privacy Manifest fi •

    Software license declaration • Internal module scaffolding 27 TO-BE | Before: Our CocoaPods Setup
  15. 28 TO-BE | Why? • First-party tool for Swift •

    Of fi cially released 10 years ago: getting stable and mature Some concerns… • Developer experience and stability on Xcode workspaces/projects generated by SPM in large codebases with many external libraries • Flexibility as a host tool for (our) supplemental tasks After: Dependency Manager Swift Package Manager
  16. 🧩 Dependency 🛠 Project management tool 🏠 Host tool for

    supplemental tasks • Privacy Manifest fi les generation • Software license declaration • Internal module scaffolding 29 TO-BE | Before: Our CocoaPods Setup
  17. + Host tool for supplemental tasks Tuist 30 TO-BE |

    Why? • OSS tool to manage Xcode projects (https://github.com/tuist/tuist) • Support SPM to integrate libraries • Generate Xcode projects and workspaces from con fi guration fi les written in Swift • Flexibility to customize build scripts and con fi gurations After: Project Management Tool
  18. 🧩 Dependency manager 🛠 Project management tool 🏠 Host tool

    for supplemental tasks • Privacy Manifest fi les generation • Software license declaration • Internal module scaffolding 31 TO-BE | Swift Package Manager Tuist After: SPM + Tuist Setup
  19. Side-note: Tools that we did not select Disclaimer: these are

    good tools, just not ful fi lling our requirements. XcodeGen • Uses YAML to de fi ne the con fi guration, while Tuist uses Swift.
 With Tuist, we can leverage compile-time veri fi cation to ensure a more robust maintainability. Bazel or Buck2 • Higher learning curve and a quite complex system. • Strategy to keep the team lean and fast, so we need a low-maintenance setup. 32 TO-BE |
  20. Our Strategy Multiple milestones with parallel execution by 2 engineers

    Main App Layer External Libraries Layer Internal Modules Layer 35 STRATEGY |
  21. Our Strategy Multiple milestones with parallel execution by 2 engineers

    Main App Layer External Libraries Layer Internal Modules Layer 36 STRATEGY |
  22. Our Strategy Multiple milestones with parallel execution by 2 engineers

    Main App Layer External Libraries Layer Internal Modules Layer 37 STRATEGY |
  23. Our Strategy Multiple milestones with parallel execution by 2 engineers

    Main App Layer External Libraries Layer Internal Modules Layer Milestone 1: The Main App Migration • Generate .xcodeproj via Tuist • Con fi rm the feasibility of Tuist migration at early stage • Ensure rollback option if using Tuist is not feasible 38 STRATEGY |
  24. Our Strategy Multiple milestones with parallel execution by 2 engineers

    Main App Layer External Libraries Layer Internal Modules Layer Milestone 2: External Libraries Migration • Migrate all libraries from CocoaPods to Swift Packages • Con fi rm the version compatibility of Swift packages libraries 39 STRATEGY |
  25. Our Strategy Multiple milestones with parallel execution by 2 engineers

    Main App Layer External Libraries Layer Internal Modules Layer Milestone 3: Internal Modules Migration • Migrate all modules from the .podspec de fi nition to the Tuist manifest fi le de fi nition • The fi nal step is to migrate everything away from CocoaPods 40 STRATEGY |
  26. Our Strategy Multiple milestones with parallel execution by 2 engineers

    Main App Layer External Libraries Layer Internal Modules Layer Timeline Main App Layer External Libraries Layer Internal Modules Layer 41 STRATEGY |
  27. Our Strategy Multiple milestones with parallel execution by 2 engineers

    Main App Layer External Libraries Layer Internal Modules Layer Timeline Main App Layer External Libraries Layer Internal Modules Layer 42 STRATEGY |
  28. Our Strategy Multiple milestones with parallel execution by 2 engineers

    Main App Layer External Libraries Layer Internal Modules Layer Timeline Main App Layer External Libraries Layer Internal Modules Layer 43 STRATEGY |
  29. Our Strategy Multiple milestones with parallel execution by 2 engineers

    Main App Layer External Libraries Layer Internal Modules Layer Timeline Main App Layer External Libraries Layer Internal Modules Layer 44 STRATEGY |
  30. Challenge: Zero-Downtime Migration It is critical to keep both feature

    development and delivery going: • 15+ engineers are working on the same iOS repository • Actively developing and delivering new features every week 45 STRATEGY |
  31. How did we keep feature development going? • Introduced automated

    tools to convert from .podspec to Tuist manifest fi le • .podspec fi le can be used as a source of con fi guration until the migration completion 46 STRATEGY | Challenge: Zero-Downtime Migration How did we keep feature delivery going? • Tests for migration: prepared guardrail tools to avoid and detect regressions • Established feedback loops to fail fast with minimum scope
  32. Main App Layer External Libraries Layer Internal Modules Layer Milestone

    1 Milestone 2 Milestone 3 Timeline 48 EXECUTION |
  33. Milestone 1: The Main App Migration Before Our main app

    project fi le (project.pbxproj) was versioned on Git The Xcode project was integrated into the Xcode workspace generated by CocoaPods SmartNews.xcworkspace (by CocoaPods) Pods.xcodeproj Internal Module 1 External Lib A Internal Module 2 External Lib B SmartNews.xcodeproj Main App Target 49 EXECUTION |
  34. Milestone 1: The Main App Migration After Generates the Xcode

    project by Tuist and integrates it into the Xcode workspace (still generated by CocoaPods) SmartNews.xcworkspace (by CocoaPods) Pods.xcodeproj Internal Module 1 External Lib A Internal Module 2 External Lib B SmartNews.xcodeproj (by Tuist) Main App Target 50 EXECUTION |
  35. Milestone 1: The Main App Migration After Generates the Xcode

    project by Tuist and integrates it into the Xcode workspace (still generated by CocoaPods) No more merge con fl icts on project fi le 🎉 SmartNews.xcworkspace (by CocoaPods) Pods.xcodeproj Internal Module 1 External Lib A Internal Module 2 External Lib B SmartNews.xcodeproj (by Tuist) Main App Target 51 EXECUTION |
  36. Milestone 1: The Main App Migration Migration Flow Initialized a

    bare minimum Tuist project structure with dummy naming import ProjectDescription let workspace = Workspace( name: "App-Workspace", projects: [ "./TuistDummyApp", "./Modules", ] ) import ProjectDescription let project = Project( name: "TuistDummyApp", packages: [], targets: [ .target( name: "TuistDummyApp", destinations: .iOS, product: .app, bundleId: "io.tuist.TuistDummyApp", sources: [ /* sources here */ ], ), ] ) Workspace.swift Project.swift 52 EXECUTION | Initialized Tuist project structure
  37. Milestone 1: The Main App Migration Migration Flow • Synced

    the original project structure one by one e.g., fi le references, target settings, build phases, project settings, etc. • In parallel, fi xed and refactored the original project structure to make the migration smooth e.g., unused compiler fl ags, dangling fi le pointers, redundant settings 🤯 53 EXECUTION | Initialized Tuist project structure Synced project structure one by one
  38. Milestone 1: The Main App Migration Migration Flow • Developed

    a project structure comparison tool
 based on OSS “xcdiff” which outputs a side-by- side HTML diff report on CI pipelines (https://github.com/bloomberg/xcdi ff ) • Could catch unsynced con fi gurations while migrating and detect regressions 54 EXECUTION | Initialized Tuist project structure Synced project structure one by one Developed a comparison tool
  39. Milestone 1: The Main App Migration Migration Flow Replaced with

    Tuist generated one Initialized Tuist project structure Synced project structure one by one Developed a comparison tool 55 EXECUTION | Done! ✅ Successfully replaced by leveraging our comparison tool
  40. Main App Layer External Libraries Layer Internal Modules Layer Milestone

    1 Milestone 2 Milestone 3 Timeline 56 EXECUTION |
  41. Milestone 2: External Libraries Migration Investigation • 60+ external libraries

    were managed by CocoaPods • Investigated each library and separated into 4 cases: SPM is supported in the current version: Easy 🎉 SPM is supported in newer version: Need to update version 👌 XCFramework is distributed: Integrate as a binary target 😩 SPM is not supported: Either remove, fork or embed code 😱 57 EXECUTION |
  42. Milestone 2: External Libraries Migration Investigation • 60+ external libraries

    were managed by CocoaPods • Investigated each library and separated into 4 cases: SPM is supported in the current version: Easy 🎉 SPM is supported in newer version: Need to update version 👌 XCFramework is distributed: Integrate as a binary target 😩 SPM is not supported: Either remove, fork or embed code 😱 58 EXECUTION | 10% 23% 11% 56%
  43. Milestone 2: External Libraries Migration Dif fi culties of XCFramework

    (23% in our case) Integrate XCFrameworks (binary target) in SPM is sometimes tricky 😩 e.g., need to fi gure out transitive dependencies and declare them .binaryTarget( name: "XCFrameworkLibrary", // <- XCFramework format library url: "XXXXXXXX", checksum: "YYYYYY" ), .target( name: "XCFrameworkLibraryWrapper", // <- Prepares a wrapper target to declare transitive dependencies dependencies: [ .target(name: "XCFrameworkLibrary"), // <- Original XCFramework target .product(name: "TransitiveDep1", package: "transitive-dep-1"), .product(name: "TransitiveDep2", package: "transitive-dep-2"), .product(name: "TransitiveDep3", package: "transitive-dep-3"), ] path: "Wrappers/XCFrameworkLibrary" // Prepares a dummy directory structure ), 59 EXECUTION |
  44. Milestone 2: External Libraries Migration Dif fi culties of XCFramework

    (23% in our case) Integrate XCFrameworks (binary target) in SPM is sometimes tricky 😩 e.g., need to fi gure out transitive dependencies and declare them .binaryTarget( name: "XCFrameworkLibrary", // <- XCFramework format library url: "XXXXXXXX", checksum: "YYYYYY" ), .target( name: "XCFrameworkLibraryWrapper", // <- Prepares a wrapper target to declare transitive dependencies dependencies: [ .target(name: "XCFrameworkLibrary"), // <- Original XCFramework target .product(name: "TransitiveDep1", package: "transitive-dep-1"), .product(name: "TransitiveDep2", package: "transitive-dep-2"), .product(name: "TransitiveDep3", package: "transitive-dep-3"), ] path: "Wrappers/XCFrameworkLibrary" // Prepares a dummy directory structure ), 60 EXECUTION |
  45. Milestone 2: External Libraries Migration Dif fi culties of SPM

    not-supported libraries (10% in our case) SPM not-supported libraries are archived or not maintained in many cases • Remove from our app: decouple all uses of the library from our code • Fork the repository: support SPM by ourselves • Embed the library into our code: treat it as local packages or private classes 61 EXECUTION |
  46. Milestone 2: External Libraries Migration Hybrid Approach • Adopted hybrid

    integration approach to avoid big-bang release of migrated libraries at the last phase 62 EXECUTION |
  47. Milestone 2: External Libraries Migration Hybrid Approach • Adopted hybrid

    integration approach to avoid big-bang release of migrated libraries at the last phase • Gradually rolled out Swift Packages via CocoaPods by using OSS tool “cocoapods-spm” (https://github.com/trinhngocthuyen/cocoapods-spm) pod_pkg 'SwiftRichString', '3.7.2', :spm_path => 'Tuist/.build/checkouts/SwiftRichString', :spm_prd_ready => true Part of extended Pod fi le: 63 EXECUTION |
  48. Milestone 2: External Libraries Migration Hybrid Approach • Adopted hybrid

    integration approach to avoid big-bang release of migrated libraries at the last phase • Gradually rolled out Swift Packages via CocoaPods by using OSS tool “cocoapods-spm” (https://github.com/trinhngocthuyen/cocoapods-spm) pod_pkg 'SwiftRichString', '3.7.2', :spm_path => 'Tuist/.build/checkouts/SwiftRichString', :spm_prd_ready => true Part of extended Pod fi le: 64 EXECUTION |
  49. Milestone 2: External Libraries Migration Hybrid Approach • Adopted hybrid

    integration approach to avoid big-bang release of migrated libraries at the last phase • Gradually rolled out Swift Packages via CocoaPods by using OSS tool “cocoapods-spm” (https://github.com/trinhngocthuyen/cocoapods-spm) pod_pkg 'SwiftRichString', '3.7.2', :spm_path => 'Tuist/.build/checkouts/SwiftRichString', :spm_prd_ready => true Part of extended Pod fi le: 65 EXECUTION |
  50. Milestone 2: External Libraries Migration Downside of Hybrid Approach •

    2x Redundant time to solve packages might be taken in the worst case • Xcode tries to download transitive dependencies again, even though Tuist/SPM already resolved that • Trade-off: Safer approach v.s. Time until Xcode is ready 66 EXECUTION |
  51. Milestone 2: External Libraries Migration Downside of Hybrid Approach •

    2x Redundant time to solve packages might be taken in the worst case • Xcode tries to download transitive dependencies again even though Tuist/SPM already resolved that • Trade-off: Safer approach v.s. Time until Xcode is ready 67 EXECUTION |
  52. Main App Layer External Libraries Layer Internal Modules Layer Milestone

    1 Milestone 2 Milestone 3 Timeline 68 EXECUTION |
  53. Milestone 3: Internal Modules Migration Problem Not realistic to migrate

    one by one manually because: • 70+ internal modules • Other engineers might keep editing existing modules con fi guration • Other engineers might keep adding new modules Let other engineers continue development without worrying about migration 69 EXECUTION |
  54. Milestone 3: Internal Modules Migration Our Solution Developed an automated

    migration tool named “podspec2tuist” • Convert .podspec fi le to Tuist con fi guration fi le automatically • Other engineers can keep editing .podspec fi le as usual • Other engineers can keep adding new modules with .podspec fi le 70 EXECUTION |
  55. Milestone 3: Internal Modules Migration podspec2tuist Read *.podspec fi les

    Generate umbrella headers Parse and transform metadata for Tuist Generate prepare commands Generate {Module}.swift Generate Modules.swift Integrate workspace Generate {Module}.swift Generate {Module}.swift Generate {Module}.swift 71 EXECUTION |
  56. Milestone 3: Internal Modules Migration podspec2tuist Read *.podspec fi les

    Generate umbrella headers Parse and transform metadata for Tuist Generate prepare commands Generate {Module}.swift Generate Modules.swift Integrate workspace Generate {Module}.swift Generate {Module}.swift Generate {Module}.swift 72 EXECUTION |
  57. Milestone 3: Internal Modules Migration podspec2tuist Read *.podspec fi les

    Generate umbrella headers Parse and transform metadata for Tuist Generate prepare commands Generate Modules.swift Integrate workspace Generate {Module}.swift 73 EXECUTION |
  58. Milestone 3: Internal Modules Migration podspec2tuist Read *.podspec fi Generate

    umbrella headers Parse and transform metadata for Tuist Generate prepare commands Generate {Module}.swift Generate Modules.swift Integrate workspace Generate {Module}.swift Generate {Module}.swift Generate {Module}.swift 74 EXECUTION |
  59. Milestone 3: Internal Modules Migration podspec2tuist Read *.podspec fi Generate

    umbrella headers Parse and transform metadata for Tuist Generate prepare commands Generate {Module}.swift Generate Modules.swift Integrate workspace Generate {Module}.swift Generate {Module}.swift Generate {Module}.swift 75 EXECUTION |
  60. Milestone 3: Internal Modules Migration podspec2tuist Read *.podspec fi les

    Generate umbrella headers Parse and transform metadata for Tuist Generate prepare commands Generate {Module}.swift Generate Modules.swift Integrate workspace Generate {Module}.swift Generate {Module}.swift Generate {Module}.swift 76 EXECUTION |
  61. Milestone 3: Internal Modules Migration IPA validator/comparator Developed IPA fi

    les validation and comparison tool to detect regressions before/after migration 79 EXECUTION |
  62. Milestone 3: Internal Modules Migration IPA validator/comparator Developed IPA fi

    les validation and comparison tool to detect regressions before/after migration • Check dynamic frameworks: prevent crash if missing, and launch time increases 80 EXECUTION |
  63. Milestone 3: Internal Modules Migration IPA validator/comparator Developed IPA fi

    les validation and comparison tool to detect regressions before/after migration • Check bundle fi les: prevent crash or unexpected behavior 81 EXECUTION |
  64. Milestone 3: Internal Modules Migration IPA validator/comparator Developed IPA fi

    les validation and comparison tool to detect regressions before/after migration • Check bundle fi les: prevent crash or unexpected behavior • Check resource fi les: prevent crash if missing 82 EXECUTION |
  65. Main App Layer External Libraries Layer Internal Modules Layer Milestone

    1 Milestone 2 Milestone 3 Timeline All Done! ✅ 83 EXECUTION |
  66. Internal Rollout • At SmartNews, we have an internal app

    (Early Access App) to rollout in-development features to employees before public releases • Leveraged the Early Access App at each milestone to check the migrated app • Can fail fast if something goes wrong (We found a few crashes and could fi x it before public release 👌) • Can monitor archived IPA fi le meta data like IPA fi le size changes or missing frameworks 85 ROLLOUT |
  67. Public Rollout Results • After validating the internally rolled-out Early

    Access App, it was fi nally rolled out to all public users! • No critical issues on public release thanks to a bunch of guardrail tools and internal testing 🎉 🎉 🎉 86 ROLLOUT |
  68. Public Rollout Results • After validating the internally rolled-out Early

    Access App, it was fi nally rolled out to all public users! • No critical issues on public release
 thanks to a bunch of guardrail tools and internal testing 🎉 🎉 🎉 87 ROLLOUT | Success! ✅
  69. Takeaways • Custom automation tools enable small teams to migrate

    massive and historic apps without interrupting ongoing developer work fl ows • IPA-level validation and project di ff tools are essential for ensuring stability during large-scale legacy app migrations • Gradual hybrid integration minimizes deployment risks by enabling early validation of new libraries formats in real environments 89 TAKEAWAYS
  70. Thank you for listening! Akira Fukunaga Senior Software Engineer (iOS)

    @ SmartNews LinkedIn/GitHub: @kagemiku Otávio Lima Staff Software Engineer (iOS) @ SmartNews LinkedIn/GitHub: @otaviolima 90 Any questions? Feel free to reach out!