有没有办法检测发布者何时有新订阅者? | Swift, 合并

Is there a way to detect when a publisher has a new subscriber? | Swift, Combine

我正在开发一个带有 API 调用的 MVVM 结构。 我现在有这个结构:

    //Get publisher
    loginPublisher = LoginService.generateLoginPublisher()
        
    //Create a subscriber
    loginSubscriber = loginPublisher!
        .sink { error in
            print("Something bad happened")
            self.isLoading = false
        } receiveValue: { value in               
            
            self.saveClient(value)
            self.client = value
            self.isLoading = false
        }
    
    //Asking service to start assync task and notify its result on publisher
    LoginService.login(email, password, loginPublisher!)

基本上我所做的是从 LoginService 获取某个发布者,然后我在 loginPublisher 上创建一个订阅者,然后我告诉 LoginService 做一些异步逻辑并发送它结果 loginPublisher 这样我用 loginSubscriber.

管理发送的数据

我想在执行 LoginService.generateLoginPublisher() 时在内部执行 LoginService.login(),但如果我这样做,LoginService.login() 逻辑有可能在我创建 loginSubscriber,这就是为什么我被迫控制何时调用 LoginService.login()

当发布者有新订阅者时,如何从 LoginService 检测到?

这是我的 LoginService class:

class LoginService{
static func generateLoginPublisher() -> PassthroughSubject<Client, NetworkError>{
    return PassthroughSubject<Client, NetworkError>()
}

static func login(_ email: String,_ password: String,_ loginPublisher: PassthroughSubject<Client, NetworkError>){
    let url = NetworkBuilder.getApiUrlWith(extraPath: "login")
    
    print(url)
    
    let parameters: [String: String] = [
        "password": password,
        "login": email
    ]
    
    print(parameters)
    
    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{//Success
            loginPublisher.send(loginResponse)
        }
        else{//Failure
            loginPublisher.send(completion: Subscribers.Completion<NetworkError>.failure(.thingsJustHappen))
        }
    }
}

}

您可以处理发布者事件并注入副作用来跟踪订阅,例如

class LoginService {
static func generateLoginPublisher(onSubscription: @escaping (Subscription) -> Void) -> AnyPublisher<Client, NetworkError> {
    return PassthroughSubject<Client, NetworkError>()
        .handleEvents(receiveSubscription: onSubscription, receiveOutput: nil, receiveCompletion: nil, receiveCancel: nil, receiveRequest: nil)
        .eraseToAnyPublisher()
    }
}

所以

loginPublisher = LoginService.generateLoginPublisher() { subscription in
   // do anything needed here when on new subscription appeared
}

如果您想完全控制订阅,您可以创建自定义 PublisherSubscription

Publisherfunc receive<S: Subscriber>(subscriber: S) 方法是发布者收到新订阅者时调用的方法。

如果你只是想在发生这种情况时发出网络请求,你只需要创建一个自定义 Publisher 和 return 一个 Future 来包装来自这个方法的网络请求.

一般来说,您应该将 Future 用于一次性异步事件,PassthroughSubject 不适合用于网络请求 Publisher

我终于使用 Future 而不是 Dávid Pásztor 建议的 PassthroughtSubject 解决了我的问题。使用 Future 我不必担心 LoginService.login() 在创建 loginSubscriber.

之前逻辑完成

LoginSevice.login()方法:

static func login(_ email: String,_ password: String) -> Future<Client, NetworkError>{        
    return Future<Client, NetworkError>{ completion in
        let url = NetworkBuilder.getApiUrlWith(extraPath: "login")
        
        print(url)
        
        let parameters: [String: String] = [
            "password": password,
            "login": email
        ]
        
        print(parameters)
        
        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{//Success
                completion(.success(loginResponse))
            }
            else{//Failure
                completion(.failure(NetworkError.thingsJustHappen))
            }
        }
    }
}

实施:

        loginSubscriber = LoginService.login(email, password)
        .sink { error in
            print("Something bad happened")
            self.isLoading = false
        } receiveValue: { value in
            self.saveClient(value)
            self.client = (value)
            self.isLoading = false
        }