Swift Combine如何检测Publisher新增订阅者以触发登录异步请求
实现方案
你遇到的问题本质是PassthroughSubject属于热发布者,提前发送的事件会被未订阅的接收方丢失,以下是两种适配方案:
方案1:原生响应式实现(推荐,无需手动检测订阅)
直接使用Combine内置的Deferred+Future组合,即可实现只有订阅者接入时才执行登录请求的逻辑,完全规避请求提前完成丢数据的问题,无需额外维护Subject和监听逻辑。
修改后的LoginService代码如下:
class LoginService { static func loginPublisher(email: String, password: String) -> AnyPublisher<Client, NetworkError> { // Deferred会在有订阅者接入时才执行内部逻辑 Deferred { Future { promise in let url = NetworkBuilder.getApiUrlWith(extraPath: "login") let parameters: [String: String] = [ "password": password, "login": email ] let request = AF.request( url, method: .post, parameters: parameters, encoder: JSONParameterEncoder.default ) request.validate(statusCode: 200...299) request.responseDecodable(of: Client.self) { response in if let loginResponse = response.value { promise(.success(loginResponse)) } else { promise(.failure(.thingsJustHappen)) } } } } .eraseToAnyPublisher() } }
调用侧代码可简化为直接订阅,无需手动触发请求:
loginSubscriber = LoginService.loginPublisher(email: email, password: password) .sink { completion in if case .failure = completion { print("Something bad happened") } self.isLoading = false } receiveValue: { value in self.saveClient(value) self.client = value self.isLoading = false }
该方案优势:
- 符合Combine原生设计规范,无额外自定义逻辑
- 自动控制请求时机,不存在事件丢失风险
- 代码结构更简洁,无需维护多余的Publisher实例
方案2:手动检测订阅事件
如果你的业务场景必须使用PassthroughSubject,可以通过handleEvents操作符的receiveSubscription回调监听新订阅者接入事件:
static func generateLoginPublisher(email: String, password: String) -> AnyPublisher<Client, NetworkError> { let subject = PassthroughSubject<Client, NetworkError>() return subject .handleEvents(receiveSubscription: { _ in // 新订阅者接入时触发,执行登录请求 Self.login(email, password, subject) }) .eraseToAnyPublisher() }
注意事项:
- 该实现会在每有一个新订阅者接入时都触发一次登录请求,如需避免重复请求可追加
share()操作符 - 优先推荐方案1的实现,更适配响应式编程的开发习惯
内容的提问来源于stack exchange,提问作者Sebastián García Burgos
相关产品推荐
相关产品推荐

