Fix duplicate didFinishDownloadingImageForURL delegate callback#2548
Open
onevcat wants to merge 1 commit into
Open
Fix duplicate didFinishDownloadingImageForURL delegate callback#2548onevcat wants to merge 1 commit into
onevcat wants to merge 1 commit into
Conversation
A completed download invoked the delegate's imageDownloader(_:didFinishDownloadingImageForURL:with:error:) twice: once from the onDownloadingFinished forwarding in setupSessionHandler() and once from reportDidDownloadImageData in the onTaskDone handler. The forwarding block was added commented-out in 8b6d412 (aware of the onTaskDone path) but activated in 83cfe18 without removing the other call, shipping with 5.0. The duplication was also inconsistent: successful and network-errored downloads notified twice, while invalid-status-code or cancelled downloads notified once (the second path was filtered by the task having been removed). When the delegate's data-modifying method returned nil, the first notification even reported a nil error before the second reported the failure. Remove the onDownloadingFinished path and keep the onTaskDone one, which covers every termination (success, error, cancellation, invalid status code, data modification failure) with the correct error value. The delegate is now notified exactly once per download completion.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
imageDownloader(_:didFinishDownloadingImageForURL:with:error:): theonDownloadingFinishedforwarding inImageDownloader.setupSessionHandler()and its hook inSessionDelegateare deleted; thereportDidDownloadImageDatacall instartDownloadTask'sonTaskDonehandler remains as the single notification point.The bug
A completed download notified the delegate twice, back to back, from the same
urlSession(_:task:didCompleteWithError:)flow:SessionDelegateline 211 →onDownloadingFinished→ forwarding closure insetupSessionHandler()SessionDelegateline 224 →onCompleted→onTaskDone→reportDidDownloadImageDataThe call count was also inconsistent, verified by the new tests (before the fix):
didDownload datareturnsnil)error: nil(a "success"), second reported the failureHistory
The duplication shipped with 5.0 and survived for over seven years:
onTaskDonepath existed.onDownloadingFinishedhook was added with the forwarding commented out — the duplication was evidently known at the time.onTaskDonecall. The duplicate was born.It went unnoticed because the callback is typically used for logging/metrics where duplication is low-symptom, and the method had zero test coverage.
Why keep the
onTaskDonepath (and not the other one)The
onTaskDonepath covers every termination — success, session error, cancellation, invalid status code, and data-modifying failure — with the correcterrorvalue. TheonDownloadingFinishedpath only fired whendidCompleteWithErrorstill found the task registered, and reportederror: nilfor data-modifying failures because it only saw the transport-level result.Note:
KingfisherError.ResponseErrorReason.noURLResponse(error code 2005) no longer has a producer after this change — it was only ever synthesized to feed the duplicate call and never reached a completion handler. The public enum case remains for API compatibility.Tests
testDidFinishDownloadingDelegateCalledOncePerSuccessfulDownload,testDidFinishDownloadingDelegateCalledOncePerFailedDownload,testDidFinishDownloadingDelegateCalledOncePerErroredDownload,testDidFinishDownloadingDelegateConsistentWhenDataModifyingFails. All four fail againstmaster(counting 2 calls / a nil error on the first event) and pass with this change.Known remaining wrinkles (documented, intentionally not addressed here)
didDownload image for urlis notified once per attached callback (processing itself is deduplicated per processor identifier, the notification is not).didFinishDownloadingImageForURLwithtaskCancelledwhile the download continues for remaining callers, followed by a second (correct) notification at actual completion — a consequence ofonTaskDoneserving both per-callback delivery and terminal notification. Both are pre-existing, low-impact, and left for a separate discussion.