One year ago I wrote Breaking Free from Xcode: Unlock AI's Full Potential in iOS. At that time, getting an agent to build, run and check an iOS app took a pile of MCP servers, VS Code extensions and some patience.

A lot has changed since. Models are much smarter. Xcode ships with agents built in. Claude Code can control the simulator on its own: it builds, launches, taps through screens and checks the result with screenshots.

So, is iOS development solved?

For prototypes and MVPs, mostly yes. You can describe an idea and get the app “one-shotted”. But if your software has to be as reliable as running water (a phrase I borrow from Uber's mission: "transportation as reliable as running water, everywhere, for everyone"), two principles still matter: design and harness.

Principle 1: You still own the design

This is the biggest shift I've seen. While building a feature, I don't keep in my head which method calls which method, which file it lives in, or how state moves through each type. The agent handles that.

That doesn't mean you can stop reviewing code. Where quality matters, you still read PRs. But your attention moves up a level. You own the design of the whole system, not just one module, so architecture is now the most valuable skill you have.

In 2025 you held the code in your head. In 2027 you hold the design.

At the design level you are pretty high in the hierarchy of components, that’s why visualization helps. A diagram with the right amount of detail shows how the pieces fit together, which is hard to see in plain text. The C4 model gives you that visualization at a few zoom levels.

Last year I suggested putting the project architecture in AGENTS.md. I've changed my mind: a dedicated docs/architecture/ folder is a better place. You don't need the entire architecture loaded into the context every time you do something. Instead, AGENTS.md keeps a reference to docs/architecture/context.md, so the agent can retrieve it when on demand. Luis Herrera's deep dive walks through C4 levels on a sample system.

  1. System Context. Your app, its users and the outside systems it talks to: the backend, push notifications, analytics. This is context.md, and it links to the containers.

  2. Containers. Conventional C4 container usually stands for a piece of software that can de deployed, like a micro-service or app. I had to adapt the original idea: a container is a feature, a flow or an SDK module, like Feed, Checkout or Networking. containers.md shows how they connect, explains what each one owns and what it may depend on, and links to components where they exist.

  3. Components. The parts inside a container. Only draw these for complex areas, like a post composer with drafts, uploads and retries, or a sync engine. A stateless FeedService with a single getFeed() call doesn't need a component diagram. C4 itself says you only need the levels "that add value".

  4. Code. No diagrams. Component docs just link to the specific files. The code is the source of truth, and an agent can read it faster than you can draw it.

Think about C4 as a pattern that generalizes an approach to structure progressive complexity of the architecture.

Here's what that looks like for a simple feed app:

AGENTS.md ← rules + link to context.md docs/architecture/ context.md ← system context, links to containers.md containers.md ← container diagram, links to components components/composer.md ← complex flow only, links to code 

Here's the container diagram from containers.md, as Mermaid source and rendered:

Mermaid flowchart source next to its rendered container diagram: User, App, Features, Persistence, Networking and Backend API.

Use Mermaid. It's plain text, so it shows up in diffs, GitHub renders it, and agents read it as easily as code.

This is what I call discoverable architecture. The agent doesn't load the whole architecture up front. AGENTS.md points it to context.md, and from there it follows links down only as far as the task needs. Humans onboard the same way.

Keep the diagrams alive

Documentation that goes stale is worse than none. So let the agent maintain it:

  • Add a skill that updates the affected docs and diagrams in docs/architecture/ after a change. I use my c4-architecture-docs skill for this.
  • Add a hook that triggers that skill at the end of every agent turn. In Claude Code, a Stop hook runs when the agent finishes. If Swift files changed, it sends the agent back to update the docs:
{ "hooks": { "Stop": [{ "hooks": [{ "type": "command", "command": "\"${CLAUDE_PROJECT_DIR}\"/.claude/hooks/update-architecture.sh" }] }] } } 
#!/bin/bash # Ask the agent to refresh architecture docs once per new set of Swift changes jq -e '.stop_hook_active' >/dev/null && exit 0 cd "$CLAUDE_PROJECT_DIR" || exit 0 changes=$(git diff HEAD -- '*.swift'; git ls-files --others --exclude-standard -- '*.swift') [ -z "$changes" ] && exit 0 stamp="$(git rev-parse --git-dir)/architecture-docs-stamp" hash=$(printf '%s' "$changes" | shasum | cut -d' ' -f1) [ "$hash" = "$(cat "$stamp" 2>/dev/null)" ] && exit 0 echo "$hash" > "$stamp" echo "Swift files changed. Run the c4-architecture-docs skill for the affected folders." >&2 exit 2 

The script needs jq, and don't forget to chmod +x it. The stamp file matters: the docs pass runs once per new set of Swift changes, not after every question you ask.

Principle 2: Harness

Models are far more capable now, but they still need guidance. I regularly see agents get confused by strict concurrency and SwiftUI performance. The code compiles and the tests pass but the produced code can crash, that why your hard skills still matter.

I define these priorities in terms of resources (compute and time):

  1. Automation first. Unit tests, integration tests, CI and linters catch most mistakes before I ever see them.
  2. Agentic review. A separate agent, or at least one with a clean context, reviews the diff. Remember it's not deterministic: the same diff can get different feedback twice. So when the reviewer keeps catching the same kind of mistake, use AI to turn that into a test or a lint rule, and move it up to step 1.
  3. Then me. Once everything is green, I read the code with care. (Ask your agent make small and reviewable PRs)

For agent review guardrails, I use skills. The most useful thing in a skill is a pair of bad and good examples. For concurrency, for example, you can show what blocking Swift's cooperative thread pool looks like:

// ❌ Wraps a callback API with a semaphore: blocks a pool thread func fetchToken() -> String { let semaphore = DispatchSemaphore(value: 0) var token = "" auth.fetchToken { token = $0; semaphore.signal() } semaphore.wait() return token } // ✅ Suspends instead of blocking func fetchToken() async -> String { await withCheckedContinuation { continuation in auth.fetchToken { continuation.resume(returning: $0) } } } 

I've caught agents writing the first version more than once and writing a linter could be an overkill or just flaky, so I put it into a skill, the review agent flags concerns when spot bad examples.

Keep the context lean

Every rule, skill and doc you add competes for the same context window, so load the right context at the right moment. Oleg Efimov explains the cost well in Navigating Claude Code: The Context Window Tax: every token you load is paid for on every turn.

  • Use reusable, discoverable architecture. Consistent patterns mean the agent copies an existing feature instead of reading a guide.
  • Prefer tests and static linters to indeterministic outputs. A failing SwiftLint rule beats a paragraph the agent might skip.
  • Write minimal documentation in code. Clear names and tests explain the code better than comments that drift out of date.
  • Make code-review skills short and specific. Aggregate skills per area of the code: SwiftUI, concurrency, GraphQL schema reviewer — whatever makes sense to separate and ship as independent expertise (skill).

Conclusion

Agents can write the code. What they still can't do is decide what the system should look like, or build the harness that keeps it that way. To recap:

  • Own the design, and keep it in C4 diagrams that both humans and agents can navigate.
  • Let a skill and a hook keep that architecture current.
  • Put automation first, then agent review, then review small diffs yourself.
  • Use skills with good and bad examples where agents struggle.
  • Keep context lean: short AGENTS.md, tests and linters over prose.

iOS development isn't solved. The hard part has just moved up a level.