Reputation: 438
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))
}
}
}
}
Upvotes: 0
Views: 2334
Reputation: 438
I finally solved my problem using Future
instead of PassthroughtSubject
as Dávid Pásztor suggested. Using Future
I don't have to worried about LoginService.login()
logic finish before I create loginSubscriber
.
LoginSevice.login()
method:
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))
}
}
}
}
Implementation:
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
}
Upvotes: 1
Reputation: 54755
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.
Upvotes: 1