From c02260dc8a4296e63551a6e5476d2d5d3ab1bfaa Mon Sep 17 00:00:00 2001 From: NachoSoto Date: Sat, 11 Jul 2026 10:51:51 -0700 Subject: [PATCH] [Performance] Remove redundant image scheduler hop --- AsyncImageView/AsyncImageLoader.swift | 4 +- AsyncImageView/AsyncImageView.swift | 3 +- AsyncImageView/AsyncSwiftUIImageView.swift | 3 +- .../AsyncImageSchedulerSpec.swift | 71 +++++++++++++++++++ 4 files changed, 74 insertions(+), 7 deletions(-) create mode 100644 AsyncImageViewTests/AsyncImageSchedulerSpec.swift diff --git a/AsyncImageView/AsyncImageLoader.swift b/AsyncImageView/AsyncImageLoader.swift index 68d4fba2..636a5aca 100644 --- a/AsyncImageView/AsyncImageLoader.swift +++ b/AsyncImageView/AsyncImageLoader.swift @@ -27,11 +27,9 @@ Renderer.RenderResult == PlaceholderRenderer.RenderResult { requestsSignal: Signal, renderer: Renderer, placeholderRenderer: PlaceholderRenderer?, - uiScheduler: ReactiveSwift.Scheduler, - imageCreationScheduler: ReactiveSwift.Scheduler + uiScheduler: ReactiveSwift.Scheduler ) -> Signal { return requestsSignal.skipRepeats(==) - .observe(on: imageCreationScheduler) .flatMap(.latest) { data -> SignalProducer in let prefixSignal: SignalProducer = .init(value: nil) diff --git a/AsyncImageView/AsyncImageView.swift b/AsyncImageView/AsyncImageView.swift index b8907ddf..4da4ab9f 100644 --- a/AsyncImageView/AsyncImageView.swift +++ b/AsyncImageView/AsyncImageView.swift @@ -59,8 +59,7 @@ open class AsyncImageView< requestsSignal: self.requestsSignal, renderer: renderer, placeholderRenderer: placeholderRenderer, - uiScheduler: uiScheduler, - imageCreationScheduler: imageCreationScheduler + uiScheduler: uiScheduler ) .observeValues { [weak self] result in self?.updateImage(result) diff --git a/AsyncImageView/AsyncSwiftUIImageView.swift b/AsyncImageView/AsyncSwiftUIImageView.swift index 4f17e2b7..e26fa628 100644 --- a/AsyncImageView/AsyncSwiftUIImageView.swift +++ b/AsyncImageView/AsyncSwiftUIImageView.swift @@ -178,8 +178,7 @@ private final class AsyncSwiftUIImageViewModel< requestsSignal: self.requestsSignal, renderer: self.renderer, placeholderRenderer: self.placeholderRenderer, - uiScheduler: self.uiScheduler, - imageCreationScheduler: self.imageCreationScheduler + uiScheduler: self.uiScheduler ) .observeValues { [weak self] result in self?.renderResult = result diff --git a/AsyncImageViewTests/AsyncImageSchedulerSpec.swift b/AsyncImageViewTests/AsyncImageSchedulerSpec.swift new file mode 100644 index 00000000..77eff4f4 --- /dev/null +++ b/AsyncImageViewTests/AsyncImageSchedulerSpec.swift @@ -0,0 +1,71 @@ +import Quick +import Nimble +import UIKit + +import ReactiveSwift + +import AsyncImageView + +class AsyncImageSchedulerSpec: QuickSpec { + override class func spec() { + describe("AsyncImageView scheduling") { + it("schedules image creation once per request") { + let imageCreationScheduler = CountingScheduler() + let renderer = SchedulerCheckingRenderer(scheduler: imageCreationScheduler) + let view = AsyncImageView< + TestRenderData, + TestData, + SchedulerCheckingRenderer, + SchedulerCheckingRenderer + >( + initialFrame: CGRect(origin: .zero, size: CGSize(width: 10, height: 10)), + renderer: renderer, + placeholderRenderer: nil, + uiScheduler: ImmediateScheduler(), + imageCreationScheduler: imageCreationScheduler + ) + let window = UIWindow() + window.addSubview(view) + imageCreationScheduler.reset() + + view.data = .a + + expect(imageCreationScheduler.scheduleCount.value) == 1 + expect(renderer.startedOnScheduler.value) == true + } + } + } +} + +private final class CountingScheduler: Scheduler { + let scheduleCount = Atomic(0) + let isExecuting = Atomic(false) + + func schedule(_ action: @escaping () -> Void) -> Disposable? { + self.scheduleCount.modify { $0 += 1 } + self.isExecuting.modify { $0 = true } + action() + self.isExecuting.modify { $0 = false } + + return nil + } + + func reset() { + self.scheduleCount.modify { $0 = 0 } + } +} + +private final class SchedulerCheckingRenderer: RendererType { + let startedOnScheduler = Atomic(false) + private let scheduler: CountingScheduler + + init(scheduler: CountingScheduler) { + self.scheduler = scheduler + } + + func renderImageWithData(_ data: TestRenderData) -> SignalProducer { + self.startedOnScheduler.modify { $0 = self.scheduler.isExecuting.value } + + return SignalProducer(value: UIImage()) + } +}