QUEST LOG · December 20, 2025· UPDATED October 29, 2026

Don't Be Too Independent: Untangling Swift's Dependencies and Architecture

In the Swift world we've gotten used to a powerful Xcode and a clean syntax, but as a project grows, an invisible monster starts prowling through the code: dependencies that have spun out of control

Lately I've been building several iOS apps. When a project is small you don't really feel it, but mine have now grown to hundreds of files. It's time to deal with this, or development speed goes right down the drain.

The development-speed problem mostly comes from not knowing which Swift files a change to a given file will affect, while the project won't even compile in the middle of that change. That's exactly when I want a solid static analysis sidekick.

# So why are Swift dependencies such a "mess"?

Unlike Java, Python, or Rust, Swift's design philosophy has every file inside the same Module share a namespace by default.

  1. The missing import

In most languages, if you want to use a class from another file, you have to import it explicitly. But within the same Swift Target, you can access any internal symbol belonging to that Target without importing anything. That convenience is a double-edged sword:

  • Pro: fast development, less boilerplate.
  • Con: dependencies between files turn into a tangled mess (spaghetti code). Without compiling, it's hard to tell at a glance which other Swift files a change to User.swift will affect.
  1. Scripting and the xcrun black box

Xcode's build process is a heavily encapsulated black box. When you call swiftc through xcrun, the compiler scans the entire directory to build its symbol table. That's great for the final product, but extremely unfriendly when a developer wants to do an "architecture review":

  • Build times explode: because dependencies are implicit, incremental builds frequently stop working.
  • Hard to script: if you try to write a script that checks for circular dependencies, you'll find Swift simply doesn't offer a lightweight tool for "just look at the relationships, don't compile."

# Making the structure visible

I recently built a tool called `swift-deps-map` ↗. In this post I want to talk about why I think this matters, and how I started from "lightweight visualization" while planning a path toward "rigorous static analysis."

Why build this tool? The original motivation

Why reinvent the wheel when you could just use someone else's? Because the most accurate dependency analysis tools out there (such as those built on IndexStoreDB https://github.com/swiftlang/indexstore-db ↗ ) all share one prerequisite: the project has to compile successfully. But in real development, the moment we most need to see the dependency graph is often when the code "doesn't run yet" or is "mid-restructure." What I want to fill is that gap before it compiles.

The moment we most need to see the dependency graph is often when the code "doesn't run yet" or is "mid-restructure." What I want to fill is that gap before it compiles.

# Current strength: pre-compilation visualization

Right now, swift-deps-map's selling points are "fast" and "intuitive":

  • Zero-setup scanning: doesn't depend on an Xcode environment or build artifacts; all it needs is the source code.
  • Visual feedback: exports to Mermaid and Cytoscape (JSON). When you can spot the arrow that shouldn't be there at a glance, your motivation to refactor shoots way up.
  • Lightweight gatekeeper: it fits nicely in a pre-commit hook or the early stages of CI, running a first-pass "health check" before the expensive build kicks off.

# Toward rigor: the tool's future

Today swift-deps-map relies on fast static scanning, but I want to add a few features to make the tool more broadly useful

1.Adding lint rules (Strict Mode)

Right now the tool only "presents" the facts. Going forward, I plan to add lint rules.

  • Cycle detection: when A depends on B and B depends on A, spit a red warning straight into the terminal.
  • Depth limits: flag a file when its dependency depth exceeds a configured threshold.

2.Bringing in SwiftSyntax (semantic parsing)

To get past the limits of regex (like false positives in comments and strings), I plan to bring in SwiftSyntax. It's still pre-compilation, but it can precisely understand Swift's abstract syntax tree (AST) and tell real class instantiations apart from mere Protocol declarations.

3.Indexing integration (maximum rigor)

Finally, I plan to add optional support for IndexStoreDB. Once the project has been built, the tool can read the index database Xcode generates.

  • 100% accuracy: backed by real data from the compiler.
  • Cross-module analysis: not just inside a Target, but also the deeper relationships with third-party libraries.

# You can only make code beautiful once you can see the architecture

A lot of the time, messy code isn't a skill problem (well, maybe the AI's skills are the problem) but a matter of code dependencies you can't see

I want Swift developers to be able to run it as a simple Python script, with something like a cytoscape-powered supervisor that tells you when a file is a total busybody with its nose in everything

Shell
uvx --from . swift-deps-map --root . --graph-format cytoscape --graph-output deps.cyto.json

It's already published on PyPI https://pypi.org/project/swift-deps-map/ ↗

The code is open source too: https://github.com/kidneyweakx/swift-deps-map ↗

Feel free to play with it, try it out, open an issue, or chat with me about how to make Swift dependencies more elegant

Apps I ship

Free on iOS and Android.

Solid(ar)ity

Solid(ar)ity

A privacy-first identity wallet: zero-knowledge proofs and signed cards swapped face to face, no central server.