Claude Code Swift App: How to Migrate an Older App from GCD to Swift Concurrency with the CFA AI Toolkit
The short answer
Yes, Claude Code can move an older Swift app from GCD (Grand Central Dispatch, the DispatchQueue and completion-handler style of concurrency) to Swift Concurrency, but it needs a plan that protects what the app already does. The free, open-source CFA AI Toolkit includes a Legacy GCD migration skill that gives Claude Code that plan: record the app's current behaviour first, move one feature at a time into a clear structure, convert that feature to async/await, then re-test. Copy the toolkit's skills into ~/.claude/skills, open your project and ask:
Using CFA, migrate this legacy GCD codebase to Swift Concurrency.
💡 What this article covers
Why a find-and-replace migration goes wrong, how to add the skill to Claude Code, the four passes it asks Claude Code to work through, the behaviour contract it writes before touching code, and how you check the result. CFA stands for Cooperative Feature Architecture, a way of laying out an iOS app in Xcode, and it's the structure your app ends up in.
Why "just swap DispatchQueue for Task" breaks apps
It's tempting to ask an AI to replace every DispatchQueue.async with Task { } and call it done. The toolkit's README warns against exactly that. In an older app, a queue often quietly protects something important: the order saves happen in, shared data that two threads mustn't touch at once, what happens when a request is cancelled, how errors reach the screen, and the rule that an old network response mustn't overwrite a newer one.
Swap the syntax and those guarantees can disappear while the app still compiles. The skill puts it plainly: migrate by preserving the product's behaviour, not by translating concurrency syntax.
What the Legacy GCD migration skill is
It's one of the toolkit's eight AI skills. An AI skill is a folder of instructions an AI coding tool can read. This one is for modernising a whole legacy codebase that uses GCD, OperationQueue or callbacks. Its own description says it isn't meant for converting one small, isolated API.
It's also a full architecture migration. As well as replacing the concurrency code, it moves the app into CFA, where every screen has its own ViewModel, business rules live in a Feature Manager, and data loading and saving sit behind repositories. The skill says a pure GCD app is a valid starting point. If your app already uses modern Swift and you only want the CFA structure, the toolkit has a separate CFA Architecture Adoption skill for that.
Add the skill to Claude Code
The README says to copy all eight folders from the toolkit's skills folder, without renaming them, into the folder your tool reads skills from. For Claude Code, that folder is commonly ~/.claude/skills. Download or clone the toolkit repository, then, from inside that folder:
mkdir -p ~/.claude/skills
cp -R skills/* ~/.claude/skills/
Start a new Claude Code conversation and send the toolkit's check message:
I have installed the CFA Toolkit for iOS from
https://github.com/3DaysOfSwift/cooperative-feature-architecture.
Do you have access to create and maintain iOS projects with CFA?
Two honest notes. The newest download on the Releases page, v0.2.0-beta.2, is an older pre-release with four skills, so copy from the repository's skills folder to get all eight, including this one. And the cfa Terminal command comes with the toolkit's installer, such as the Codex plugin install, so a copy-only install like this one may not have it. The migration skill itself doesn't need it.
Before you start: commit and run your tests
A migration touches a lot of files, so commit your project to Git first and keep the work on its own branch. Then run your existing test suite. The skill asks Claude Code to record which tests pass, fail or can't run before anything changes. A test that already fails isn't an excuse to ignore what it covers; it's something to look into.
Pass 1: Record what the app does today
Claude Code is told to list every queue, barrier, dispatch group, semaphore, operation and callback in the app. For each one it notes what it protects:
which work has to start at the same time, and which must stay in order;
which shared data the queue keeps safe;
whether the work is durable, replaceable, cancellable or long-running;
who needs the result, and how errors and cancellation behave today.
It also writes a file called MIGRATION_BEHAVIOUR_CONTRACT.md in your project. Each line is one plain-English sentence about something a user can see, with an ID such as BEH-001. The skill's own example of a good requirement is: "When a saved weight entry is deleted, it no longer appears in History and the projection is recalculated from the remaining entries." A line like "deleteEntry() calls refreshSnapshot()" is the wrong kind, because it only repeats today's code and would block a safe redesign.
Where important behaviour has no reliable test, Claude Code is asked to add a characterisation test first, which simply records what the app does now. Destructive actions, saved data, purchases, ordering and cancellation come first.
Pass 2: Move one feature into CFA
Next, Claude Code restructures one complete feature without changing its concurrency code yet. Each SwiftUI View gets its own ViewModel, the feature's decisions move into its Feature Manager, shared objects are created in the AppModel, and saving and loading go behind a repository. Keeping this pass separate means a problem can be traced to either the new structure or the new concurrency, not both at once.
Pass 3: Convert that feature to Swift Concurrency
Only now does the feature's background work become async functions that tests can call and await directly. Claude Code is asked to choose a deliberate rule for each kind of operation: saves that must happen one after another, actions that are refused while one is already running, requests where only the latest result counts, and work that can run independently. Every independent Task needs a named owner and a cancellation plan.
Here's the kind of change involved, as a simplified illustration rather than code from the toolkit:
// Before: a callback on a queue.
func loadEntries(completion: @escaping ([Entry]) -> Void)
// After: an async function the ViewModel and tests can await.
func loadEntries() async throws -> [Entry]
When an older API only offers a callback, the skill allows a checked continuation as a bridge, but only after proving it resumes exactly once on every path, including failure, cancellation and early exit. It also reminds Claude Code that async doesn't mean "off the main thread", and that compiler warnings shouldn't be silenced with unchecked annotations that have no justification.
Pass 4: Re-test, compare and record the gaps
After each pass, Claude Code is told to build every affected target, run the original tests and the new ones, and update the evidence for each requirement in the contract. Every difference gets exactly one label:
Regression: the new code breaks a requirement.
Intended approved change: someone deliberately decided to change the behaviour.
Existing defect exposed: the migration revealed a bug that was already there.
Environment or flaky evidence: the test setup caused it, and that was investigated.
Unknown: nobody knows why yet.
A regression or an unknown blocks the pass from being finished. The skill is firm that a cleaner architecture never justifies changing what the app does. Any legacy GCD code that remains is listed, with the reason it stays.
Check the result yourself
The skill expects people to test too. Its contract asks for a manual regression run after the architecture milestone and again after the GCD migration, on real devices when the product needs it, with the tester, date, build and result written down. So after Claude Code reports a pass, open the project in Xcode, build and run it, and walk through the journeys in your contract yourself. The skill admits the process isn't infallible; it's a disciplined way to spot what broke and why.
If you'd like to see a finished example, Metro Mate iOS is an open-source app the toolkit lists as migrated into CFA and Swift Concurrency.
What to Remember
💡 What to Remember
Copy the eight folders from the toolkit's skills folder into ~/.claude/skills, then ask "Using CFA, migrate this legacy GCD codebase to Swift Concurrency."
The skill migrates behaviour, not syntax: replacing DispatchQueue.async with Task { } isn't the goal.
Claude Code records the app's behaviour in MIGRATION_BEHAVIOUR_CONTRACT.md before changing code.
It works one feature at a time: structure first, then async/await, then re-test.
Regressions and unexplained differences block progress, and you still run the app and test it yourself.
Is this the right skill for a small change?
Probably not. The skill's description says it's for migrating a legacy codebase, not for a small isolated API conversion. For a single function, an ordinary request to Claude Code, guided by the toolkit's Swift Code Quality skill, is a better fit.
Will my app end up using CFA?
Yes. This skill treats CFA as the target architecture, so features end up with their own ViewModels, Feature Managers and repositories, wired together in the AppModel. If you want to keep your current architecture, this isn't the skill to use.
Is the CFA AI Toolkit free?
Yes. It's open source under the MIT licence, so you can use, change and share it, including in commercial apps.
Related articles
About Us

3 Days of Swift Concurrency is created by Matthew Thomas, an iOS developer building in public since 2022. He publishes the free, MIT-licensed CFA AI Toolkit and reference apps on GitHub. Visit 3DaysOfSwiftConcurrency.com to follow along. The paid training programme is still being built: a soft launch is targeted for 1 December 2026, but that date depends on deadlines being met and isn't guaranteed.




Comments