§ · Frequently asked

Common questions.

Short answers. Long answers live in the docs.

What is napkin?

napkin is a Swift 6.2 framework for building iOS and macOS apps as a tree of small, isolated, composable units called napkins. Each napkin is a Router-Interactor-Builder unit, modeled on Uber's RIBs but rebuilt around Swift Concurrency. Business logic lives in final actor interactors. Routing and presentation are @MainActor. Crossings between isolation domains are explicit.

How does napkin differ from Uber's RIBs?

napkin keeps the same vocabulary (Router, Interactor, Builder, Component, Listener, Presentable) and the same tree composition. It changes the underlying mechanics to fit Swift 6: interactors are final actor types instead of subclasses of an open class; routing protocols are @MainActor with async methods; reactive streams are AsyncStream instead of RxSwift Observable; there is no runtime leak detector. Both frameworks ship the same architecture pattern but make different trade-offs underneath.

Why doesn't napkin use RxSwift?

Swift Concurrency provides native primitives for everything RxSwift offered the RIBs pattern: async/await for cross-boundary calls, AsyncStream for pushed sequences, structured task cancellation, and Sendable for safe cross-actor data transfer. Adding RxSwift would mean another transitive dependency, another idiom set to learn, and a parallel cancellation model. napkin uses the language built-ins so the framework stays small and the surface stays familiar to Swift developers learning concurrency.

Does napkin replace RIBs?

No. Uber's RIBs is actively maintained and ships in many large iOS apps. napkin is an alternative for teams that want the RIBs pattern without RxSwift or the runtime leak detector. Use RIBs if you have an existing RIBs codebase, your team is fluent in RxSwift, or you specifically want the leak detector. Use napkin if you're starting fresh on Swift 6 and prefer compile-time guarantees.

What does Clean Architecture have to do with napkin?

napkin enforces the Clean Architecture dependency rule by isolation: business logic (interactors) is on its own actor and depends only on Sendable protocols; UI (routers, presenters, views) is @MainActor and depends on those protocols, never on concrete interactor types. The name napkin refers to the classic back-of-the-napkin architecture sketch — the framework is small enough to fit on one.

Can I use napkin with SwiftUI?

Yes. SwiftUI views are wrapped in a UIHostingController that conforms to a feature's Presentable protocol. The view's button actions call a dispatch helper which bridges from @MainActor SwiftUI closures into the interactor actor: dispatch { [listener] in await listener?.didTap() }. The dispatch helper captures the listener weakly and handles cancellation cleanly. See the SwiftUIIntegration article for the full pattern.

How do I unit-test an actor interactor?

Mock the protocols, not the actor. Each interactor talks to its collaborators through protocols (presenter, router, service, listener). In a test, replace each protocol with a final class that records calls. Then drive the actor by calling its async methods directly from an async test method. The actor's serial executor guarantees ordering; you don't need XCTestExpectation or scheduler tricks. See TestingANapkin for full examples.

What are napkin's platform requirements?

iOS 26 / macOS 26 and the Swift 6.2 toolchain (Xcode 26 or later). That's a deliberate support target, not the hard compiler minimum: the sources type-check down to iOS 18 / macOS 15, bounded by Mutex from the Synchronization module. The project tracks only the current OS generation so the actor model and isolated deinit teardown (SE-0371, Swift 6.2) run on a single current Swift runtime rather than a back-deployment matrix.

How do I install napkin?

Add napkin as a Swift Package dependency. In Xcode: File > Add Package Dependencies, then paste https://github.com/WikipediaBrown/napkin and pick a version (2.0 or later). Or in Package.swift:

.package(url: "https://github.com/WikipediaBrown/napkin", from: "2.0.0")

and add "napkin" to your target's dependencies.

Is there an example app?

Yes. Napkin's Rib House (Examples/RibHouse in the repo) is a runnable iOS app demonstrating napkin end-to-end: a headless LaunchNapkin holds an AuthService and swaps between a LoggedOutNapkin (single Login button) and a LoggedInNapkin (user name + barbecue food list) on tap. The xcodeproj is tracked in source control so the project opens directly without running xcodegen first.

Does napkin support Mac Catalyst?

The framework compiles for Mac Catalyst because it uses standard UIKit / AppKit primitives. The example app currently targets iOS only, but adopting Catalyst is a project-level setting change and a few view-layer adjustments — the napkin tree itself is platform-agnostic.

How does dependency injection work in napkin?

Each feature declares a Dependency protocol listing what it needs from above. Each feature has a Component class that satisfies its children's Dependency protocols (via extension conformance). The root component owns the service instances. The whole graph is checked at compile time — there is no runtime container and no annotation processor. Services injected at the root reach every leaf without anyone hand-rolling a singleton.

Why are there listener protocols in every napkin?

Listeners are how children talk to their parents. Each napkin defines a <Self>NapkinListener protocol with the methods it can call upward (e.g. counterDidFinish). The parent's interactor conforms to that protocol. When the parent builds the child, it passes itself as the listener. Children never import or reference their parent — they only know the listener protocol. This is what makes any napkin replaceable in isolation.

Can a napkin have no view?

Yes — these are called headless napkins. The parent in the example app (LaunchNapkin) is headless: it has an interactor and a router but no Presentable. Its view controller is a plain UIViewController that just embeds whichever child is active. Headless napkins are useful for orchestrators that route between siblings without rendering their own UI. See HeadlessNapkins.

How does napkin handle Swift 6 strict concurrency?

By design, not by suppression. Every cross-isolation crossing is explicit: actor → @MainActor uses await, view → actor uses the dispatch helper, parent listener calls use await. There are no @unchecked Sendable hacks on the hot path. The compiler errors that Swift 6 raises are taken as architecture signals — they tell you where data flows between isolation domains, which is exactly where the boundaries should be.

Does napkin hit the Swift 6 “Main actor-isolated property cannot be used to satisfy nonisolated protocol requirement” error?

No, and it can’t. napkin’s base Presentable protocol is annotated @MainActor, so every feature’s presentable seam — including any var listener requirement on it — inherits that isolation. The @MainActor view controller that witnesses the requirement is then in the same isolation domain, so the error is structurally impossible rather than suppressed. This is the headline Swift 6 question on Uber’s actively-maintained RIBs-iOS (issue #43); napkin’s whole-protocol @MainActor design avoids it by construction. The child-to-parent listener is a separate seam — an actor-isolated weak property behind a Sendable async protocol — and never had the problem. Full write-up: The Swift 6 @MainActor listener-conformance error.

I added napkin and immediately got “Main actor-isolated initializer init(dependency:) has different actor isolation from nonisolated overridden declaration”. Why?

Xcode 26’s App template sets the Default Actor Isolation build setting to MainActor (Swift’s approachable concurrency, SE-0466). napkin’s Builder and Component are deliberately nonisolated dependency-injection plumbing, so in a MainActor-default module your Builder/Component subclass is inferred @MainActor and its init(dependency:) override no longer matches napkin’s nonisolated base initializer. It’s not a napkin bug. Fix it per type by marking each Builder/Component subclass nonisolated (the bundled Xcode templates already generate this), or per module by setting the target’s Default Actor Isolation to nonisolated. Routers and view controllers stay @MainActor; interactors stay actors — only the DI plumbing needs nonisolated. Full explanation in Getting Started.

Where can I find the docs?

Full documentation at getnapkin.to/documentation/napkin/. Architecture articles (Cross-Isolation Patterns, Protocol Composition Over Inheritance), tutorials (Building a Login Flow, Testing a Napkin, Adding a Networked Service, Tab Bar Napkin), and full API reference. The repository is at github.com/WikipediaBrown/napkin.