When Build in Public Goes Back Underground
Over the past few months, the community has seen a growing number of cases where AI is used to quickly reverse-engineer commercial products or create pixel-perfect replicas of popular apps. With enough tokens to burn, building a free “shadow version” no longer seems particularly difficult.
At the same time, many indie developers who embrace Build in Public are facing a new challenge: while the original product is still being refined, imitators can take the screenshots and ideas its creator has shared on social media, feed them to AI, and launch a clone before the original is even ready. Some do it out of technical curiosity and experimentation; others simply want to showcase what AI tools can produce. But there are also those who see it as an easy shortcut.
Watching this trend unfold, I can’t help feeling a little uneasy.
Cloning and imitation have existed for decades, but AI has dramatically reduced the time and cost required for implementation. An original creator may spend months discovering an idea worth pursuing, while an imitator can look at the finished result, skip much of the trial and error, and produce something remarkably similar in appearance and functionality in a very short time.
Competition, of course, needs challengers, and AI can be just as helpful to original creators. If lower implementation costs push teams to innovate faster, improve the experience, or offer better value, that is something consumers should welcome. After all, copying an interface or a feature set is not the same as copying the product itself. The design judgments hidden in the details and the deep understanding of users’ needs cannot be reproduced through a pixel-perfect clone alone.
But not every creator has enough time and resources to wait for the market to recognize those differences. For an established commercial product, a few AI-generated clones may not pose much of a threat. For an indie developer still trying to find a place in the market, however, a lead of just a few weeks or months can matter enormously.
Build in Public used to be a way for indie developers to build a community and gather feedback by sharing their ideas, designs, and development process. More importantly, it encouraged people to share ideas, experiments, and even failures with other developers while their products were still rough and unfinished.
That kind of culture didn’t just appear out of nowhere. It was built gradually over the years.
But when publicly shared information also becomes a shortcut for others to find targets to clone and bypass the exploration process, the risks of sharing begin to change. If more developers conclude that staying quiet until a product is ready is safer than sharing openly, Build in Public may genuinely start turning back into Build in Private.
That is what makes me most uneasy.
What we stand to lose may not just be a few original products that never make it to market, but the hard-won culture among indie developers of sharing unfinished ideas with one another—and inspiring each other in the process.
Previous Issue|Newsletter Archive
📢 Sponsor Fatbobman’s Swift Weekly
Promote your product to Swift & iOS developers across:
- Blog: 50,000+ monthly visitors
- Newsletter: 5,000+ subscribers, 50% open rate
Perfect for developer tools, courses, and services.
Enjoyed this issue? Buy me a coffee ☕️
Original
AI-Friendly Frameworks: As Abstraction Moves Up, How Can Verification Keep Pace?
With the rise of AI Coding, a growing number of people have begun to favor the idea that imperative frameworks like UIKit may be better suited to Coding Agents than SwiftUI. The reasoning is intuitive—UIKit’s logic is direct and explicit: create objects, set properties, add them to the hierarchy, establish constraints. Every step operates on something concrete. SwiftUI, by contrast, folds a great deal of implementation detail behind declarative syntax, where state, environment, identity, layout, and rendering together determine the final result. Once the rendered result deviates from expectations, an Agent is left facing a black box, trying to infer what happened internally from limited external observations.
But perhaps the problem is not abstraction itself. Rather, as abstraction moves up, we have neglected the other half of the work: observation and verification. When abstraction hides lower-level details, we need mechanisms at the new level that can make intent explicit and verify that it has been preserved.
Recent Recommendations
Revisiting “Split Isolation”
In a non-Sendable type with no actor isolation of its own, adding @MainActor to only some methods gives the same instance different isolation requirements. Matt Massicotte calls this pattern “split isolation.” Many developers, including Matt himself, have considered it a poor design.
In this article, Matt revisits his earlier assessment: while split isolation should still generally be avoided, it can have value in certain situations. In particular, when only a small amount of independent functionality depends on MainActor, or when migrating to Swift 6 and trying to prevent isolation requirements from rapidly spreading through a large existing codebase, adding @MainActor locally can be a reasonable or even necessary trade-off. Whether to use split isolation depends on whether it is obscuring the type’s true isolation requirements or deliberately expressing a localized constraint.
The Task Is Not the Unit of Work: Ownership, Isolation, and Cancellation Beyond async/await
SwiftUI’s .task automatically cancels a task when the view disappears. That’s convenient, but it can also make it all too easy for developers to tie the lifetimes of various business operations to the UI lifecycle. A disappearing screen, however, does not necessarily mean that the user’s intent has become irrelevant.
Bruno Valente Pimentel points out that a Task is a unit of execution, but not necessarily a unit of product work; an Actor solves isolation, but does not decide who should own the work; structured concurrency can propagate cancellation, but cannot determine whether that parent-child relationship matches the product’s semantics. Rather than first asking “Where should I put my Task?”, Bruno suggests first defining the operation’s intent, owner, resource, and lifetime, and only then choosing the appropriate implementation mechanism. The compiler can help us ensure concurrency safety, but it cannot decide for us when a piece of work truly “stops mattering.”
A SwiftUI calendar with custom days and event indicators
When implementing a calendar UI, drawing the date grid is often not the difficult part. The real challenges lie in the state and edge cases behind it: separating the currently displayed date from the selected range; consistently using Calendar for date arithmetic and day boundaries; correctly handling the first weekday, month boundaries, daylight saving time, and non-Gregorian calendars; making taps and drags work together for continuous range selection; avoiding repeated scans of the event list in every cell; and preventing unnecessary cell view reconstruction. Szymon Lorenz shares what he learned while developing the calendar component for SwiftUIComponents, an open-source project that also provides Android compatibility through Skip Fuse.
SwiftUI Profiling with Instruments: 100,000 Calendar Events
When a SwiftUI app with a large dataset starts to stutter, is the problem in the database, data processing, or view updates? Maciej Szamowski stress-tested hora Calendar with a database containing 100,000 events. Using Instruments’ SwiftUI, Hangs, Hitches, and Time Profiler tools, he found that a large amount of main-thread time during stalls was being spent on SwiftUI graph updates, shifting the optimization focus away from the database and toward data preparation and the view update path.
Both of these calendar articles adopt similar optimization principles for improving rendering performance: avoid making individual cells perform repetitive work that can be handled at a higher level. Prepare data and interaction context upstream → let downstream Views receive values that are as stable and as close to their final presentation form as possible → keep information unrelated to rendering out of the SwiftUI observation/update graph.
Why your fastlane snapshot screenshots are all in English(and how to check every language)
When using fastlane snapshot to generate localized App Store screenshots in bulk, you may encounter a frustrating problem: the language directories are all created correctly, yet every screenshot still appears in English. What makes the issue particularly troublesome is that there is no single cause, making it difficult to diagnose.
Manuel Lorenzo Parejo lists five areas to investigate: when setupSnapshot is called; how snapshot is launched; localize_simulator; whether the App Bundle contains the corresponding .lproj; and whether residual app state is affecting the language setting. He also provides the free shotfleet check command, which recognizes text in screenshots and compares it against the app’s own localized strings, allowing you to verify in bulk whether the generated screenshots actually use the expected language.
Tools
Redlamp: A Native RAW Editor Built by a Photography Enthusiast with AI
Redlamp is an open-source macOS RAW photo editor built with Swift and Metal under the MPL-2.0 license, with a development-panel workflow familiar to Lightroom users. Its creator, Pedro Gomes, is both an engineer and a photography enthusiast who has long wanted more tools that fit the way he prefers to work.
Even though AI has played a significant role in developing the project, Redlamp still reflects clear personal intent and judgment. Pedro chose to make the photo library database a rebuildable index while keeping important information alongside the photos themselves. When measurements showed that an optimization around mask edges was sacrificing detail, he narrowed the scope where the algorithm would be applied. Decisions like these give Redlamp a distinct authorial imprint.
The project’s blog documents many of the ideas and trade-offs that emerged during development, while the repository also makes its collaboration rules and verification mechanisms for coding agents public. Pedro is exploring how multiple agents can participate in the continuous development of complex native software—and how to determine whether the results they deliver are actually reliable.
AnnotateIt: Closing the Loop from Annotation to Create ML Training on Mac
For developers who want to train custom Object Detection models with Create ML, the training itself is often not the most troublesome part. Data preparation is where much of the time goes: the Create ML app itself does not provide annotation tools, so developers typically need another tool to draw bounding boxes and then organize the results into a data format Create ML can use.
AnnotateIt brings these steps together in a single Mac app. After annotating images, you can train an Object Detector locally using Apple Create ML. The resulting model can then be applied to new images and videos to generate suggested annotations, which can be reviewed and corrected manually. For Swift developers, this not only lowers the barrier to experimenting with local Object Detection, but also connects “annotation → training → prediction → correction” into a complete workflow.
In the official parking-lot example, a model trained on 292 images completes in under a minute. The entire process requires neither a Python environment nor a cloud GPU, and the data can remain entirely on the local machine.
InnoDI: Turning the Dependency Graph into a Verifiable Build Artifact
InnoDI, developed by Ethan Parker, is a dependency injection framework built around Swift Macros. It turns the initialization and parameter-passing logic that would otherwise be written manually in an object graph into explicit dependency declarations, while generating container initializers and type-safe test override interfaces. Macros handle local diagnostics, an SPM Build Plugin validates the dependency graph across files and modules, and a companion CLI can inspect and analyze that graph.
InnoSquad’s InnoFlow, InnoRouter, and InnoNetwork follow a similar philosophy, reducing implicit behavior through explicit boundaries and repeatable verification: declare constraints up front wherever possible, and leave runtime behavior to unified execution policies.
In the age of AI Coding, framework authors need to rethink who their consumers are. “Making code easier for developers to write” may no longer be the only top priority. When the cost of learning is no longer borne entirely by human developers, “making the intent and behavior of code easier to verify” is rapidly becoming more important.
SimAgentation: Annotate the iOS Simulator in Your Browser and Hand the Work to AI
When using AI to adjust an interface, accurately expressing “this is what needs to change” can often take more effort than describing the change itself. SimAgentation, developed by citron, lets developers interact with the iOS Simulator from a browser (IOSurface -> VideoToolbox -> WebCodecs), select UI elements or draw a box around a region, and write modification requests directly. The tool sends the feedback, along with element information, source-code search hints, full-screen screenshots, and cropped screenshots, to a coding assistant through MCP, while displaying the processing status in the browser.
The optional SimAgentationPlus adds .simTag() for attaching names, files, and line numbers to views. This shares some similarities with the element-anchor approach I used in FlowCanvas: combining visual output with structured information so that AI can understand the target of feedback more precisely.
Thanks for reading Fatbobman’s Swift Weekly! This post is public so feel free to share it.
当 Build in Public 重新转入地下
过去一段时间,社区里频繁出现用 AI 快速逆向商业产品或像素级复刻热门应用的案例。只要 Token 充足,搭建一个免费的“影子版本”似乎已不再是难事。
与此同时,许多坚持“公开构建”(Build in Public)的独立开发者也遭遇了新的挑战:原创产品尚在打磨之中,一些仿制者仅凭作者在社交媒体上披露的设计截图和思路,便借助 AI 抢先一步上线了“克隆品”。有的源于技术探索与好奇,有的只是想展示 AI 工具的生产力,但也不乏投机取巧的捷径心态。
看着这股风气,我始终感到一丝不安。
复刻与模仿在过去几十年中一直都存在,但 AI 大幅压缩了实现所需要的时间与成本。原创者可能花费数月才找到一个值得做的方向,模仿者却可以观察最终结果,跳过大量试错,在极短时间内做出一个外观和功能都颇为相似的版本。
商业竞争当然需要鲶鱼,AI 对原创者同样有所帮助。如果更低的实现门槛能够促使原创团队加快创新、改善体验,或者提供更具性价比的服务,自然是消费者乐于看到的结果。更何况,复制界面与功能并不等于复制产品本身,那些隐藏在细节中的设计判断和对用户需求的理解,并不是一次像素级复刻就能获得的。
但并不是每个原创者都有足够的时间和资源,等待市场认识到这些差异。对于成熟的商业产品,几个 AI 仿制品或许不足以构成真正的威胁;但对于仍在寻找市场位置的独立开发者,几周甚至几个月的时间差就可能非常重要。
曾经,通过 Build in Public 分享创意、设计与开发过程,是独立开发者建立社区、获得反馈的一种方式。更重要的是,它让大家愿意在产品还不成熟的时候,就拿出自己的想法、尝试甚至失败,与其他开发者交流。
这种氛围并不是天然存在的,而是这些年一点点形成的。
但当公开的信息同时成为别人寻找复刻目标、快速跳过探索阶段的捷径,分享所需要承担的风险也发生了变化。如果越来越多的开发者发现,在产品真正准备好之前,保持沉默比公开分享更加安全,那么 Build in Public 或许真的会重新变成 Build in Private。
这才是最让我不安的地方。
我们失去的可能不只是几个尚未问世的原创产品,而是独立开发者好不容易形成的、愿意彼此展示不成熟想法并相互启发的交流氛围。
如果您发现这份周报或我的博客对您有所帮助,可以考虑通过 Buy Me a Coffee 支持我的创作。
原创
AI-Friendly Framework:当抽象上移,验证如何跟上?
随着 AI Coding 的普及,不少人开始倾向一种观点:UIKit 这样的命令式框架,可能比 SwiftUI 更适合 Coding Agent。理由很直观——UIKit 的逻辑直接明确:创建对象、设置属性、加入层级、建立约束,每一步都有具体操作的实体;而 SwiftUI 把大量实现细节收束在声明式语法之后,状态、环境、身份、布局与渲染共同决定了最终结果。一旦呈现效果偏离预期,Agent 只能对着黑盒,从有限的外部现象去反推内部究竟发生了什么。
但问题或许并不在抽象本身,而在于抽象之后我们漏掉了另一半工作:观察与验证。当抽象隐藏了底层细节,我们必须在新的层级上,补齐能够固定并验证意图的手段。
近期推荐
重审「局部隔离」的利弊与权衡 (Revisiting “Split Isolation”)
在一个本身没有 actor isolation、且 non-Sendable 的类型中,只给部分方法添加 @MainActor,会让同一个实例同时具有不同的隔离要求。Matt Massicotte 将这种模式称为 “split isolation”。包括 Matt 自己在内,不少开发者都认为这并非一个好的设计。
在本文中,Matt 重新审视了自己过去的判断:split isolation 虽然仍应尽量避免,但在特定场景仍有存在价值。尤其在只有少量独立功能依赖 MainActor,或者正在向 Swift 6 迁移、不希望隔离要求迅速扩散到大量既有代码时,局部添加 @MainActor 也可能是一种合理甚至必要的权衡。是否采用 split isolation,关键在于它是在掩盖类型真正的隔离需求,还是在有意识地表达一个局部约束。
超越 async/await:Task 不是业务工作的基本单元 (The Task Is Not the Unit of Work: Ownership, Isolation, and Cancellation Beyond async/await)
SwiftUI 的 .task 会随着视图消失自动取消任务,这很方便,但也很容易让开发者顺手将各类业务操作的生命周期绑定到 UI 的生命周期。不过,页面消失并不意味着用户的意图已经失效。
Bruno Valente Pimentel 指出:Task 是执行单元,却不一定是产品工作的单元;Actor 解决 isolation,却不会决定谁应该拥有这项工作;结构化并发能够传播 cancellation,却无法判断这种父子关系是否符合产品语义。相比先考虑“Task 应该放在哪里”,Bruno 建议首先明确操作的 intent、owner、resource 和 lifetime,然后再选择具体的实现机制。编译器可以帮助我们保证并发安全,但无法替我们决定一项工作什么时候才真正“不再重要”。
SwiftUI 日历组件的边界与状态设计 (A SwiftUI calendar with custom days and event indicators)
实现一个日历 UI,真正麻烦的往往不是画出日期网格,而是处理背后的状态与边界条件:如何将当前显示日期与选择范围分离;统一通过 Calendar 处理日期运算和 day boundary;正确应对 first weekday、跨月份、夏令时以及非 Gregorian Calendar;点击与拖动如何共同完成连续的范围选择;避免每个 cell 重复扫描事件列表;避免不必要的 cell 视图重建等挑战。Szymon Lorenz 分享了他为 SwiftUIComponents 开发日历组件的经验,这个开源项目通过 Skip Fuse 还提供了 Android 端的兼容性。
100,000 个事件下的 SwiftUI 性能排查实战 (SwiftUI Profiling with Instruments: 100,000 Calendar Events)
当一个包含大量数据的 SwiftUI 应用出现卡顿时,问题究竟来自数据库、数据处理,还是视图更新?Maciej Szamowski 使用包含 100,000 个事件的测试数据库对 hora Calendar 进行压力测试,通过 Instruments 的 SwiftUI、Hangs、Hitches 和 Time Profiler,发现卡顿期间大量主线程时间消耗在 SwiftUI graph updates 上,从而将优化重点从数据库转向了数据准备和视图更新路径。
前后两篇有关日历的文章在改善渲染性能上都采用了类似的优化原则:尽量不要让 cell 自己承担那些可以在更高层级完成的重复工作。上层准备数据和交互上下文 → 下层 View 接收尽可能稳定、接近最终展示形式的值 → 与 rendering 无关的信息不要进入 SwiftUI observation/update graph。
排查 fastlane snapshot 多语言截图全部变成英语的问题 (Why your fastlane snapshot screenshots are all in English)
使用 fastlane snapshot 批量生成 App Store 多语言截图时,有时会遇到一个让人头痛的问题:语言目录都正确创建了,截图内容却全部显示为英语。真正麻烦的是,导致这一现象的原因并不唯一,排查起来也比较困难。
Manuel Lorenzo Parejo 列出了五个排查点:setupSnapshot 的调用时机;snapshot 的启动方式;localize_simulator;App Bundle 中是否包含对应的 .lproj;残留的 App 状态是否影响语言设置。同时还提供了免费的 shotfleet check 命令,通过识别截图文字并与 App 自身的本地化字符串进行比对,批量检查生成的截图是否真的使用了预期语言。
工具
Redlamp:一位摄影爱好者借助 AI 打造的原生 RAW 编辑器
Redlamp 是一款采用 Swift 和 Metal 开发的开源 macOS RAW 照片编辑器,使用 MPL-2.0 许可证,沿用了 Lightroom 用户熟悉的显影面板与操作习惯。作者 Pedro Gomes 既是一名工程师,也是一名摄影爱好者,一直希望有更多符合自己使用方式的工具选择。
在这个大量借助 AI 进行开发的项目中,仍能看到鲜明的个人意图与判断:Pedro 选择让照片库的数据库成为可重建的索引,将重要信息保存在照片旁;也会在测量发现蒙版边缘优化损失了细节后,收紧算法的适用范围。这些具体取舍,让 Redlamp 带有鲜明的作者印记。
项目博客记录了不少开发过程中的思考,代码库也公开了面向编程代理的协作规则与验证机制。Pedro 正在探索,如何让多个代理在持续迭代中参与复杂原生软件的开发,以及如何判断它们交付的成果是否可靠。
AnnotateIt:在 Mac 上完成从标注到 Create ML 训练的闭环
对想用 Create ML 训练自定义 Object Detection 模型的开发者来说,训练本身往往不是最麻烦的部分。真正耗时的是数据准备:Create ML 应用本身不提供标注功能,开发者通常需要借助其他工具画框,再将结果整理成 Create ML 可以使用的数据格式。
AnnotateIt 把这些步骤放进了同一个 Mac 应用。完成图片标注后,可以直接通过 Apple Create ML 在本地训练 Object Detector;训练得到的模型又可以用于新的图片和视频,辅助生成标注并由人工继续修正。对于 Swift 开发者而言,它不仅降低了尝试本地 Object Detection 的门槛,也把「标注 → 训练 → 预测 → 修正」连接成了一套完整的工作流。
在官方的停车场示例中,292 张图片的模型训练不到一分钟即可完成。整个过程不需要配置 Python 环境或云端 GPU,数据也可以始终留在本机。
InnoDI:让依赖图成为可验证的构建产物
Ethan Parker 开发的 InnoDI 是一套基于 Swift Macro 的依赖注入框架。它将对象图中原本需要手工编写的初始化与参数传递逻辑转化为显式的依赖声明,并生成容器初始化器和类型安全的测试替换接口。Macro 负责局部诊断,SPM Build Plugin 验证跨文件、跨模块的依赖图,配套 CLI 则可以对这张图进行检查和分析。
InnoSquad 的 InnoFlow、InnoRouter 和 InnoNetwork 也延续了类似思路,通过明确边界和可重复验证来减少隐式行为:把能够声明的约束提前声明,把运行时行为交给统一的执行策略管理。
在 AI Coding 时代,框架作者需要重新思考框架的消费者。“让开发者更容易编写代码”或许已不再是唯一的首要目标。当学习成本不再完全由人类开发者承担,“让代码的意图和行为更容易被验证”的重要性正在迅速提高。
SimAgentation:在浏览器中为 iOS 模拟器标注并交办 AI
使用 AI 调整界面时,如何准确表达“这里需要修改”,往往比描述修改本身更费力。citron 开发的 SimAgentation 让开发者在浏览器中操作 iOS 模拟器(IOSurface -> VideoToolbox -> WebCodecs),选中界面元素或框选区域,直接写下修改意见。工具会将反馈连同元素信息、源码搜索线索、整屏截图和局部截图,通过 MCP 交给编码助手,并在浏览器中显示处理状态。
可选的 SimAgentationPlus 则支持通过 .simTag() 为视图补充名称、文件和行号。这与我在 FlowCanvas 中采用的元素锚点思路有相通之处:将画面与结构化信息结合,让 AI 更准确地理解反馈对象。


