如何在第一次发出后终止 Swift 合并响应?
How to terminate Swift Combine response after first emit?
我正在向发布商请求单个值,并希望在收到响应后终止。下面我只是在第一次后取消分配取消令牌,有没有更好的方法来做到这一点?
extension MyInteractor {
private static var locationPermissionToken: Cancellable?
func requestLocationPermission(completion: @escaping (Result<Void, LocationError>) -> Void) {
Self.locationPermissionToken = locationProxy.authorizationPublisher
.sink { status in
Self.locationPermissionToken = nil
status ? completion(.success(())) : completion(.failure(.deniedLocationServices))
}
locationProxy.requestAuthorization()
}
}
考虑到您有自定义 Publisher
,我建议修改它的实现,以便它在发出值后立即发送完成。
由于您使用的是 PassthroughSubject
而不是符合 Publisher
的自定义类型,因此您可以创建一个发送值和完成的自定义方法。然后您需要调用此方法,而不是从您的类型内部调用 authorizationSubject.send
。
private func emitAndComplete(authorizationStatus: Bool) {
Self.authorizationSubject.send(authorizationStatus)
Self.authorizationSubject.send(completion: .finished)
}
完整修改代码:
@available(OSX 10.15, iOS 13, tvOS 13, watchOS 6, *)
public class LocationProxy: NSObject {
private lazy var manager = CLLocationManager()
private static let authorizationSubject = PassthroughSubject<Bool, Never>()
public private(set) lazy var authorizationPublisher: AnyPublisher<Bool, Never> = Self.authorizationSubject.eraseToAnyPublisher()
var isAuthorized: Bool { CLLocationManager.isAuthorized }
func requestAuthorization(for type: LocationAPI.AuthorizationType = .whenInUse) {
// Handle authorized and exit
guard !isAuthorized(for: type) else {
emitAndComplete(authorizationStatus: true)
return
}
// Request appropiate authorization before exit
defer {
#if os(macOS)
if #available(OSX 10.15, *) {
manager.requestAlwaysAuthorization()
}
#elseif os(tvOS)
manager.requestWhenInUseAuthorization()
#else
switch type {
case .whenInUse:
manager.requestWhenInUseAuthorization()
case .always:
manager.requestAlwaysAuthorization()
}
#endif
}
// Handle mismatched allowed and exit
guard !isAuthorized else {
// Process callback in case authorization dialog not launched by OS
// since user will be notified first time only and ignored subsequently
emitAndComplete(authorizationStatus: false)
return
}
// Handle denied and exit
guard CLLocationManager.authorizationStatus() == .notDetermined else {
emitAndComplete(authorizationStatus: false)
return
}
}
public func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
guard status != .notDetermined else { return }
emitAndComplete(authorizationStatus: isAuthorized)
}
private func emitAndComplete(authorizationStatus: Bool) {
Self.authorizationSubject.send(authorizationStatus)
Self.authorizationSubject.send(completion: .finished)
}
}
我正在向发布商请求单个值,并希望在收到响应后终止。下面我只是在第一次后取消分配取消令牌,有没有更好的方法来做到这一点?
extension MyInteractor {
private static var locationPermissionToken: Cancellable?
func requestLocationPermission(completion: @escaping (Result<Void, LocationError>) -> Void) {
Self.locationPermissionToken = locationProxy.authorizationPublisher
.sink { status in
Self.locationPermissionToken = nil
status ? completion(.success(())) : completion(.failure(.deniedLocationServices))
}
locationProxy.requestAuthorization()
}
}
考虑到您有自定义 Publisher
,我建议修改它的实现,以便它在发出值后立即发送完成。
由于您使用的是 PassthroughSubject
而不是符合 Publisher
的自定义类型,因此您可以创建一个发送值和完成的自定义方法。然后您需要调用此方法,而不是从您的类型内部调用 authorizationSubject.send
。
private func emitAndComplete(authorizationStatus: Bool) {
Self.authorizationSubject.send(authorizationStatus)
Self.authorizationSubject.send(completion: .finished)
}
完整修改代码:
@available(OSX 10.15, iOS 13, tvOS 13, watchOS 6, *)
public class LocationProxy: NSObject {
private lazy var manager = CLLocationManager()
private static let authorizationSubject = PassthroughSubject<Bool, Never>()
public private(set) lazy var authorizationPublisher: AnyPublisher<Bool, Never> = Self.authorizationSubject.eraseToAnyPublisher()
var isAuthorized: Bool { CLLocationManager.isAuthorized }
func requestAuthorization(for type: LocationAPI.AuthorizationType = .whenInUse) {
// Handle authorized and exit
guard !isAuthorized(for: type) else {
emitAndComplete(authorizationStatus: true)
return
}
// Request appropiate authorization before exit
defer {
#if os(macOS)
if #available(OSX 10.15, *) {
manager.requestAlwaysAuthorization()
}
#elseif os(tvOS)
manager.requestWhenInUseAuthorization()
#else
switch type {
case .whenInUse:
manager.requestWhenInUseAuthorization()
case .always:
manager.requestAlwaysAuthorization()
}
#endif
}
// Handle mismatched allowed and exit
guard !isAuthorized else {
// Process callback in case authorization dialog not launched by OS
// since user will be notified first time only and ignored subsequently
emitAndComplete(authorizationStatus: false)
return
}
// Handle denied and exit
guard CLLocationManager.authorizationStatus() == .notDetermined else {
emitAndComplete(authorizationStatus: false)
return
}
}
public func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
guard status != .notDetermined else { return }
emitAndComplete(authorizationStatus: isAuthorized)
}
private func emitAndComplete(authorizationStatus: Bool) {
Self.authorizationSubject.send(authorizationStatus)
Self.authorizationSubject.send(completion: .finished)
}
}