Skip to content

Release the image view while its download is still in flight - #2561

Merged
onevcat merged 1 commit into
onevcat:masterfrom
devzahirul:fix/2313-release-imageview-during-download
Jul 25, 2026
Merged

Release the image view while its download is still in flight#2561
onevcat merged 1 commit into
onevcat:masterfrom
devzahirul:fix/2313-release-imageview-during-download

Conversation

@devzahirul

Copy link
Copy Markdown
Contributor

What

Fixes #2313.

imageView.kf.setImage(with:) hands several closures to KingfisherManager.retrieveImage (downloadTaskUpdated, progressiveImageSetter, completionHandler). Each of them captures self, which is a KingfisherWrapper value that references the target image view strongly. Those closures live on the in-flight SessionDataTask until the download finishes, so the image view is kept alive until the request completes — even after its owner has been released.

In practice, an image view inside a view controller that is popped/dismissed while an image is still downloading is not deallocated until the download finishes (or until cancelDownloadTask() is called manually, as suggested in the issue thread).

Fix

Hold the image view weakly inside those callbacks through a tiny WeakBox:

  • downloadTaskUpdated / progressiveImageSetter become no-ops once the view is gone.
  • completionHandler skips all UI work when the view is gone, but still forwards the result to the caller so anyone awaiting completion is not left hanging.

The download itself is untouched — it keeps running and still populates the cache — and the behavior is identical while the view is alive. This only removes Kingfisher's own retention of the view; there is no public API change.

Scope note: this PR addresses the UIImageView / NSImageView path reported in #2313. The button / CPListItem / NSTextAttachment paths in HasImageComponent+Kingfisher.swift use an accessor-closure indirection and can get the same treatment in a follow-up; I kept this PR focused per the contribution guidelines.

Tests

Added testImageViewNotRetainedByInFlightDownload in ImageViewExtensionTests:

  1. Start a download backed by a delayed stub, so it stays in flight.
  2. Release the only strong reference to the image view.
  3. Assert the view is deallocated immediately (a weak reference becomes nil).
  4. Let the stub finish and assert the image is still cached, proving the download was not cancelled.

I confirmed the test fails on master (the view is retained) and passes with this change. The full ImageViewExtensionTests suite (35 tests) passes on the iOS simulator.

Why it matters

Keeping a view alive for the duration of a download is surprising and can noticeably increase peak memory in image-heavy, fast-scrolling screens where cells/controllers are torn down before their images arrive. Capturing the view weakly matches the common expectation (and the behavior of other image libraries) that setting an image should not extend the view's lifetime.

`setImage(with:...)` handed closures to `KingfisherManager.retrieveImage`
that captured `self` (the `KingfisherWrapper`), which references the image
view strongly. An in-flight download therefore kept the view alive until it
finished, so a view whose owner had already been released (for example, an
image view inside a popped view controller) was deallocated late.

Hold the view weakly in the download callbacks through a small `WeakBox`.
When the view is already gone as a callback runs, the UI update is skipped
and the result is still forwarded to the caller. The download keeps running
and still populates the cache, so behavior is unchanged while the view is
alive.

Closes onevcat#2313

@onevpaw onevpaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the exact head: the weak capture cleanly removes the ImageView retention path while preserving download, cache, and UI-update behavior. The targeted iOS and macOS tests pass.

Optional follow-up: add a regression test confirming that completionHandler is still invoked after the ImageView has been released. This is not a blocking issue.

The workflows observed during review still required approval before producing jobs, so I am leaving a comment rather than an approval.

onevpaw - an assistant to @onevcat

@onevcat

onevcat commented Jul 25, 2026

Copy link
Copy Markdown
Owner

@devzahirul Make sense! Thank you for the fix.

@onevcat
onevcat merged commit 91c1ab2 into onevcat:master Jul 25, 2026
24 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ImageView retained by kingfisher when it should be dealloc

3 participants