No File is an Island: Unraveling Swift Dependencies and Architecture
kidneyweakx
December 20, 2025 (Updated: December 21, 2025)
In the world of Swift, we are accustomed to the power of Xcode and clean syntax. However, as a project's scale grows, an invisible monster begins to haunt the codebase: runaway dependencies.
Lately, I've been developing several iOS apps. When a project is small, it’s hardly noticeable, but as my current projects have grown to hundreds of files, it’s time to take action—otherwise, development efficiency is often ruined.
The core problem with development efficiency is not knowing which Swift files will be affected when you want to modify a specific file, only to find that it fails to compile mid-edit. This is why I found myself wishing for a helpful static analysis tool.
# Why are Swift dependencies so "messy"?
Unlike Java, Python, or Rust, Swift’s design philosophy allows all files within the same Module to share a namespace by default.
1. The Disappearing import
In most languages, if you want to use a class from another file, you must explicitly import it. However, under the same Target in Swift, you don't need to import anything to access internal symbols belonging to that same Target. This convenience is a double-edged sword:
Pro: Fast development, reduced redundant code.
Con: Dependencies between files become a tangled mess (Spaghetti code). It is very difficult to see at a glance which other Swift files will be affected by changing
User.swiftwithout compiling.
2. Scripting and the xcrun Black Box
Xcode’s compilation process is a highly encapsulated black box. When calling swiftc via xcrun, the compiler scans the entire directory to build the symbol table. While this is beneficial for the final product, it is extremely unfriendly for developers performing an "architectural audit":
Souring Compilation Times: Because dependencies are unclear, Incremental Builds often fail.
Difficulty in Scripting: If you want to write a script to check for circular dependencies, you'll find that Swift provides no lightweight tools to "look only at relationships, not compilation."
# Making the Structure Visible
I recently developed `swift-deps-map` . In this article, I want to talk about why I think this is important, and how I am starting with "lightweight visualization" while planning a path toward "rigorous static analysis" in the future.
Why write this tool? The Design Philosophy
Why reinvent the wheel if you can use someone else's? Most of the highly accurate dependency analysis tools currently on the market (such as those based on IndexStoreDB https://github.com/swiftlang/indexstore-db ) share a common prerequisite: the project must compile successfully. However, in actual development, we often need to see the dependency graph most when the code "won't run yet" or is "undergoing structural adjustment." I wanted to solve that pre-compilation gap.
We often need to see the dependency graph most when the code "won't run yet" or is "undergoing structural adjustment." I wanted to solve that pre-compilation gap.
# Current Advantage: Pre-compilation Visualization
The selling point of swift-deps-map currently lies in being "fast" and "intuitive":
Zero-threshold Scanning: It does not depend on the Xcode environment or build artifacts; it only needs the source code to scan.
Visual Feedback: Provides Mermaid and Cytoscape (JSON) exports. When you can see that one arrow that shouldn't be there at a glance, the motivation to refactor increases significantly.
Lightweight Gatekeeper: It is suitable for Pre-commit Hooks or the early stages of CI, performing a first wave "health check" before the expensive Build process begins.
# Towards Rigor: The Future of the Tool
Currently, swift-deps-map relies on high-efficiency static scanning, but I plan to introduce several features to enhance its versatility:
1. Introducing Lint Rules (Strict Mode)
Right now, the tool only "presents" the facts. In the future, I plan to add Lint rules.
Cycle Detection: When A depends on B and B depends on A, it will spit out a red warning directly in the terminal.
Depth Limits: Provide prompts when the dependency depth of a file exceeds a set value.
2. Introducing SwiftSyntax (Semantic Parsing)
To move beyond the limitations of Regex (such as misidentifying comments or strings), I plan to introduce SwiftSyntax. This is still Pre-compilation, but it allows for an accurate understanding of the Swift Abstract Syntax Tree (AST), distinguishing between actual class instantiations and mere Protocol declarations.
3. Indexing Integration (Extreme Rigor)
Finally, I plan to add optional support for IndexStoreDB. Once the project is compiled, the tool can read the index database generated by Xcode.
100% Accuracy: Combined with real data from the compiler.
Cross-Module Analysis: Looking not just inside the Target, but also at deep relationships with third-party libraries.
# Visible Architecture Leads to Beautiful Code
Oftentimes, messy code isn't a result of poor technical skill (maybe it's poor AI skill), but rather invisible code dependencies.
I hope to allow Swift developers to run a simple Python script and have a manager like Cytoscape remind you if a file is "living by the sea" (meddling in too many things).
uvx --from . swift-deps-map --root . --graph-format cytoscape --graph-output deps.cyto.json
It is currently published on PyPI: https://pypi.org/project/swift-deps-map/
The code is also open source: https://github.com/kidneyweakx/swift-deps-map
Feel free to play with it, try it out, raise Issues, or discuss with me how to make Swift dependencies more elegant.
Related Articles
Saved by the Logs: How to Recover a Forgotten Keystore Password via IDE Build Logs.
> > The greatest distance in the world is when the password is right in the IDE, but I can't see it
Hexo升級V5排除疑難雜症
> 因為剛好把電腦重灌,然後這個blog就躺在D槽等著我幫他換新的環境,然後他就被我快樂的升級了。
C# 用VID和PID自動連線serialport
> 原本C#的serial port function官方範例,是類似這樣的