From cd74a16accce4919a56158d4ddc64ebcc18c1117 Mon Sep 17 00:00:00 2001 From: NachoSoto Date: Sat, 11 Jul 2026 10:51:45 -0700 Subject: [PATCH] [Performance] Skip unchanged UIKit image sizes --- AsyncImageView/AsyncImageView.swift | 8 +- .../AsyncImageViewRequestSpec.swift | 84 +++++++++++++++++++ 2 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 AsyncImageViewTests/AsyncImageViewRequestSpec.swift diff --git a/AsyncImageView/AsyncImageView.swift b/AsyncImageView/AsyncImageView.swift index b8907dd..a8d98ed 100644 --- a/AsyncImageView/AsyncImageView.swift +++ b/AsyncImageView/AsyncImageView.swift @@ -77,13 +77,17 @@ open class AsyncImageView< open override var frame: CGRect { didSet { - self.requestNewImageIfReady() + if self.frame.size != oldValue.size { + self.requestNewImageIfReady() + } } } open override var bounds: CGRect { didSet { - self.requestNewImageIfReady() + if self.bounds.size != oldValue.size { + self.requestNewImageIfReady() + } } } diff --git a/AsyncImageViewTests/AsyncImageViewRequestSpec.swift b/AsyncImageViewTests/AsyncImageViewRequestSpec.swift new file mode 100644 index 0000000..a46c5e0 --- /dev/null +++ b/AsyncImageViewTests/AsyncImageViewRequestSpec.swift @@ -0,0 +1,84 @@ +import Quick +import Nimble +import UIKit + +import ReactiveSwift + +import AsyncImageView + +class AsyncImageViewRequestSpec: QuickSpec { + override class func spec() { + describe("AsyncImageView requests") { + var counter: Atomic! + var view: AsyncImageView< + RequestTestRenderData, + RequestTestViewData, + RequestTestRenderer, + RequestTestRenderer + >! + var window: UIWindow! + + beforeEach { + counter = Atomic(0) + view = AsyncImageView< + RequestTestRenderData, + RequestTestViewData, + RequestTestRenderer, + RequestTestRenderer + >( + initialFrame: CGRect(origin: .zero, size: CGSize(width: 10, height: 10)), + renderer: RequestTestRenderer(), + placeholderRenderer: nil, + uiScheduler: ImmediateScheduler(), + imageCreationScheduler: ImmediateScheduler() + ) + window = UIWindow() + window.addSubview(view) + view.data = RequestTestViewData(counter: counter) + } + + it("does not create render data for frame origin changes") { + for offset in 1...10 { + view.frame.origin.x = CGFloat(offset) + } + + expect(counter.value) == 1 + } + + it("does not create render data for bounds origin changes") { + for offset in 1...10 { + view.bounds.origin.x = CGFloat(offset) + } + + expect(counter.value) == 1 + } + + it("creates render data for size changes") { + view.frame.size = CGSize(width: 20, height: 20) + view.bounds.size = CGSize(width: 30, height: 30) + + expect(counter.value) == 3 + } + } + } +} + +private struct RequestTestViewData: ImageViewDataType { + let counter: Atomic + + func renderDataWithSize(_ size: CGSize) -> RequestTestRenderData { + self.counter.modify { $0 += 1 } + + return RequestTestRenderData(size: size) + } +} + +private struct RequestTestRenderData: RenderDataType { + let size: CGSize +} + +private final class RequestTestRenderer: RendererType { + func renderImageWithData(_ data: RequestTestRenderData) -> SignalProducer { + return SignalProducer(value: UIImage()) + } +}