§ 00·The framework

Apps as a tree of small, isolated, composable units.

napkin is a Swift 6.2 framework for clean-architecture iOS & macOS apps, modeled on Uber's RIBs and rebuilt around Swift Concurrency. Business logic lives in final actor interactors. Routing and presentation are @MainActor. Crossings are explicit.

Latest
2.0.15
Targets
iOS 26 · macOS 26
Swift
6.2 strict
License
Apache‑2.0
HomeInteractor.swift Swift 6.2
 1final actor HomeInteractor: PresentableInteractable {
 2    nonisolated let lifecycle = InteractorLifecycle()
 3    nonisolated let presenter: HomePresentable
 4
 5    init(presenter: HomePresentable) {
 6        self.presenter = presenter
 7    }
 8
 9    func didBecomeActive() async {
10        task {
11            for await user in users.stream() {
12                await presenter.update(user)
13            }
14        }
15    }
16}
§ 01

Specifications

Four moves that distinguish napkin from every framework that came before it — including its parent.

  1. Clean isolation, by the compiler

    Business logic lives in final actor interactors — off the main thread, by construction. Routing and presentation are @MainActor. Swift enforces the dependency rule, not convention.

  2. No Combine. No RxSwift.

    @Observable for state. AsyncStream for events. Structured concurrency for everything that used to need a subscription bag. Combine is removed; Rx never arrives.

  3. iOS 26 · macOS 26 · Swift 6.2

    Built on isolated deinit, Mutex from Synchronization, @Observable, and Observations { }. Concrete bets on the modern stack — no polyfills, no compromises.

  4. Modeled on Uber's RIBs

    Builder, Component, Interactor, Router, Presenter. The familiar architecture you can sketch on a napkin — rebuilt around actor and friends, with composition where inheritance once lived.

§ 02

The isolation map

Three regions. Each ring lives in a specific isolation domain. Every crossing is explicit and named.

Sendable
Builder
constructs the napkin
Component
DI container
@MainActor
Router
owns the subtree
ViewController
hosts the SwiftUI view
Presenter
@Observable state
final actor
Interactor
business logic, off main
await router?.routeTo(…)
await presenter.update(…)
dispatch { await listener?.didTap(…) }

Data flows down the tree. Events flow up through listener protocols. Every cross-region call is an await — an architectural seam, made legible.

§ ∞·Get started

Sketch your next app on a napkin.

Pull the package, read the docs, run the example. Three commands, one tree, one isolation rule the compiler is willing to enforce.

// Package.swift
.package(url: "https://github.com/WikipediaBrown/napkin.git", from: "2.0.15")