作为一种替代设置,signIn
返回仅具有输出String
和故障类型Service.Error
的发布者(Result
类型对于发布者而言是多余的)。
我对Combine
还是很陌生,而不是将我的所有任务都运行到viewModel
中,而是试图更好地隔离与业务逻辑有关的代码。
让我们以SignIn
服务为例。服务接收username
和password
并返回token
和userID
。
[该服务的公开调用是signIn
,该调用在内部调用私有函数networkCall
。我想实现这两个函数以返回Publisher
。networkCall
的作用应该是调用API并存储接收到的令牌,而signIn
的作用只是返回成功或失败。
这是我的代码,我还在这里突出显示我被卡住的地方。通常,我不知道在哪里使用从API接收到的信息(并存储令牌)的正确位置。目前,我正在.map
调用中执行此操作,但这对我来说是错误的。您是否可以分享一些建议以改进此逻辑,尤其是解释哪个是运行业务逻辑的正确位置...我想.map
不是正确的位置!并且.sink
只会停止链接。
struct SignInResponse:Codable{
var token:String
var userID:String
}
class SignInService {
// Perform the API call
private func networkCall(with request:SignInRequest)->AnyPublisher<SignInResponse, ServiceError>{
return URLSession.DataTaskPublisher(request: request, session: .shared)
.decode(type: SignInResponse.self, decoder: JSONDecoder())
.mapError{error in return ServiceError.error}
.eraseToAnyPublisher()
}
func signIn(username:String, password:String)->AnyPublisher<Result<String, ServiceError>, Never>{
let request = SignInRequest(with username:username, password:password)
return networkCall(with: request)
.map{ (response) -> Result<String, ServiceError> in
if response.token != ""{
// THIS SOUNDS EXTREMELLY WRONG. I SHOULD NOT USE MAP TO HANDLE THE TOKEN -------
self.storage.save(key: "token", value: response.token)
return Result.success(response.userID)
}else{
return Result.failure(ServiceError.unknown)
}
}
.replaceError(with: Result.failure(ServiceError.unknown))
.eraseToAnyPublisher()
}
......
}
从模型中,我以这种方式调用SignIn:
func requestsSignIn(){
if let username = username, let password = password{
cancellable = service.signIn(username: username, password: password)
.sink(receiveValue: { (result) in
switch result{
case .failure(let error):
self.errorMessage = error.localizedDescription
case .success(let userID):
// the sigin succeeded do something here
}
})
}
}
基本上,我同意现有的答案。您在这里的误解似乎是Combine管道的目的所在。这个想法是any一个有用的值—在这里,您的用户ID – or一个错误(如果合适;否则,什么也没有)应该弹出管道的末尾。管道末端的订户准备接收其中任何一个。
因此通常将Result对象传递到管道的末端是没有意义的,必须将其进一步分析为成功或失败值。 Result对象的目的仅仅是允许您传递异步性,即,通过在将来的某个时间将结果交给Result来调用其他人,以免不必使用two]中的一个进行调用。 >值,即使用两个可选参数的实数值或错误。在Combine出版商发布之后,已经发生了异步性,并且您已经收到了这一事实的信号;这就是发布means的内容。您现在唯一需要保留的是信号的任何部分或突变对您都有意义且有用。
这里是一个相当典型的管道,可以完成您想做的事情;我没有像您一样将其分为两部分,但是您当然可以将其分成两个部分:
URLSession.DataTaskPublisher(request: request, session: .shared) .map {$0.data} .decode(type: SignInResponse.self, decoder: JSONDecoder()) .tryMap { response -> String in if response.token == "" { throw ServiceError.unknown } return response.userID } .receive(on:DispatchQueue.main) .sink(receiveCompletion: {err in /* do something with error */ }, receiveValue: {userID in /* do something with userID */}) .store(in:&storage)
首先,数据任务的结果是一个元组,但是我们需要的只是数据部分,因此我们映射到该部分。然后我们解码。然后,我们检查一个空令牌,如果得到一个,则抛出该令牌。否则,我们向下映射到用户ID,因为这是唯一有用的结果。最后,我们切换到主线程并使用接收器捕获输出,然后将接收器存储在通常的
Set<AnyCancellable>
中,以便其持续足够长的时间以致发生某些事情。
观察到,如果在我们遇到故障错误的过程中的any
阶段,那个错误立即传播到整个管道末端。如果数据任务失败,它将是URLError。如果解码失败,则与解码器一样,报告该问题将是错误。如果令牌不存在,将出现ServiceError。当然,在此过程中的任何时候,您都可以根据需要捕获和阻止或转换错误,因为它会出现。作为一种替代设置,signIn
返回仅具有输出String
和故障类型Service.Error
的发布者(Result
类型对于发布者而言是多余的)。
然后,对于响应中的错误令牌字符串之类的错误,请使用tryMap
而不是map
从网络功能转换Result
类型,并使其抛出ServiceEror.emptyToken
或类似内容。这将导致发布者立即将其发布为失败。
作为一种替代设置,signIn
返回仅具有输出String
和故障类型Service.Error
的发布者(Result
类型对于发布者而言是多余的)。