Search code examples
swiftswiftuicombine

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


I'm developing a MVVM structure with API calls. I have this structure now:

    //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!)

Basically what I do is obtain certain publisher from a LoginService, then I create a subscriber on loginPublisher, and then I tell LoginService to make some assync logic and send it result to loginPublisher this way I manage sent data with loginSubscriber.

I would like to execute LoginService.login() internally when I execute LoginService.generateLoginPublisher(), but if I do that, there is a chance that LoginService.login() logic finish before I create loginSubscriber, that's why I was forced to control when to call LoginService.login().

How could I detect from LoginService when its publisher has a new subscriber?

This is my 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))
        }
    }
}

}


Solution

  • If you want full control over subscriptions, you can create a custom Publisher and Subscription.

    Publisher's func receive<S: Subscriber>(subscriber: S) method is the one that gets called when the publisher receives a new subscriber.

    If you simply want to make a network request when this happens, you just need to create a custom Publisher and return a Future that wraps the network request from this method.

    In general, you should use Future for one-off async events, PassthroughSubject is not the ideal Publisher to use for network requests.