此源码解析适用于
0.41.0
之前的版本。最新的源码已经把Reducer
重构成了一个协议ReducerProtocol
,但核心的实现方法还是离不开这个版本的源码。
debounce
这个扩展的作用是将普通 Effect
变成防抖动的。
extension Effect {
public func debounce<S: Scheduler>(
id: AnyHashable,
for dueTime: S.SchedulerTimeType.Stride,
scheduler: S,
options: S.SchedulerOptions? = nil
) -> Self {
switch self.operation {
case .none:
return .none
case .publisher, .run:
// 利用 `Combine` 自带的 `delay` 方法和 上一篇文章讲解的 `cancellable` 实现防抖动。
// 例如,假设防抖动的时间为 3 秒,每秒调用一次 `debounce` 方法:
// 第 1 秒调用:`Just` 发出的值,因为使用了 `delay`,要 3 秒后才会真正发出;
// 第 2 秒调用:因为使用 `cancellable`,第 1 秒产生的 `Just` 会无法发出值;
// 第 3 秒调用:因为使用 `cancellable`,第 2 秒产生的 `Just` 会无法发出值;
// 第 6 秒:距离第 3 秒调用已经过去了 3 秒,第 3 秒产生的 `Just` 正常发出值,,然后使用 `flatMap` 把 `Just` 的值转换为 `publisher` 的值。
// 最终实现了防抖动。
return Self(
operation: .publisher(
Just(())
.setFailureType(to: Failure.self)
.delay(for: dueTime, scheduler: scheduler, options: options)
.flatMap { self.publisher.receive(on: scheduler) }
.eraseToAnyPublisher()
)
)
.cancellable(id: id, cancelInFlight: true)
}
}
// 上面那个方法的重载方法,这里的 `id` 是一个 `Any.Type`
public func debounce<S: Scheduler>(
id: Any.Type,
for dueTime: S.SchedulerTimeType.Stride,
scheduler: S,
options: S.SchedulerOptions? = nil
) -> Self {
self.debounce(id: ObjectIdentifier(id), for: dueTime, scheduler: scheduler, options: options)
}
}
deferred
这个扩展的作用是延迟 Effect
的执行。从代码可以看到,它的实现与 debounce
非常类似,直接利用 Combine
自带的 delay
方法实现。
extension Effect {
public func deferred<S: Scheduler>(
for dueTime: S.SchedulerTimeType.Stride,
scheduler: S,
options: S.SchedulerOptions? = nil
) -> Self {
switch self.operation {
case .none:
return .none
case .publisher, .run:
return Self(
operation: .publisher(
Just(())
.setFailureType(to: Failure.self)
.delay(for: dueTime, scheduler: scheduler, options: options)
.flatMap { self.publisher.receive(on: scheduler) }
.eraseToAnyPublisher()
)
)
}
}
}
throttle
这个扩展的作用是将普通 Effect
变成限流的。
extension Effect {
public func throttle<S: Scheduler>(
id: AnyHashable,
for interval: S.SchedulerTimeType.Stride,
scheduler: S,
latest: Bool // 是否发布最新的值。如果是 `false`,将发出在 `interval` 期间接收的第一个值
) -> Self {
switch self.operation {
case .none:
return .none
case .publisher, .run:
return self.receive(on: scheduler) // 在指定的 `scheduler` 上接收值
.flatMap { value -> AnyPublisher<Action, Failure> in
// 加锁,保证相关数据的准确性
throttleLock.lock()
defer { throttleLock.unlock() }
guard let throttleTime = throttleTimes[id] as! S.SchedulerTimeType? else {
// --------- 缓存中没有 `id` 对应的时间,说明是第一次接收到值,应该直接发出值 ---------
// 把 `id` 对应的时间设置为当前时间
throttleTimes[id] = scheduler.now
// 把 `id` 对应的值设置为 `nil`
throttleValues[id] = nil
// 发出值
return Just(value).setFailureType(to: Failure.self).eraseToAnyPublisher()
}
// -------------------- 从第二次接收到值开始,代码就会执行到这里 --------------------
// 根据 `latest` 得到下一次要发出的值:
// 如果 `latest` 为 `true`,使用当前接收到的值 `value`,
// 否则使用缓存的值(因为每次发出值后 `throttleValues[id]` 都会被重置为 `nil`, 所以
// 最后的 ` ?? value` 就能保证是在 `interval` 期间接收的第一个值)
let value = latest ? value : (throttleValues[id] as! Action? ?? value)
throttleValues[id] = value
guard throttleTime.distance(to: scheduler.now) < interval else {
// ------------- 缓存时间与当前时间的间隔已经超过 `interval`,应该发出值 -------------
// 把 `id` 对应的时间设置为当前时间
throttleTimes[id] = scheduler.now
// 把 `id` 对应的值设置为 `nil`
throttleValues[id] = nil
// 发出值
return Just(value).setFailureType(to: Failure.self).eraseToAnyPublisher()
}
// ---- 缓存时间与当前时间的间隔小于 `interval`,需要延迟到时间间隔等于 `interval` ----
return Just(value)
// 使用 `delay` 延迟发出值
.delay(
// 计算延迟的时间:当前时间与 (缓存时间 + `interval`) 的间隔
for: scheduler.now.distance(to: throttleTime.advanced(by: interval)),
scheduler: scheduler
)
.handleEvents(
receiveOutput: { _ in
throttleLock.sync {
// `delay` 发出了值,重置相关数据
throttleTimes[id] = scheduler.now
throttleValues[id] = nil
}
}
)
.setFailureType(to: Failure.self)
.eraseToAnyPublisher()
}
.eraseToEffect()
// 与 `debounce` 类似,这里使用 `cancellable` 可以把 `interval` 期间产生的不应该发出值的 `Just` 取消掉
.cancellable(id: id, cancelInFlight: true)
}
}
// 上面那个方法的重载方法,这里的 `id` 是一个 `Any.Type`
public func throttle<S: Scheduler>(
id: Any.Type,
for interval: S.SchedulerTimeType.Stride,
scheduler: S,
latest: Bool
) -> Self {
self.throttle(id: ObjectIdentifier(id), for: interval, scheduler: scheduler, latest: latest)
}
}
var throttleTimes: [AnyHashable: Any] = [:]
var throttleValues: [AnyHashable: Any] = [:]
// 递归锁,保证相关数据的准确性。
// 使用递归锁的原因是在 `Store.send(_:originatingFrom)` 方法中有递归操作。
let throttleLock = NSRecursiveLock()