轻量客户端(基于闭包)
使用此模式让网络或服务依赖保持简单且可测试,而无需引入完整的视图模型或重量级 DI 框架。它非常适合 SwiftUI 应用:你希望有一个小巧、可组合的 API 表面,并能在预览/测试中替换。
意图
- 提供一个由异步闭包构成的小型"客户端"类型。
- 将业务逻辑保留在 store 或功能层中,而不是视图中。
- 便于在预览/测试中打桩。
最小形态
structSomeClient{varfetchItems:(_limit:Int)asyncthrows->[Item]varsearch:(_query:String,_limit:Int)asyncthrows->[Item]}extensionSomeClient{staticfunclive(baseURL:URL=URL(string:"https://example.com")!)->SomeClient{letsession=URLSession.sharedreturnSomeClient(fetchItems:{limitin// build URL, call session, decode},search:{query,limitin// build URL, call session, decode})}}用法模式
@MainActor@ObservablefinalclassItemsStore{enumLoadState{caseidle,loading,loaded,failed(String)}varitems:[Item]=[]varstate:LoadState=.idleprivateletclient:SomeClientinit(client:SomeClient){self.client=client}funcload(limit:Int=20)async{state=.loadingdo{items=tryawaitclient.fetchItems(limit)state=.loaded}catch{state=.failed(error.localizedDescription)}}}structContentView:View{@Environment(ItemsStore.self)privatevarstorevarbody:someView{List(store.items){iteminText(item.title)}.task{awaitstore.load()}}}@mainstructMyApp:App{@Stateprivatevarstore=ItemsStore(client:.live())varbody:someScene{WindowGroup{ContentView().environment(store)}}}指导
- 将解码和 URL 构建保留在客户端中;将状态变化保留在 store 中。
- 让 store 在
init中接受客户端并将其保持为 private。 - 避免全局单例;使用
.environment注入 store。 - 如果需要多种变体(mock/stub),添加
static func mock(...)。
陷阱
- 不要把 UI 状态放在客户端中;将状态保留在 store 中。
- 不要在客户端闭包中捕获
self或视图状态。