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.
- 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.swiftwill affect.
- Scripting and the
xcrunblack 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
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.


