此源码解析适用于
0.41.0
之前的版本。最新的源码已经把Reducer
重构成了一个协议ReducerProtocol
,但核心的实现方法还是离不开这个版本的源码。
这个扩展主要是实现了 Publisher
协议,并且提供了一系列使用 Publisher
创建 Effect
的方法。
实现 Publisher
协议
extension Effect: Publisher {
public typealias Output = Action
// `Publisher` 协议唯一要求实现的方法
public func receive<S: Combine.Subscriber>(
subscriber: S
) where S.Input == Action, S.Failure == Failure {
// 把 `subscriber` 附着在自己的 `publisher` 上,
// `subscriber` 就可以开始接收 `publisher` 发出的值。
self.publisher.subscribe(subscriber)
}
// `Effect` 内部真正产生 `Action` 流的 `Publisher`
var publisher: AnyPublisher<Action, Failure> {
switch self.operation {
case .none:
// 不做任何操作,返回 `Empty`
return Empty().eraseToAnyPublisher()
case let .publisher(publisher):
// `operation` 已经是 `.publisher` 类型,直接返回
return publisher
case let .run(priority, operation):
// 异步任务类型的 `Effect` 转换成 `Publisher`。
// 使用了自定义的 `Publishers.Create`,具体查看源码 `Internal/Create.swift`。
// `Publishers.Create` 要求传入一个闭包,参数是 `Effect.Subscriber`,返回值是 `Cancellable`。
return .create { subscriber in
// 在闭包里面,使用 `Task` 来执行 `operation`
let task = Task(priority: priority) { @MainActor in
// 在 `operation(send)` 运行完成后,发出 `.finished`
defer { subscriber.send(completion: .finished) }
// 定义一个 `Send`,将 `Action` 发给 `subscriber`
let send = Send { subscriber.send($0) }
await operation(send)
}
// 返回 `AnyCancellable`,目的是下游取消订阅的时候,可以取消 `task` 运行
return AnyCancellable {
task.cancel()
}
}
}
}
}
使用 Publisher
创建 Effect
方法
extension Effect {
// 直接使用 `Publisher` 创建 `Effect`
public init<P: Publisher>(_ publisher: P) where P.Output == Output, P.Failure == Failure {
self.operation = .publisher(publisher.eraseToAnyPublisher())
}
// 使用 `value` 创建 `Effect`,并且马上把传入的 `value` 发出
public init(value: Action) {
self.init(Just(value).setFailureType(to: Failure.self))
}
// 使用 `error` 创建 `Effect`,并且马上把传入的 `error` 发出
public init(error: Failure) {
// 这里最好是使用 `Fail`,但在 iOS 13 有 bug(iOS 14 已修复),为了支持 iOS 13,这里做了一点变通。
self.init(
Deferred {
Future { $0(.failure(error)) }
}
)
}
// 创建可以在将来异步提供单个值的 `Effect`。例如:
/// ```swift
/// Effect<Int, Never>.future { callback in
/// DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
/// callback(.success(42))
/// }
/// }
/// ```
public static func future(
_ attemptToFulfill: @escaping (@escaping (Result<Action, Failure>) -> Void) -> Void
) -> Self {
// 在创建 `Future` 时,传入的闭包会马上执行,所以这里使用了 `Deferred`,
// 等有订阅者订阅时才去执行闭包。
Deferred { Future(attemptToFulfill) }.eraseToEffect()
}
// 创建可以在将来同步提供单个值的 `Effect`,与上面的异步很类似。例如:
/// ```swift
/// Effect<User, Error>.result {
/// let fileUrl = URL(
/// fileURLWithPath: NSSearchPathForDirectoriesInDomains(
/// .documentDirectory, .userDomainMask, true
/// )[0]
/// )
/// .appendingPathComponent("user.json")
///
/// let result = Result<User, Error> {
/// let data = try Data(contentsOf: fileUrl)
/// return try JSONDecoder().decode(User.self, from: $0)
/// }
///
/// return result
/// }
/// ```
public static func result(_ attemptToFulfill: @escaping () -> Result<Action, Failure>) -> Self {
Deferred { Future { $0(attemptToFulfill()) } }.eraseToEffect()
}
// 用闭包创建可以发送任意数量值的 `Effect`,并且可以发送 completion。例如:
/// ```swift
/// Effect.run { subscriber in
/// subscriber.send(MPMediaLibrary.authorizationStatus())
///
/// guard MPMediaLibrary.authorizationStatus() == .notDetermined else {
/// subscriber.send(completion: .finished)
/// return AnyCancellable {}
/// }
///
/// MPMediaLibrary.requestAuthorization { status in
/// subscriber.send(status)
/// subscriber.send(completion: .finished)
/// }
/// return AnyCancellable {
/// // Typically clean up resources that were created here, but this effect doesn't
/// // have any.
/// }
/// }
/// ```
public static func run(
_ work: @escaping (Effect.Subscriber) -> Cancellable
) -> Self {
AnyPublisher.create(work).eraseToEffect()
}
// 创建执行某些工作的 `Effect`,并且不需要把数据反馈给 Store。如果抛出错误,`Effect` 将完成,错误将忽略。
public static func fireAndForget(_ work: @escaping () throws -> Void) -> Self {
Deferred { () -> Publishers.CompactMap<Result<Action?, Failure>.Publisher, Action> in
try? work()
// 这里最好是返回 `Empty(completeImmediately: true)`。
// 但在 iOS 13.2 有 bug,它不会 complete,在 iOS 13.3 已修复。
// 为了支持更低版本,这里做了一点变通。
return Just<Action?>(nil)
.setFailureType(to: Failure.self)
.compactMap { $0 }
}
.eraseToEffect()
}
}
extension Effect where Failure == Error {
// 创建可以延迟执行某些工作并且可以同步发送数据给 Store 的 `Effect`。例如:
/// ```swift
/// Effect<User, Error>.catching {
/// let fileUrl = URL(
/// fileURLWithPath: NSSearchPathForDirectoriesInDomains(
/// .documentDirectory, .userDomainMask, true
/// )[0]
/// )
/// .appendingPathComponent("user.json")
///
/// let data = try Data(contentsOf: fileUrl)
/// return try JSONDecoder().decode(User.self, from: $0)
/// }
/// ```
public static func catching(_ work: @escaping () throws -> Action) -> Self {
.future { $0(Result { try work() }) }
}
}
extension Publisher {
// 把 `Publisher` 转换成 `Effect`
public func eraseToEffect() -> Effect<Output, Failure> {
Effect(self)
}
// 把 `Publisher` 转换成 `Effect`,同时执行转换
public func eraseToEffect<T>(
_ transform: @escaping (Output) -> T
) -> Effect<T, Failure> {
self.map(transform)
.eraseToEffect()
}
// 把 `Publisher` 转换成 `Effect`,把 Output 和 Failure 转换成 `Result`
public func catchToEffect() -> Effect<Result<Output, Failure>, Never> {
self.map(Result.success)
.catch { Just(.failure($0)) }
.eraseToEffect()
}
// 把 `Publisher` 转换成 `Effect`,把 Output(使用闭包转换) 和 Failure 转换成 `Result`
public func catchToEffect<T>(
_ transform: @escaping (Result<Output, Failure>) -> T
) -> Effect<T, Never> {
self
.map { transform(.success($0)) }
.catch { Just(transform(.failure($0))) }
.eraseToEffect()
}
// 把任意类型 Output 和 Failure 的 `Publisher` 转换成 `Effect`,并且忽略 Output 和 Failure。
// 适用于想要执行一些操作,但不需要反馈任何数据给 Store。例如:
/// ```swift
/// case .buttonTapped:
/// return analyticsClient.track("Button Tapped")
/// .fireAndForget()
/// ```
public func fireAndForget<NewOutput, NewFailure>(
outputType: NewOutput.Type = NewOutput.self,
failureType: NewFailure.Type = NewFailure.self
) -> Effect<NewOutput, NewFailure> {
return
self
.flatMap { _ in Empty<NewOutput, Failure>() }
.catch { _ in Empty() }
.eraseToEffect()
}
}