Kingfisher Documentation

A lightweight, pure-Swift library for downloading and caching images from the web.

24,390 stars Swift Swift 1 file · ~26,740 tokens #cache#filters#image#image-processor#ios#kingfisher#macos#swift
RAW Doc

File: README.md

Kingfisher is a powerful, pure-Swift library for downloading and caching images from the web. It provides you a chance to use a pure-Swift way to work with remote images in your next app.

Features

  • Asynchronous image downloading and caching.
  • Loading image from either URLSession-based networking or local provided data.
  • Useful image processors and filters provided.
  • Multiple-layer hybrid cache for both memory and disk.
  • Fine control on cache behavior. Customizable expiration date and size limit.
  • Opt-in async cache probing in KingfisherManager to avoid blocking the caller thread on disk cache checks.
  • Cancelable downloading and auto-reusing previous downloaded content to improve performance.
  • Independent components. Use the downloader, caching system, and image processors separately as you need.
  • Prefetching images and showing them from the cache to boost your app.
  • Extensions for UIImageView, NSImageView, NSButton, UIButton, NSTextAttachment, WKInterfaceImage, TVMonogramView and CPListItem to directly set an image from a URL.
  • Built-in transition animation when setting images.
  • Customizable placeholder and indicator while loading images.
  • Extensible image processing and image format easily.
  • Low Data Mode support.
  • SwiftUI support.
  • Swift 6 & Swift Concurrency (strict mode) prepared.
  • Load & cache for Live Photo.

Kingfisher 101

The simplest use-case is setting an image to an image view with the UIImageView extension:

swift
import Kingfisher

let url = URL(string: "https://example.com/image.png")
imageView.kf.setImage(with: url)

Kingfisher will download the image from url, send it to both memory cache and disk cache, and display it in imageView.
When you set it with the same URL later, the image will be retrieved from the cache and shown immediately.

It also works if you use SwiftUI:

swift
var body: some View {
    KFImage(URL(string: "https://example.com/image.png")!)
}

A More Advanced Example

With the powerful options, you can do hard tasks with Kingfisher in a simple way. For example, the code below:

  1. Downloads a high-resolution image.
  2. Downsamples it to match the image view size.
  3. Makes it round cornered with a given radius.
  4. Shows a system indicator and a placeholder image while downloading.
  5. When prepared, it animates the small thumbnail image with a "fade in" effect.
  6. The original large image is also cached to disk for later use, to get rid of downloading it again in a detail view.
  7. A console log is printed when the task finishes, either for success or failure.
swift
let url = URL(string: "https://example.com/high_resolution_image.png")
let processor = DownsamplingImageProcessor(size: imageView.bounds.size)
             |> RoundCornerImageProcessor(cornerRadius: 20)
imageView.kf.indicatorType = .activity
imageView.kf.setImage(
    with: url,
    placeholder: UIImage(named: "placeholderImage"),
    options: [
        .processor(processor),
        .scaleFactor(UIScreen.main.scale),
        .transition(.fade(1)),
        .cacheOriginalImage
    ])
{
    result in
    switch result {
    case .success(let value):
        print("Task done for: \(value.source.url?.absoluteString ?? "")")
    case .failure(let error):
        print("Job failed: \(error.localizedDescription)")
    }
}

It is a common situation I can meet in my daily work. Think about how many lines you need to write without
Kingfisher!

Method Chaining

If you are not a fan of the kf extension, you can also prefer to use the KF builder and chained the method
invocations. The code below is doing the same thing:

swift
// Use `kf` extension
imageView.kf.setImage(
    with: url,
    placeholder: placeholderImage,
    options: [
        .processor(processor),
        .loadDiskFileSynchronously,
        .cacheOriginalImage,
        .transition(.fade(0.25)),
        .lowDataMode(.network(lowResolutionURL))
    ],
    progressBlock: { receivedSize, totalSize in
        // Progress updated
    },
    completionHandler: { result in
        // Done
    }
)

// Use `KF` builder
KF.url(url)
  .placeholder(placeholderImage)
  .setProcessor(processor)
  .loadDiskFileSynchronously()
  .cacheMemoryOnly()
  .fade(duration: 0.25)
  .lowDataModeSource(.network(lowResolutionURL))
  .onProgress { receivedSize, totalSize in  }
  .onSuccess { result in  }
  .onFailure { error in }
  .set(to: imageView)

And even better, if later you want to switch to SwiftUI, just change the KF above to KFImage, and you've done:

swift
struct ContentView: View {
    var body: some View {
        KFImage.url(url)
          .placeholder(placeholderImage)
          .setProcessor(processor)
          .loadDiskFileSynchronously()
          .cacheMemoryOnly()
          .fade(duration: 0.25)
          .lowDataModeSource(.network(lowResolutionURL))
          .onProgress { receivedSize, totalSize in  }
          .onSuccess { result in  }
          .onFailure { error in }
    }
}

Requirements

Kingfisher 8.0

  • (UIKit/AppKit) iOS 13.0+ / macOS 10.15+ / tvOS 13.0+ / watchOS 6.0+ / visionOS 1.0+
  • (SwiftUI) iOS 14.0+ / macOS 11.0+ / tvOS 14.0+ / watchOS 7.0+ / visionOS 1.0+
  • Swift 5.9+

Kingfisher 7.0

  • (UIKit/AppKit) iOS 12.0+ / macOS 10.14+ / tvOS 12.0+ / watchOS 5.0+ / visionOS 1.0+
  • (SwiftUI) iOS 14.0+ / macOS 11.0+ / tvOS 14.0+ / watchOS 7.0+ / visionOS 1.0+
  • Swift 5.0+

Installation

Refer to one of the following tutorials to install and use the framework:

Alternatively, you can follow either of the methods below.

Swift Package Manager

  • File > Swift Packages > Add Package Dependency
  • Add https://github.com/onevcat/Kingfisher.git
  • Select "Up to Next Major" with "8.0.0"

CocoaPods

ruby
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '13.0'
use_frameworks!

target 'MyApp' do
  pod 'Kingfisher', '~> 8.0'
end

Pre-built Framework

  1. Open the release page, download the latest version of Kingfisher from the assets section.
  2. Drag the Kingfisher.xcframework into your project and add it to the target (usually the app target).
  3. Select your target, in the "General" Tab, find the "Frameworks, Libraries, and Embedded Content" section, set the Embed Without Signing to Kingfisher.

Documentation

Check the documentation and tutorials:

Migrating

If you are using an even earlier version, see the guides below to know the steps for migrating.

Other

Future of Kingfisher

I want to keep Kingfisher lightweight. This framework focuses on providing a simple solution for downloading and caching images. This doesn’t mean the framework can’t be improved. Kingfisher is far from perfect, so necessary and useful updates will be made to make it better.

Developments and Tests

Any contributing and pull requests are warmly welcome. However, before you plan to implement some features or try to fix an uncertain issue, it is recommended to open a discussion first. It would be appreciated if your pull requests could build with all tests green. :)

The logo of Kingfisher is inspired by Tangram (七巧板), a dissection puzzle consisting of seven flat shapes from China. I believe she's a kingfisher bird instead of a swift, but someone insists that she is a pigeon. I guess I should give her a name. Hi, guys, do you have any suggestions?

Contact

Follow and contact me on Twitter or Sina Weibo. If you find an issue, open a ticket. Pull requests are warmly welcome as well.

Backers & Sponsors

Open-source projects cannot live long without your help. If you find Kingfisher to be useful, please consider supporting this
project by becoming a sponsor. Your user icon or company logo shows up on my blog with a link to your home page.

Become a sponsor through GitHub Sponsors. :heart:

Special thanks to:

License

Kingfisher is released under the MIT license. See LICENSE for details.


File: docs/architecture.md

Kingfisher Architecture Documentation

High-Level System Organization

Kingfisher is a sophisticated image loading and caching library for Apple platforms, designed with a modular architecture that promotes separation of concerns and extensibility. At its core, the library employs a coordinator pattern where KingfisherManager serves as the central orchestrator, managing the flow between network operations, caching layers, and image processing pipelines. The architecture leverages protocol-oriented design principles, with all functionality exposed through a .kf namespace wrapper that provides a clean, chainable API surface.

The system is built on three fundamental pillars: downloading, caching, and processing. The ImageDownloader handles all network operations with support for authentication, retries, and progressive loading. The ImageCache implements a dual-layer caching strategy combining memory and disk storage for optimal performance. The ImageProcessor protocol enables a flexible transformation pipeline where multiple processors can be chained together. These components work in concert through a sophisticated options system (KingfisherOptionsInfo) that allows fine-grained control over every aspect of the image loading process.

Cross-platform compatibility is achieved through extensive use of conditional compilation and type aliases, allowing the same codebase to support iOS, macOS, tvOS, watchOS, and visionOS. The library provides both UIKit/AppKit extensions and dedicated SwiftUI components (KFImage, KFAnimatedImage), ensuring seamless integration regardless of the UI framework being used.

Component Map

Core Components

Component Location Purpose
KingfisherManager Sources/General/KingfisherManager.swift Central coordinator managing image retrieval, caching, and processing workflows
ImageDownloader Sources/Networking/ImageDownloader.swift Handles all network operations for downloading images
ImageCache Sources/Cache/ImageCache.swift Dual-layer caching system with memory and disk storage
ImageProcessor Sources/Image/ImageProcessor.swift Protocol and implementations for image transformation pipeline
KF Sources/General/KF.swift Builder pattern entry point for fluent API
Source Sources/General/ImageSource/Source.swift Represents image data sources (network/provider)
Resource Sources/General/ImageSource/Resource.swift Protocol for cacheable resources with key/URL

Storage Layer

Component Location Purpose
MemoryStorage Sources/Cache/MemoryStorage.swift In-memory cache implementation with LRU eviction
DiskStorage Sources/Cache/DiskStorage.swift File-based cache with expiration and size limits
CacheSerializer Sources/Cache/CacheSerializer.swift Handles image data serialization for cache storage

Networking Layer

Component Location Purpose
SessionDelegate Sources/Networking/SessionDelegate.swift URLSession delegate for download management
SessionDataTask Sources/Networking/SessionDataTask.swift Wrapper for URLSessionDataTask with cancellation
ImagePrefetcher Sources/Networking/ImagePrefetcher.swift Preloads images for improved performance
RequestModifier Sources/Networking/RequestModifier.swift Protocol for modifying URL requests
RetryStrategy Sources/Networking/RetryStrategy.swift Configurable retry logic for failed downloads

UI Integration

Component Location Purpose
ImageView+Kingfisher Sources/Extensions/ImageView+Kingfisher.swift UIImageView/NSImageView extensions
KFImage Sources/SwiftUI/KFImage.swift SwiftUI image component
KFAnimatedImage Sources/SwiftUI/KFAnimatedImage.swift SwiftUI animated image support
AnimatedImageView Sources/Views/AnimatedImageView.swift GIF animation support view

Key Files

KingfisherManager.swift (Lines 107-420)

The heart of the library, containing:

  • shared singleton instance (line 113)
  • retrieveImage() main entry point (lines 196-210, 233-248)
  • Cache lookup logic (lines 400-403)
  • Download coordination (lines 415-418)
  • Retry and alternative source handling (lines 306-385)

ImageDownloader.swift (Lines 35-150)

Network layer implementation:

  • ImageLoadingResult struct for download results (lines 36-58)
  • DownloadTask class for cancellable downloads (lines 65-102)
  • URLSession management and request handling

ImageCache.swift (Lines 52-200)

Caching infrastructure:

  • CacheType enum defining cache levels (lines 52-72)
  • Memory and disk cache coordination
  • Cache key generation and expiration logic

KF.swift (Lines 50-100)

Builder pattern implementation:

  • Static factory methods for creating builders (lines 56-99)
  • Fluent API entry points for different source types

ImageProcessor.swift (Lines 37-100)

Processing pipeline:

  • ImageProcessItem enum for input types (lines 37-46)
  • ImageProcessor protocol definition (lines 49-76)
  • Processor chaining via append() (lines 85-95)

KingfisherOptionsInfo.swift (Lines 43-250)

Configuration system:

  • Option items enumeration with associated values
  • Cache, downloader, and processor configuration
  • Transition and placeholder settings

Data Flow

1. Image Request Initiation

text
UIImageView.kf.setImage(with: url)
    │
    └─> ImageView+Kingfisher.swift (line 77-87)
        │
        └─> KingfisherManager.retrieveImage()
            Sources/General/KingfisherManager.swift (line 196)

2. Cache Lookup

text
KingfisherManager.retrieveImage()
    │
    └─> retrieveImageFromCache() (line 400)
        │
        ├─> ImageCache.retrieveImage()
        │   Sources/Cache/ImageCache.swift
        │   │
        │   ├─> MemoryStorage.value(forKey:)
        │   │   Sources/Cache/MemoryStorage.swift
        │   │
        │   └─> DiskStorage.value(forKey:)
        │       Sources/Cache/DiskStorage.swift
        │
        └─> [Cache Hit] → completionHandler(.success)
            [Cache Miss] → Continue to download

3. Network Download

text
KingfisherManager.loadAndCacheImage() (line 415)
    │
    └─> ImageDownloader.downloadImage()
        Sources/Networking/ImageDownloader.swift
        │
        ├─> SessionDelegate.downloadTask()
        │   Sources/Networking/SessionDelegate.swift
        │
        └─> URLSession.dataTask()
            │
            └─> CompletionHandler with ImageLoadingResult

4. Image Processing

text
Downloaded Data
    │
    └─> ImageProcessor.process() (line 434)
        Sources/Image/ImageProcessor.swift
        │
        ├─> DefaultImageProcessor (if none specified)
        │
        └─> Custom processors chain
            │
            └─> Processed KFCrossPlatformImage

5. Cache Storage

text
Processed Image
    │
    └─> KingfisherManager.cacheImage() (line 459)
        │
        ├─> ImageCache.store() (line 482)
        │   │
        │   ├─> MemoryStorage.store()
        │   │   In-memory cache with cost calculation
        │   │
        │   └─> DiskStorage.store()
        │       File system with expiration
        │
        └─> completionHandler(.success(RetrieveImageResult))
            │
            └─> UI Update on main queue

6. Error Handling and Retry

text
Download/Processing Error
    │
    └─> RetryStrategy.retry() (line 362)
        Sources/Networking/RetryStrategy.swift
        │
        ├─> [Retry] → startNewRetrieveTask() (line 306)
        │
        └─> [No Retry] → Check alternative sources (line 334)
            │
            ├─> [Alternative exists] → Start new task
            │
            └─> [No alternatives] → completionHandler(.failure)

This architecture enables Kingfisher to efficiently handle image loading with features like progressive downloading, multiple cache layers, flexible processing pipelines, and robust error handling, all while maintaining a clean and intuitive API surface for developers.


File: docs/build-system.md

Kingfisher Build System Documentation

Overview

Kingfisher uses a dual build system approach supporting both Swift Package Manager and Fastlane/CocoaPods for maximum flexibility and distribution options.

Primary Build Tools

  • Swift Package Manager (Package.swift) - Modern dependency management and building
  • Fastlane (fastlane/Fastfile) - Automated testing, building, and release workflows
  • CocoaPods (Kingfisher.podspec) - Legacy distribution and integration
  • GitHub Actions (.github/workflows/) - Continuous integration and testing

Key Configuration Files

text
.
├── Package.swift                    # Swift Package Manager configuration
├── Kingfisher.podspec              # CocoaPods specification
├── Gemfile                         # Ruby dependencies for Fastlane/CocoaPods
├── fastlane/
│   ├── Fastfile                    # Fastlane automation workflows
│   └── actions/                    # Custom Fastlane actions
└── .github/workflows/
    ├── build.yaml                  # CI build workflow
    └── test.yaml                   # CI test workflow

Build Workflows

Building with Swift Package Manager

bash
# Build for default platform
swift build

# Build with specific Swift version
swift build -Xswiftc -swift-version -Xswiftc 5

# Build for release
swift build -c release

# Build and run tests
swift test

# Generate Xcode project (if needed)
swift package generate-xcodeproj

Building with Fastlane

First, install dependencies:

bash
# Install Ruby dependencies
bundle install

Common build commands:

bash
# Run all platform tests (iOS, macOS, tvOS, watchOS)
bundle exec fastlane tests

# Build for CI with specific destination
DESTINATION="platform=iOS Simulator,name=iPhone 15,OS=17.5" bundle exec fastlane build_ci

# Test for CI with specific destination  
DESTINATION="platform=macOS" bundle exec fastlane test_ci

# Build specific platform
bundle exec fastlane build destination:"platform=iOS Simulator,name=iPhone 15"

# Lint both CocoaPods and SPM
bundle exec fastlane lint

Release Process

The release workflow automates versioning, tagging, and distribution:

bash
# Full release (tests, lint, version bump, GitHub release, CocoaPods push)
bundle exec fastlane release version:X.X.X

# Skip tests during release
bundle exec fastlane release version:X.X.X skip_tests:true

# Create XCFramework for distribution
bundle exec fastlane xcframework version:X.X.X

Release steps performed:

  1. Ensures clean git state and correct branch
  2. Runs all tests (unless skipped)
  3. Lints CocoaPods spec and SPM package
  4. Updates version in all configuration files
  5. Extracts and updates changelog
  6. Creates signed git tag
  7. Builds XCFramework for all platforms
  8. Creates GitHub release with assets
  9. Pushes to CocoaPods trunk

Platform-specific Setup

Supported Platforms

From Package.swift and Kingfisher.podspec:

  • iOS: 13.0+
  • macOS: 10.15+
  • tvOS: 13.0+
  • watchOS: 6.0+
  • visionOS: 1.0+

CI Test Matrix

From .github/workflows/test.yaml:

  • Destinations: macOS, iOS Simulator, tvOS Simulator, watchOS Simulator
  • Xcode Versions: 15.4, 16.2

Platform Build Commands

bash
# macOS
bundle exec fastlane test destination:"platform=macOS"

# iOS Simulator
bundle exec fastlane test destination:"platform=iOS Simulator,name=iPhone 15,OS=17.5"

# tvOS Simulator  
bundle exec fastlane test destination:"platform=tvOS Simulator,name=Apple TV,OS=17.5"

# watchOS Simulator (build only, no test)
bundle exec fastlane build destination:"platform=watchOS Simulator,name=Apple Watch Series 9 (41mm),OS=10.5"

Reference

Build Targets

From Package.swift:

  • Library: Kingfisher (single library product)
  • Target: Kingfisher (sources in Sources/ directory)

Fastlane Lanes

Available lanes in fastlane/Fastfile:

Lane Description Parameters
tests Run tests on all platforms None
test Run tests on specific platform destination
build Build for specific platform destination
test_ci CI test lane (builds watchOS) Uses ENV["DESTINATION"]
build_ci CI build lane Uses ENV["DESTINATION"]
lint Lint CocoaPods spec and SPM None
release Full release workflow version, skip_tests (optional)
xcframework Build XCFramework version, swift_version, xcode_version

Environment Variables

Variable Description Used By
DESTINATION Build/test destination CI workflows
XCODE_VERSION Xcode version to use CI workflows, Fastlane
GITHUB_TOKEN GitHub API token Release workflow

Custom Fastlane Actions

Located in fastlane/actions/:

  • extract_current_change_log.rb - Extract changelog for version
  • git_commit_all.rb - Commit all changes
  • sync_build_number_to_git.rb - Sync build number with git
  • update_change_log.rb - Update changelog file

Troubleshooting

Common Issues

  1. Bundle install fails

    bash
    # Update bundler
    gem install bundler
    
    # Install with specific bundler version
    bundle _2.x.x_ install
  2. Xcode version mismatch

    bash
    # Set Xcode version explicitly
    XCODE_VERSION=16.2 bundle exec fastlane tests
    
    # Or use xcode-select
    sudo xcode-select -s /Applications/Xcode_16.2.app
  3. Simulator not found

    bash
    # List available simulators
    xcrun simctl list devices
    
    # Update destination string accordingly
  4. CocoaPods push fails

    bash
    # Verify pod spec locally first
    pod lib lint Kingfisher.podspec
    
    # Register session if needed
    pod trunk register [email protected]
  5. Swift version issues

    bash
    # Override Swift version in build
    bundle exec fastlane build xcargs:"SWIFT_VERSION=5.9"

Build Settings

Key build settings used:

  • BUILD_LIBRARY_FOR_DISTRIBUTION: YES (for XCFramework)
  • SKIP_INSTALL: NO (for archiving)
  • SWIFT_VERSION: 5.0 (default, can be overridden)

Distribution Artifacts

Release builds generate:

  • Kingfisher-{version}.xcframework.zip - All platforms XCFramework
  • Kingfisher-iOS-{version}.xcframework.zip - iOS-only XCFramework

Both are code-signed with Apple Distribution certificate and uploaded to GitHub releases.


File: docs/deployment.md

Deployment Guide

This document provides comprehensive guidance for deploying Kingfisher across different platforms and distribution channels.

Overview

Kingfisher supports multiple deployment strategies:

  • CocoaPods: Traditional CocoaPods spec deployment
  • Swift Package Manager: Native Swift package distribution
  • XCFramework: Pre-built universal frameworks
  • GitHub Releases: Automated release management

The deployment process is fully automated using Fastlane with comprehensive platform coverage across iOS, macOS, tvOS, watchOS, and visionOS.

Package Types

CocoaPods Distribution

Configuration File: /Users/onevcat/Sync/github/Kingfisher/Kingfisher.podspec

Build Targets:

  • iOS 13.0+
  • macOS 10.15+
  • tvOS 13.0+
  • watchOS 6.0+
  • visionOS 1.0+

Key Features:

  • Module stability enabled (BUILD_LIBRARY_FOR_DISTRIBUTION)
  • Privacy manifest included (PrivacyInfo.xcprivacy)
  • Weak framework dependencies (SwiftUI, Combine)
  • Required frameworks (CFNetwork, Accelerate)

Swift Package Manager

Configuration File: /Users/onevcat/Sync/github/Kingfisher/Package.swift

Build Targets:

  • Single library target: Kingfisher
  • Source path: Sources/
  • Minimum Swift tools version: 5.1

Platform Support:

  • iOS 13.0+
  • macOS 10.15+
  • tvOS 13.0+
  • watchOS 6.0+

XCFramework Distribution

Output Locations:

text
build/
├── Kingfisher-{version}.xcframework.zip          # All platforms
├── Kingfisher-iOS-{version}.xcframework.zip      # iOS only
├── Kingfisher-{version}/
│   └── Kingfisher.xcframework/
│       ├── ios-arm64/
│       ├── ios-arm64_x86_64-simulator/
│       ├── macos-arm64_x86_64/
│       ├── tvos-arm64/
│       ├── tvos-arm64_x86_64-simulator/
│       ├── watchos-arm64_arm64_32_armv7k/
│       ├── watchos-arm64_i386_x86_64-simulator/
│       ├── xros-arm64/
│       └── xros-arm64_x86_64-simulator/

Platform-Specific Archives:

text
build/
├── Kingfisher-iphoneos.xcarchive/
├── Kingfisher-iphonesimulator.xcarchive/
├── Kingfisher-macosx.xcarchive/
├── Kingfisher-appletvos.xcarchive/
├── Kingfisher-appletvsimulator.xcarchive/
├── Kingfisher-watchos.xcarchive/
├── Kingfisher-watchsimulator.xcarchive/
├── Kingfisher-xros.xcarchive/
└── Kingfisher-xrsimulator.xcarchive/

Platform-Specific Deployment

iOS Deployment

  • Device: iphoneos SDK
  • Simulator: iphonesimulator SDK
  • Architectures: arm64, x86_64 (simulator)
  • Framework Path: build/Kingfisher-iOS-{version}.xcframework.zip

macOS Deployment

  • SDK: macosx
  • Architectures: arm64, x86_64 (universal)
  • Framework Structure: Traditional bundle format with versioning

tvOS Deployment

  • Device: appletvos SDK
  • Simulator: appletvsimulator SDK
  • Architectures: arm64, x86_64 (simulator)

watchOS Deployment

  • Device: watchos SDK
  • Simulator: watchsimulator SDK
  • Architectures: arm64, arm64_32, armv7k (device), i386, x86_64, arm64 (simulator)

visionOS Deployment

  • Device: xros SDK
  • Simulator: xrsimulator SDK
  • Architectures: arm64, x86_64 (simulator)

Deployment Commands

Complete Release Process

bash
# Full release workflow
bundle exec fastlane release version:X.X.X

# Skip tests during release (not recommended)
bundle exec fastlane release version:X.X.X skip_tests:true

Individual Components

bash
# Create XCFramework only
bundle exec fastlane xcframework version:X.X.X

# Run linting
bundle exec fastlane lint

# Build for specific platform
bundle exec fastlane build_ci

CocoaPods Deployment

bash
# Lint podspec
pod lib lint Kingfisher.podspec

# Push to CocoaPods trunk (automated in release)
pod trunk push Kingfisher.podspec

Swift Package Manager

bash
# Build with SPM
swift build

# Test with SPM
swift test

# Validate package
swift package resolve

Continuous Integration

GitHub Actions Workflows

Build Workflow: .github/workflows/build.yaml

  • Builds across multiple Xcode versions (15.2, 15.3, 16.0, 16.1)
  • Tests all platforms in matrix configuration
  • Uses self-hosted runners

Test Workflow: .github/workflows/test.yaml

  • Runs tests on Xcode 15.4 and 16.2
  • Covers all platform destinations
  • Concurrent execution with cancellation

Matrix Configuration:

yaml
destination: [
  'macOS',
  'iOS Simulator,name=iPhone 15,OS=17.5',
  'tvOS Simulator,name=Apple TV,OS=17.5',
  'watchOS Simulator,name=Apple Watch Series 9 (41mm),OS=10.5'
]

Release Management

Automated Release Process

The release process handles:

  1. Pre-release Validation:

    • Git branch verification
    • Clean git status check
    • Comprehensive testing across platforms
    • Podspec and SPM linting
  2. Version Management:

    • Build number synchronization
    • Version number increment
    • Podspec version update
    • Changelog extraction
  3. Build Artifacts:

    • XCFramework creation for all platforms
    • Code signing with Apple Distribution certificate
    • ZIP archive creation
  4. Distribution:

    • Git tag creation with signing
    • GitHub release creation
    • Asset upload (both full and iOS-only XCFrameworks)
    • CocoaPods trunk push

Changelog Management

Configuration: /Users/onevcat/Sync/github/Kingfisher/pre-change.yml

Structure:

yaml
version: X.X.X
name: Release Name
fix:
  - Bug fix descriptions with issue links
add:
  - New feature descriptions

Version Tagging

  • Format: Semantic versioning (X.X.X)
  • Signing: GPG signed tags
  • Automation: Integrated with release workflow

Reference

Deployment Scripts

Script Location Purpose
fastlane/Fastfile Main deployment automation
.github/workflows/build.yaml CI build workflow
.github/workflows/test.yaml CI test workflow
Gemfile Ruby dependencies

Build Output Locations

Package Type Location
CocoaPods Published to CocoaPods trunk
Swift Package Manager Git repository tags
XCFramework (All) build/Kingfisher-{version}.xcframework.zip
XCFramework (iOS) build/Kingfisher-iOS-{version}.xcframework.zip
GitHub Release Assets Attached to release tags

Environment Variables

Variable Purpose Required
GITHUB_TOKEN GitHub API authentication Yes (release)
DESTINATION CI build destination Yes (CI)
XCODE_VERSION Xcode version selection Yes (CI)

Code Signing

  • Certificate: Apple Distribution: Wei Wang (A4YJ9MRZ66)
  • Timestamp: Enabled for all XCFramework builds
  • Verification: Automated signature verification

Server Configurations

GitHub Actions:

  • Runner Type: Self-hosted
  • Concurrency: Group-based with cancellation
  • Shell: bash -leo pipefail {0}

Ruby Environment:

  • Fastlane: Latest version
  • CocoaPods: Latest version
  • xcodes: For Xcode version management

This deployment system ensures reliable, automated distribution across all supported Apple platforms with comprehensive testing and validation at each step.


File: docs/development.md

Development Guide

Overview

Kingfisher follows a modular architecture designed for maintainability, testability, and cross-platform compatibility. The development environment emphasizes protocol-oriented programming, namespace safety, and comprehensive test coverage. The codebase is organized into distinct functional modules with clear separation of concerns, following Swift's modern concurrency patterns and maintaining compatibility across iOS, macOS, tvOS, watchOS, and visionOS platforms.

The codebase implements several sophisticated design patterns including the namespace wrapper pattern for API safety, builder patterns for fluent configuration, and options patterns for flexible customization. All components are designed to be thread-safe with explicit concurrency annotations where needed. Development follows strict code style guidelines with comprehensive documentation, ensuring consistency across the large codebase.

Testing is integral to the development process, with extensive unit tests covering all major components, network mocking for reliable testing, and cross-platform validation. The build system uses Fastlane for automation, supporting multiple deployment targets and maintaining high quality standards through automated linting and testing workflows.

Code Style Conventions

File Headers and Documentation

All source files must include the standard license header:

swift
//
//  FileName.swift
//  Kingfisher
//
//  Created by [Author] on [Date].
//
//  Copyright (c) 2019 Wei Wang <[email protected]>
//
//  Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software")...

Naming Conventions

  • Files: Use PascalCase with descriptive names (ImageProcessor.swift, KingfisherManager.swift)
  • Types: PascalCase for classes, structs, protocols, and enums
  • Methods/Properties: camelCase starting with lowercase
  • Constants: Use static let for type constants, let for instance constants
  • Protocols: Use descriptive names, often ending with -able or describing capability

Example from /Users/onevcat/Sync/github/Kingfisher/Sources/General/Kingfisher.swift:

swift
public protocol KingfisherCompatible: AnyObject { }
public protocol KingfisherCompatibleValue {}

Cross-Platform Type Aliases

Kingfisher uses consistent cross-platform type aliases defined in /Users/onevcat/Sync/github/Kingfisher/Sources/General/Kingfisher.swift:

swift
#if os(macOS)
public typealias KFCrossPlatformImage       = NSImage
public typealias KFCrossPlatformView        = NSView
public typealias KFCrossPlatformColor       = NSColor
public typealias KFCrossPlatformImageView   = NSImageView
public typealias KFCrossPlatformButton      = NSButton
#else
public typealias KFCrossPlatformImage       = UIImage
public typealias KFCrossPlatformColor       = UIColor
// ... additional platform-specific definitions
#endif

Sendable Compliance

Modern Swift concurrency is enforced throughout the codebase:

swift
public struct KingfisherWrapper: @unchecked Sendable {
    public let base: Base
    public init(_ base: Base) {
        self.base = base
    }
}

Common Implementation Patterns

1. Namespace Wrapper Pattern

The core pattern used throughout Kingfisher, implemented in /Users/onevcat/Sync/github/Kingfisher/Sources/General/Kingfisher.swift:

swift
/// Wrapper for Kingfisher compatible types
public struct KingfisherWrapper: @unchecked Sendable {
    public let base: Base
    public init(_ base: Base) {
        self.base = base
    }
}

/// Protocol for types that can use .kf namespace
public protocol KingfisherCompatible: AnyObject { }

extension KingfisherCompatible {
    /// Gets a namespace holder for Kingfisher compatible types
    public var kf: KingfisherWrapper<Self> {
        get { return KingfisherWrapper(self) }
        set { }
    }
}

// Usage in extensions
extension KFCrossPlatformImage: KingfisherCompatible { }

2. Builder Pattern

Fluent API implementation in /Users/onevcat/Sync/github/Kingfisher/Sources/General/KF.swift:

swift
public enum KF {
    /// Creates a builder for a given URL
    public static func url(_ url: URL?, cacheKey: String? = nil) -> KF.Builder {
        source(url?.convertToSource(overrideCacheKey: cacheKey))
    }
}

extension KF {
    public class Builder: @unchecked Sendable {
        private let source: Source?
        private var _options = KingfisherParsedOptionsInfo(KingfisherManager.shared.defaultOptions)
        
        // Fluent configuration methods
        public func placeholder(_ image: KFCrossPlatformImage?) -> Self {
            self.placeholder = image
            return self
        }
    }
}

3. Options Pattern

Comprehensive options system in /Users/onevcat/Sync/github/Kingfisher/Sources/General/KingfisherOptionsInfo.swift:

swift
/// Represents the available option items
public enum KingfisherOptionsInfoItem: Sendable {
    case targetCache(ImageCache)
    case downloader(ImageDownloader)
    case transition(ImageTransition)
    case downloadPriority(Float)
    case forceRefresh
    case processor(any ImageProcessor)
    // ... many more options
}

/// Parsed options for internal use
public struct KingfisherParsedOptionsInfo: Sendable {
    public var targetCache: ImageCache? = nil
    public var downloader: ImageDownloader? = nil
    public var transition: ImageTransition = .none
    // ... corresponding properties
    
    public init(_ info: KingfisherOptionsInfo?) {
        guard let info = info else { return }
        for option in info {
            switch option {
            case .targetCache(let value): targetCache = value
            case .downloader(let value): downloader = value
            // ... handle all options
            }
        }
    }
}

4. Protocol-Oriented Design

Example from /Users/onevcat/Sync/github/Kingfisher/Sources/Image/ImageProcessor.swift:

swift
/// Protocol for image processing
public protocol ImageProcessor: Sendable {
    /// Identifier for caching and retrieval
    var identifier: String { get }
    
    /// Process the input item
    func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage?
}

extension ImageProcessor {
    /// Append processors in pipeline
    public func append(another: any ImageProcessor) -> any ImageProcessor {
        let newIdentifier = identifier.appending("|>\(another.identifier)")
        return GeneralProcessor(identifier: newIdentifier) { item, options in
            if let image = self.process(item: item, options: options) {
                return another.process(item: .image(image), options: options)
            } else {
                return nil
            }
        }
    }
}

5. Fluent Configuration with KFOptionSetter

Protocol-based fluent API in /Users/onevcat/Sync/github/Kingfisher/Sources/General/KFOptionsSetter.swift:

swift
@MainActor
public protocol KFOptionSetter {
    var options: KingfisherParsedOptionsInfo { get nonmutating set }
    var onFailureDelegate: Delegate<KingfisherError, Void> { get }
    var onSuccessDelegate: Delegate<RetrieveImageResult, Void> { get }
    var onProgressDelegate: Delegate<(Int64, Int64), Void> { get }
}

extension KFOptionSetter {
    public func targetCache(_ cache: ImageCache) -> Self {
        options.targetCache = cache
        return self
    }
    
    public func downloader(_ downloader: ImageDownloader) -> Self {
        options.downloader = downloader
        return self
    }
}

Development Workflows

Setting Up Images for UI Components

Common pattern for UIImageView/NSImageView (file: /Users/onevcat/Sync/github/Kingfisher/Sources/Extensions/ImageView+Kingfisher.swift):

swift
// Basic usage
imageView.kf.setImage(with: url)

// With configuration
imageView.kf.setImage(
    with: url,
    placeholder: placeholderImage,
    options: [.transition(.fade(0.2)), .cacheMemoryOnly],
    completionHandler: { result in
        // Handle result
    }
)

Builder pattern approach:

swift
KF.url(imageURL)
    .placeholder(placeholderImage)
    .fade(duration: 0.2)
    .cacheMemoryOnly()
    .onSuccess { result in
        print("Image loaded: \(result.image)")
    }
    .set(to: imageView)

Adding New Image Processors

  1. Create processor conforming to ImageProcessor protocol:
swift
struct CustomProcessor: ImageProcessor {
    var identifier: String { "com.example.custom" }
    
    func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? {
        // Implementation
    }
}
  1. Add convenience method to KFOptionSetter (file: /Users/onevcat/Sync/github/Kingfisher/Sources/General/KFOptionsSetter.swift):
swift
extension KFOptionSetter {
    public func customEffect() -> Self {
        appendProcessor(CustomProcessor())
    }
}

Extending Platform Support

Add platform-specific extensions (pattern from existing platform extensions):

  1. Update type aliases in /Users/onevcat/Sync/github/Kingfisher/Sources/General/Kingfisher.swift
  2. Add compatibility conformance:
swift
#if os(newOS)
extension NewOSImageView: KingfisherCompatible { }
#endif
  1. Implement platform-specific extensions following the pattern in existing platform files

Cache Management Tasks

Working with ImageCache (main class: /Users/onevcat/Sync/github/Kingfisher/Sources/Cache/ImageCache.swift):

swift
// Configure cache
let cache = ImageCache(name: "custom")
cache.memoryStorage.config.totalCostLimit = 50 * 1024 * 1024 // 50MB
cache.diskStorage.config.sizeLimit = 200 * 1024 * 1024 // 200MB

// Use with options
imageView.kf.setImage(with: url, options: [.targetCache(cache)])

// Manual cache operations
cache.store(image, forKey: key)
cache.retrieveImage(forKey: key) { result in
    // Handle cached image
}

Reference

File Organization

text
Sources/
├── General/           # Core managers, options, data providers
│   ├── KingfisherManager.swift     # Central coordinator
│   ├── KF.swift                    # Builder pattern API
│   ├── Kingfisher.swift            # Core protocols and wrappers
│   ├── KingfisherOptionsInfo.swift # Options system
│   └── ImageSource/                # Data source abstractions
├── Networking/        # Download, prefetch, session management
│   ├── ImageDownloader.swift       # Network layer
│   ├── ImagePrefetcher.swift       # Batch prefetching
│   └── RetryStrategy.swift         # Retry logic
├── Cache/            # Multi-layer caching system
│   ├── ImageCache.swift            # Main cache interface
│   ├── MemoryStorage.swift         # Memory cache backend
│   └── DiskStorage.swift           # Disk cache backend
├── Image/            # Processing, filters, formats, transitions
│   ├── ImageProcessor.swift        # Processing protocols
│   ├── Filter.swift                # Built-in processors
│   └── ImageTransition.swift       # UI transition effects
├── Extensions/       # UIKit/AppKit integration
│   ├── ImageView+Kingfisher.swift  # Main UI extensions
│   └── UIButton+Kingfisher.swift   # Button extensions
├── SwiftUI/         # SwiftUI-specific components
│   ├── KFImage.swift               # SwiftUI image component
│   └── KFAnimatedImage.swift       # Animated SwiftUI component
├── Utility/         # Helper utilities and extensions
└── Views/           # Custom UI components

Naming Conventions

  • Manager classes: *Manager (e.g., KingfisherManager)
  • Data providers: *Provider or *DataProvider (e.g., ImageDataProvider)
  • Processors: *Processor or *ImageProcessor (e.g., BlurImageProcessor)
  • Extensions: Type+Kingfisher.swift (e.g., UIButton+Kingfisher.swift)
  • Protocols: Descriptive names often with -able suffix (KingfisherCompatible)
  • Internal utilities: Plain descriptive names (CallbackQueue, Result)

Common Issues and Solutions

Thread Safety: All public APIs are designed to be thread-safe. Use @MainActor for UI-related operations and @unchecked Sendable for wrapper types.

Memory Management: Kingfisher uses both memory and disk caching. Configure limits appropriately:

swift
ImageCache.default.memoryStorage.config.totalCostLimit = 50 * 1024 * 1024
ImageCache.default.diskStorage.config.sizeLimit = 200 * 1024 * 1024

Platform Differences: Use platform-specific compilation directives and the provided cross-platform type aliases to ensure compatibility.

Testing: Use the testing utilities in /Users/onevcat/Sync/github/Kingfisher/Tests/KingfisherTests/KingfisherTestHelper.swift and follow the mocking patterns established in existing tests.

Performance: For large images, prefer DownsamplingImageProcessor over ResizingImageProcessor for better memory efficiency.


File: docs/files.md

File Catalog - Kingfisher

Overview

Kingfisher is a modern Swift library for loading and caching images, built with a modular structure and protocol-oriented design. The project clearly separates different concerns and uses a namespace pattern (.kf) to provide a consistent API for UIKit, AppKit, and SwiftUI. Its core components include KingfisherManager as the central coordinator, ImageDownloader for handling network tasks, ImageCache for a dual-layer caching system, and ImageProcessor for managing image transformation pipelines.

The project supports multiple platforms (iOS, macOS, tvOS, watchOS, visionOS) and uses Fastlane as its main build system. It employs the XCTest framework for testing and integrates the DocC documentation system. Files are organized based on functionality, with the Sources directory divided by modules, configuration files centrally managed, and Demo projects offering complete usage examples.

Core Source Files

Primary Framework Components

  • Sources/General/KingfisherManager.swift - Central coordinator managing image loading workflow
  • Sources/General/KF.swift - Main entry point providing builder pattern API for image tasks
  • Sources/General/Kingfisher.swift - Core framework module with KingfisherCompatible protocol
  • Sources/General/KingfisherOptionsInfo.swift - Configuration options container for all operations
  • Sources/General/KingfisherError.swift - Error handling system with detailed error types
  • Sources/General/KFOptionsSetter.swift - Options configuration builder for method chaining

Image Source and Data Providers

  • Sources/General/ImageSource/Resource.swift - URL resource definitions and transformations
  • Sources/General/ImageSource/Source.swift - Abstract image source protocols and implementations
  • Sources/General/ImageSource/ImageDataProvider.swift - Data provider protocol for custom image sources
  • Sources/General/ImageSource/AVAssetImageDataProvider.swift - AVAsset image frame extraction
  • Sources/General/ImageSource/PHPickerResultImageDataProvider.swift - Photo picker integration
  • Sources/General/ImageSource/LivePhotoSource.swift - Live Photo support implementation

Network Layer

  • Sources/Networking/ImageDownloader.swift - HTTP image downloading with session management
  • Sources/Networking/ImageDownloader+LivePhoto.swift - Live Photo downloading extensions
  • Sources/Networking/ImageDownloaderDelegate.swift - Download progress and completion delegation
  • Sources/Networking/SessionDelegate.swift - URLSession delegate handling authentication and redirects
  • Sources/Networking/SessionDataTask.swift - Custom data task wrapper with cancellation support
  • Sources/Networking/ImagePrefetcher.swift - Batch image prefetching for performance optimization
  • Sources/Networking/RequestModifier.swift - HTTP request modification protocols
  • Sources/Networking/ImageModifier.swift - Response image modification protocols
  • Sources/Networking/RedirectHandler.swift - HTTP redirect handling strategies
  • Sources/Networking/RetryStrategy.swift - Failed request retry logic and policies
  • Sources/Networking/AuthenticationChallengeResponsable.swift - Authentication challenge handling
  • Sources/Networking/ImageDataProcessor.swift - Raw image data processing pipeline

Caching System

  • Sources/Cache/ImageCache.swift - Dual-layer (memory + disk) caching coordinator
  • Sources/Cache/MemoryStorage.swift - In-memory LRU cache with automatic cleanup
  • Sources/Cache/DiskStorage.swift - Persistent disk storage with expiration policies
  • Sources/Cache/Storage.swift - Abstract storage protocols and configurations
  • Sources/Cache/CacheSerializer.swift - Image serialization for disk persistence
  • Sources/Cache/FormatIndicatedCacheSerializer.swift - Format-aware image serialization

Image Processing and Transformation

  • Sources/Image/Image.swift - Cross-platform image type definitions and utilities
  • Sources/Image/ImageProcessor.swift - Image transformation and processing pipeline
  • Sources/Image/Filter.swift - Built-in image filters (blur, tint, overlay, etc.)
  • Sources/Image/ImageFormat.swift - Image format detection and handling
  • Sources/Image/ImageDrawing.swift - Core graphics drawing utilities and extensions
  • Sources/Image/ImageTransition.swift - View transition animations for image loading
  • Sources/Image/ImageProgressive.swift - Progressive JPEG loading implementation
  • Sources/Image/GIFAnimatedImage.swift - GIF animation support and playback
  • Sources/Image/GraphicsContext.swift - Graphics context management and utilities
  • Sources/Image/Placeholder.swift - Placeholder image definitions and protocols

Platform Implementation Files

UIKit Extensions

  • Sources/Extensions/ImageView+Kingfisher.swift - UIImageView integration with .kf namespace
  • Sources/Extensions/UIButton+Kingfisher.swift - UIButton background/image loading support
  • Sources/Extensions/NSTextAttachment+Kingfisher.swift - Text attachment image loading

AppKit Extensions

  • Sources/Extensions/NSButton+Kingfisher.swift - macOS NSButton image loading integration
  • Sources/Extensions/HasImageComponent+Kingfisher.swift - Generic image component protocol

Cross-Platform Extensions

  • Sources/Extensions/PHLivePhotoView+Kingfisher.swift - Live Photo view integration
  • Sources/Extensions/CPListItem+Kingfisher.swift - CarPlay list item support

SwiftUI Components

  • Sources/SwiftUI/KFImage.swift - Main SwiftUI image view component
  • Sources/SwiftUI/KFAnimatedImage.swift - Animated image support for SwiftUI
  • Sources/SwiftUI/KFImageOptions.swift - SwiftUI-specific configuration options
  • Sources/SwiftUI/KFImageProtocol.swift - Shared protocol for KF SwiftUI components
  • Sources/SwiftUI/KFImageRenderer.swift - SwiftUI view rendering and update logic
  • Sources/SwiftUI/ImageBinder.swift - Binding layer between SwiftUI and Kingfisher core
  • Sources/SwiftUI/ImageContext.swift - SwiftUI image loading context management

Custom Views

  • Sources/Views/AnimatedImageView.swift - Custom animated image view for GIF playback
  • Sources/Views/Indicator.swift - Loading indicator views and protocols

Build System Files

Package Management

  • Package.swift - Swift Package Manager manifest with dependencies and targets
  • [email protected] - Swift 5.9 compatibility package manifest
  • Kingfisher.podspec - CocoaPods specification for distribution
  • Gemfile - Ruby dependencies for Fastlane and build tools
  • Gemfile.lock - Locked Ruby gem versions for reproducible builds

Fastlane Build Automation

  • fastlane/Fastfile - Main Fastlane configuration with lanes for testing, building, and releasing
  • fastlane/actions/extract_current_change_log.rb - Extracts current version changelog
  • fastlane/actions/git_commit_all.rb - Git commit automation with custom messages
  • fastlane/actions/sync_build_number_to_git.rb - Synchronizes build numbers with git commits
  • fastlane/actions/update_change_log.rb - Automated changelog management
  • fastlane/README.md - Fastlane documentation and usage instructions

Xcode Project Files

  • Kingfisher.xcodeproj/ - Main Xcode project with targets and build settings
  • Kingfisher.xcworkspace/ - Xcode workspace for integrated development
  • Demo/Kingfisher-Demo.xcodeproj/ - Demo application Xcode project

Configuration Files

Framework Configuration

  • Sources/Info.plist - Framework bundle information and version metadata
  • Sources/PrivacyInfo.xcprivacy - Privacy manifest for App Store compliance

Development Assets

  • images/ - Sample images for testing and demo applications
    • kingfisher-1.jpg through kingfisher-10.jpg - Test image assets
    • logo.png - Project logo and branding
  • Tests/KingfisherTests/dancing-banana.gif - GIF test asset for animation testing
  • Tests/KingfisherTests/single-frame.gif - Single-frame GIF for edge case testing

Demo Applications

  • Demo/Demo/Kingfisher-Demo/ - iOS demo app showcasing all features
    • ViewControllers/ - Various demo screens (GIF, transitions, processors, etc.)
    • SwiftUIViews/ - SwiftUI demonstration views and regression tests
  • Demo/Demo/Kingfisher-macOS-Demo/ - macOS demo application
  • Demo/Demo/Kingfisher-tvOS-Demo/ - Apple TV demo application
  • Demo/Demo/Kingfisher-watchOS-Demo/ - watchOS demo application

Git and CI Configuration

  • pre-change.yml - Pre-commit hook configuration for code quality
  • .gitignore - Git ignore patterns for build artifacts and dependencies

Utility and Helper Files

Core Utilities

  • Sources/Utility/ExtensionHelpers.swift - Cross-platform compatibility helpers
  • Sources/Utility/CallbackQueue.swift - Thread-safe callback queue management
  • Sources/Utility/Box.swift - Reference wrapper for value types
  • Sources/Utility/Result.swift - Result type utilities and extensions
  • Sources/Utility/Delegate.swift - Weak delegate wrapper to prevent retain cycles
  • Sources/Utility/Runtime.swift - Runtime reflection and dynamic dispatch utilities
  • Sources/Utility/DisplayLink.swift - Cross-platform display link abstraction
  • Sources/Utility/SizeExtensions.swift - CGSize manipulation and calculations
  • Sources/Utility/String+SHA256.swift - String hashing for cache key generation

Test Infrastructure

  • Tests/KingfisherTests/KingfisherTestHelper.swift - Shared testing utilities and mocks
  • Tests/KingfisherTests/Utils/StubHelpers.swift - HTTP stubbing helpers for network tests
  • Tests/Dependency/Nocilla/ - HTTP mocking framework for isolated testing

Documentation System

  • Sources/Documentation.docc/ - DocC documentation bundle
    • Documentation.md - Main documentation entry point
    • GettingStarted.md - Quick start guide for new users
    • Tutorials/ - Step-by-step tutorials for UIKit and SwiftUI
    • CommonTasks/ - Task-oriented documentation for common use cases
    • Topics/ - Advanced topic guides (prefetching, indicators, etc.)
    • Resources/ - Documentation assets, images, and code samples

Reference

File Organization Patterns

  • Modular Structure: Core functionality separated into logical modules (General, Networking, Cache, Image, etc.)
  • Platform Abstraction: Cross-platform code in core modules, platform-specific extensions separate
  • Protocol-Oriented: Heavy use of protocols for customization and testing
  • Namespace Pattern: All public APIs accessed through .kf property extension

Naming Conventions

  • Files: PascalCase with descriptive names indicating functionality
  • Protocols: Suffix with -able for capabilities (e.g., AuthenticationChallengeResponsable)
  • Extensions: Platform prefix for clarity (e.g., UIButton+Kingfisher.swift)
  • Tests: Mirror source structure with Tests suffix

Dependency Relationships

  • Core Dependencies: Foundation, UIKit/AppKit conditionally imported
  • SwiftUI Module: Depends on core modules but isolated for optional usage
  • Extensions: Depend on core but platform-specific
  • Test Dependencies: Isolated with Nocilla for HTTP mocking
  • Build Dependencies: Fastlane for automation, Ruby gems for tooling

Key Integration Points

  • KingfisherCompatible Protocol: Entry point for all functionality via .kf namespace
  • Options System: Centralized configuration through KingfisherOptionsInfo
  • Result Types: Consistent error handling with Result<Success, KingfisherError>
  • Callback Queues: Thread-safe completion handling across all async operations

File: docs/issue-2495-analysis.md

Issue #2495 Analysis: Disk Cache Deserialization Cannot Be Cancelled

1. Issue Overview

Issue: #2495 - Images loading from cache are not canceled
Reporter: FaizanDurrani
Status: Open

Symptom

When scrolling quickly through a large collection of images, memory usage explodes (>1 GB). The reporter uses KingfisherWebP and observes that ImageCache.retrieveImageInDiskCache is called for every displayed item, even after imageView.kf.cancelDownloadTask() is invoked.

Instruments Evidence

The attached Instruments Allocations screenshot shows:

  • Memory peak ~630 MB during fast scrolling
  • Heaviest allocation stack:
    • ImageCache.retrieveImageInDiskCache(forKey:options:callbackQueue:completionHandler:) — 218.55 MB
    • KingfisherWrapper.image(webpData:options:) — 181.09 MB
    • WebP decode + CGImageCreate calls downstream

The majority of memory is consumed by WebP deserialization, not by disk I/O itself.


2. Root Cause Analysis

The problem has two layers:

Layer 1: Cache Retrieval Is Not Cancellable

cancelDownloadTask() calls DownloadTask.cancel(), which only cancels the network download (SessionDataTask.cancel(token:) in ImageDownloader.swift:121-124).

For images that hit the disk cache, KingfisherManager.retrieveImage returns nil at line 444 — there is no DownloadTask to cancel. Once retrieveImageInDiskCache dispatches its block to ioQueue, the entire chain (disk read → deserialization → memory promotion → callback) runs to completion with no interruption point.

Layer 2: Eager Disk-to-Memory Promotion

Kingfisher's cache architecture is designed as "eager read into memory":

  1. ImageCache.retrieveImage (line 608-655) checks memory first, then falls back to disk.
  2. On disk hit, the image is fully deserialized via CacheSerializer.image(with:data:options:) on ioQueue (line 738).
  3. The deserialized image is unconditionally stored into memory cache via store(image, toDisk: false) (line 641-645).
  4. There is no lazy decode path, no thumbnail downsampling, and no option to skip memory promotion.

During fast scrolling, each reused cell triggers a new setImage call. The previous cell's disk cache retrieval is already enqueued on the serial ioQueue. With N cells scrolled through rapidly, N deserialization blocks queue up. WebP decoding is particularly expensive — each decode allocates significant memory. The combined allocations exceed NSCache's eviction speed, causing the memory spike.

Why Existing Mechanisms Don't Help

Mechanism Why It Doesn't Help
cancelDownloadTask() Returns nil for cache hits — nothing to cancel
taskIdentifier check in view completion (line 331) Only runs after deserialization completes — the damage is done
fromMemoryCacheOrRefresh option Skips disk cache entirely — loses all disk caching, not a valid workaround
NSCache eviction (totalCostLimit = 25% RAM) LRU eviction is reactive, can't keep up with burst allocations

3. Proposed Solution

Approach: Source Task Identifier Check Before Deserialization

Leverage the existing Source.Identifier / taskIdentifier mechanism — which already works at the view layer — and extend it into the cache retrieval path. The check is inserted before the expensive deserialization step, skipping decode for tasks that are no longer relevant.

Key Properties

  • No public API changes — all modifications are internal
  • Backward compatible — direct KingfisherManager callers are unaffected (checker is nil)
  • Completion handler contract preserved — every setImage call produces exactly one completion callback
  • Worst case identical to current behavior — if the check doesn't fire in time, the decode proceeds as it does today

Changes Required

3.1 `KingfisherParsedOptionsInfo` — New Internal Property

Add a closure property that flows through the options chain:

swift
var sourceTaskIdentifierChecker: (@Sendable () -> Bool)?

Returns true if the task is still current, false if it has been superseded. This property is internal — not exposed in the public KingfisherOptionsInfoItem enum.

3.2 `KingfisherManager.retrieveImage` — Store Checker in Options

Currently referenceTaskIdentifierChecker (line 306) is only applied to onDataReceived side effects (line 329-332). Add one line to also store it in options:

swift
if let checker = referenceTaskIdentifierChecker {
    options.onDataReceived?.forEach {
        $0.onShouldApply = checker
    }
    options.sourceTaskIdentifierChecker = checker  // NEW
}

This ensures the checker propagates to ImageCache via the options struct.

3.3 `ImageCache.retrieveImageInDiskCache` — Two Check Points

Insert checks before disk read and before deserialization:

text
/* Detailed source-code truncated for AI context efficiency. */

Returning .success(nil) uses the same semantics as "image not found on disk", which is safely handled by all upstream callers.

3.4 `KingfisherManager.retrieveImageFromCache` — Prevent Spurious Download (Path 2 Only)

retrieveImageFromCache has two code paths:

  • Path 1 (line 639-697): Processed image lookup. On .none result, calls completionHandler(.failure(.imageNotExisting)). Since retrieveImageFromCache already returned true synchronously, no download is triggered. No change needed.

  • Path 2 (line 716-793): Original image fallback with custom processor. On .none result, calls self.loadAndCacheImage(...) (line 732) — this does trigger a download. Add a check here:

swift
guard let image = cacheResult.image else {
    // NEW: Task is stale — report error instead of downloading.
    if let checker = options.sourceTaskIdentifierChecker, !checker() {
        let error = KingfisherError.cacheError(reason: .imageNotExisting(key: key))
        options.callbackQueue.execute { completionHandler?(.failure(error)) }
        return
    }

    if options.onlyFromCache {
        let error = KingfisherError.cacheError(reason: .imageNotExisting(key: key))
        options.callbackQueue.execute { completionHandler?(.failure(error)) }
    } else {
        let task = self.loadAndCacheImage(
            source: source,
            context: context,
            completionHandler: completionHandler
        )
        downloadTaskUpdated?(task?.value)
    }
    return
}

Summary of File Changes

File Change Lines Affected
Sources/General/KingfisherOptionsInfoItems.swift Add sourceTaskIdentifierChecker property ~1 line
Sources/General/KingfisherManager.swift Store checker in options ~1 line
Sources/Cache/ImageCache.swift Two cancellation checks in retrieveImageInDiskCache ~10 lines
Sources/General/KingfisherManager.swift Prevent spurious download in path 2 ~5 lines

Total: ~17 lines of effective code.


4. Timing and Behavior Simulation

Scenario A: Fast Scrolling (Primary Problem Case)

A cell is reused 5 times within one layout pass. All setImage calls complete on the main thread before the serial ioQueue processes any block.

text
Main thread (single runloop pass):
  cellForItem → cell X: setImage(url_1), X.taskId = 101, dispatch block_1 to ioQueue
  cellForItem → cell X: setImage(url_5), X.taskId = 102, dispatch block_2 to ioQueue
  cellForItem → cell X: setImage(url_9), X.taskId = 103, dispatch block_3 to ioQueue
  cellForItem → cell X: setImage(url_13), X.taskId = 104, dispatch block_4 to ioQueue
  cellForItem → cell X: setImage(url_17), X.taskId = 105, dispatch block_5 to ioQueue
                                                                         ↕ main thread yields

ioQueue (serial, starts processing):
  block_1: CHECK 1 → X.taskId==101? → 105≠101 → SKIP (no disk read, no decode)
  block_2: CHECK 1 → X.taskId==102? → 105≠102 → SKIP
  block_3: CHECK 1 → X.taskId==103? → 105≠103 → SKIP
  block_4: CHECK 1 → X.taskId==104? → 105≠104 → SKIP
  block_5: CHECK 1 → X.taskId==105? → 105==105 → read disk → CHECK 2 → still valid → DECODE ✓

Result: 4 out of 5 decodes skipped. Only the actually-needed image is decoded.
Current behavior: All 5 images decoded, all promoted to memory, 4 results discarded at view layer.
Check hit rate: (N-1)/N → approaches 100% with faster scrolling.

Scenario B: Moderate Scrolling

User scrolls at a pace where each cell stays visible for ~200ms. Decode takes ~100ms per image.

text
Main thread:
  t=0ms    setImage(url_1), X.taskId = 101, dispatch block_1

ioQueue:
  t=5ms    block_1: CHECK 1 → 101==101 ✓ → read disk (10ms)
  t=15ms   CHECK 2 → 101==101 ✓ → decode (100ms)
  t=115ms  decode complete → store to memory → callback

Main thread:
  t=200ms  cell reuse, setImage(url_5), X.taskId = 102, dispatch block_2

Result: Decode completes before cell reuse. Check does not fire.
Current behavior: Identical — image was needed and displayed.
Impact: None — and none needed, since one-at-a-time decode doesn't cause memory pressure.

Scenario C: Moderate Scrolling with Large File

Same pace as B, but a very large WebP file takes 300ms to read from disk.

text
Main thread:
  t=0ms    setImage(url_1), X.taskId = 101, dispatch block_1

ioQueue:
  t=5ms    block_1: CHECK 1 → 101==101 ✓ → start disk read (300ms)

Main thread:
  t=200ms  cell reuse, setImage(url_5), X.taskId = 102

ioQueue:
  t=305ms  disk read complete → CHECK 2 → 101==102? → 101≠102 → SKIP decode ✓

Result: CHECK 2 catches the stale task after a slow disk read. Decode skipped.
Current behavior: Full decode runs (expensive), result discarded at view layer.
This is where the two-check design pays off — CHECK 1 was valid at dispatch time, but CHECK 2 catches the change.

Scenario D: Multiple Visible Cells (No Reuse)

10 cells appear simultaneously on initial load. Each cell has a different image, no cell reuse occurs.

text
Main thread:
  cell A: setImage(url_1), A.taskId = 101
  cell B: setImage(url_2), B.taskId = 102
  ...
  cell J: setImage(url_10), J.taskId = 110

ioQueue:
  block for cell A: CHECK → A.taskId==101? → 101==101 ✓ → decode
  block for cell B: CHECK → B.taskId==102? → 102==102 ✓ → decode
  ...
  block for cell J: CHECK → J.taskId==110? → 110==110 ✓ → decode

Result: All 10 images decoded. This is correct — all 10 are needed.
Current behavior: Identical.
Impact: None. The check is per-cell (each cell has its own taskIdentifier), so concurrent requests for different cells don't interfere with each other.

Scenario E: Fast Scrolling with Custom Processor (Path 2)

User has a custom ImageProcessor. Original image is cached on disk, processed version is not. Cell reuses rapidly.

text
Main thread:
  setImage(url_1, processor: blur), X.taskId = 101
  setImage(url_5, processor: blur), X.taskId = 102

retrieveImageFromCache:
  Path 1: processed image not cached → validCache = false
  Path 2: original image cached → canUseOriginalImageCache = true

  originalCache.retrieveImage → ioQueue:
    block_1: CHECK → 101==102? → SKIP decode → .success(nil)

  Path 2 callback:
    cacheResult.image == nil
    → CHECK: 101==102? → stale
    → completionHandler(.failure(.imageNotExisting))   ← no download triggered ✓

  View layer:
    issuedIdentifier(101) ≠ taskIdentifier(102) → .notCurrentSourceTask
    completionHandler(.failure(.notCurrentSourceTask)) ← user gets callback ✓

Without the path 2 fix: loadAndCacheImage would be called, triggering a network download that is ultimately discarded.


5. Completion Handler Contract Verification

Contract: Every setImage call produces exactly one completion callback.

For the "skipped" task (stale identifier):

text
retrieveImageInDiskCache → .success(nil)          // skipped decode
    ↓
ImageCache.retrieveImage → .success(.none)         // treated as "not found"
    ↓
retrieveImageFromCache:
  Path 1 → completionHandler(.failure(.imageNotExisting))    ✅ called
  Path 2 → checker prevents download,
           completionHandler(.failure(.imageNotExisting))    ✅ called
    ↓
handler (line 386) → failCurrentSource → completionHandler(.failure(...))
    ↓
View extension (line 331):
  issuedIdentifier ≠ taskIdentifier →
  completionHandler(.failure(.notCurrentSourceTask))         ✅ user receives callback

For the "current" task (valid identifier):

Behavior is identical to today — full decode, memory promotion, success callback. No changes in this path.


6. Risk Assessment

6.1 Thread Safety of `taskIdentifier` Access

Status: Pre-existing issue, not introduced by this change.

The checker closure { issuedIdentifier == self.taskIdentifier } reads taskIdentifier (associated object on UIImageView) from ioQueue, while writes happen on @MainActor. The associated object uses OBJC_ASSOCIATION_RETAIN_NONATOMIC.

Mitigation factors:

  • objc_getAssociatedObject / objc_setAssociatedObject use an internal lock in the ObjC runtime (AssociationsManager mutex). Memory access is serialized at the runtime level.
  • The value is wrapped in Box. After reading the Box reference, we immediately extract the UInt value (copy semantics). No retained reference escapes.
  • The same pattern already exists in onShouldApply for ImageProgressiveProvider (called from background decoder thread).

Worst case of torn read:

  • Read old value (checker thinks task is still valid) → proceed with decode → same as current behavior
  • Read new value (checker thinks task is stale) → skip a decode that was actually still needed → cell shows placeholder until the truly-current task completes

Recommendation: Consider changing OBJC_ASSOCIATION_RETAIN_NONATOMIC to OBJC_ASSOCIATION_RETAIN in a separate PR, independent of this optimization. This would make the contract explicit without affecting performance (associated object access is already locked).

6.2 Spurious `.imageNotExisting` Error Propagation

When a skipped task returns .success(nil), it becomes .imageNotExisting upstream. This error flows through handler → failCurrentSource (line 352-383).

failCurrentSource checks:

  1. error.isTaskCancelled (line 354) — no, this is a cache error, not a cancellation error. Won't short-circuit.
  2. error.isLowDataModeConstrained (line 359) — no. Won't trigger low-data-mode fallback.
  3. retrievingContext.popAlternativeSource() (line 369) — if alternative sources are configured, the next source will be tried.

Impact: If a user configures alternativeSources, a skipped task may trigger retrieval from the next alternative source. This retrieval result will be discarded at the view layer (stale taskIdentifier), but it wastes a network request or cache lookup.

Severity: Low. alternativeSources is a rarely-used feature. The wasted work is bounded (one alternative attempt that gets discarded). The exact same waste occurs today when a network download completes for a stale task.

Possible future improvement: Introduce a dedicated CacheErrorReason.taskCancelled case so that failCurrentSource short-circuits at line 354. This would require adding a case to the public KingfisherError.CacheErrorReason enum — a source-breaking change if users have exhaustive switches. Could be considered for the next major version.

6.3 Already-Running Decode Cannot Be Interrupted

Once cacheSerializer.image(with:data:options:) begins execution, it runs to completion. There is no mid-decode cancellation.

Mitigating factor: The ioQueue is serial. At most one decode runs at any time. The memory consumed by a single in-flight decode is bounded and manageable. The issue in #2495 is caused by dozens of decodes queuing up and running sequentially while their decoded images accumulate in memory — our check prevents the queue from growing unboundedly.

6.4 `loadDiskFileSynchronously` Mode

When loadDiskFileSynchronously is true, the loading queue is .untouch (caller's thread, typically main). In this case:

  • CHECK 1 runs on the main thread synchronously — taskIdentifier was just set, so the check always passes.
  • The disk read and decode run synchronously on the main thread, blocking the runloop. No opportunity for cell reuse.

Impact: No behavior change for synchronous mode. This is expected — synchronous mode is inherently non-cancellable.

6.5 Direct `KingfisherManager` / `ImageCache` API Users

Users who call KingfisherManager.shared.retrieveImage(with:options:completionHandler:) or ImageCache.default.retrieveImage(forKey:...) directly:

  • sourceTaskIdentifierChecker is never set (it's only populated from the view extension path).
  • All checks evaluate to if let checker = nil → skip → original behavior.

Impact: None. Full backward compatibility.


7. Summary

What This Fix Achieves

  • Skips expensive deserialization for disk-cached images when the requesting view has already moved on to a different image
  • Prevents spurious network downloads when a cancelled cache lookup for a custom-processed image returns "not found"
  • Preserves the completion handler contract — every setImage always results in exactly one completion callback

What This Fix Does NOT Achieve

  • Cannot interrupt a decode that is already in progress (inherent limitation of CacheSerializer protocol)
  • Does not add a first-class cancellation API for cache lookups (would require public API changes)
  • Does not address the taskIdentifier data race (pre-existing, should be fixed independently)
  • Does not change the eager disk-to-memory promotion design (a larger architectural topic)

Estimated Effectiveness

Scrolling Speed Decodes Prevented Memory Pressure Reduction
Fast (>10 cells/sec) ~90-100% of stale decodes Significant
Moderate (~5 cells/sec) ~50-70% of stale decodes Moderate
Slow (<2 cells/sec) ~0% (but no pressure) N/A

8. Implementation Plan

Step 1: Add `sourceTaskIdentifierChecker` to `KingfisherParsedOptionsInfo`

File: Sources/General/KingfisherOptionsInfo.swift
Location: Line 420, after onDataReceived (the other internal property)

Add:

swift
var onDataReceived: [any DataReceivingSideEffect]? = nil
var sourceTaskIdentifierChecker: (@Sendable () -> Bool)? = nil  // NEW

This is an internal property (no public modifier), matching the pattern of onDataReceived. It does not need handling in the init(_ info:) switch since it is never set from KingfisherOptionsInfoItem.

Verification: Build succeeds. No public API surface change.

Step 2: Store `referenceTaskIdentifierChecker` into options

File: Sources/General/KingfisherManager.swift
Location: Line 329-333, inside retrieveImage(with:options:...referenceTaskIdentifierChecker:...)

Change from:

swift
if let checker = referenceTaskIdentifierChecker {
    options.onDataReceived?.forEach {
        $0.onShouldApply = checker
    }
}

To:

swift
if let checker = referenceTaskIdentifierChecker {
    options.onDataReceived?.forEach {
        $0.onShouldApply = checker
    }
    options.sourceTaskIdentifierChecker = checker
}

This one-line addition causes the checker to propagate through RetrievingContext.options into every downstream call that receives KingfisherParsedOptionsInfo, including ImageCache.retrieveImage and retrieveImageInDiskCache.

Verification: Build succeeds. Existing tests pass — no behavioral change yet.

Step 3: Insert cancellation checks in `retrieveImageInDiskCache`

File: Sources/Cache/ImageCache.swift
Location: Line 730-749, the loadingQueue.execute block in the internal retrieveImageInDiskCache

Change from:

swift
loadingQueue.execute {
    do {
        var image: KFCrossPlatformImage? = nil
        if let data = try self.diskStorage.value(
            forKey: computedKey,
            forcedExtension: options.forcedExtension,
            extendingExpiration: options.diskCacheAccessExtendingExpiration
        ) {
            image = options.cacheSerializer.image(with: data, options: options)
        }
        if options.backgroundDecode {
            image = image?.kf.decoded(scale: options.scaleFactor)
        }
        callbackQueue.execute { [image] in completionHandler(.success(image)) }
    } catch let error as KingfisherError {
        callbackQueue.execute { completionHandler(.failure(error)) }
    } catch {
        assertionFailure("The internal thrown error should be a `KingfisherError`.")
    }
}

To:

swift
loadingQueue.execute {
    // CHECK 1: For blocks queued behind others on the serial ioQueue,
    // the task is likely already stale by the time execution begins.
    if let checker = options.sourceTaskIdentifierChecker, !checker() {
        callbackQueue.execute { completionHandler(.success(nil)) }
        return
    }

    do {
        var image: KFCrossPlatformImage? = nil
        if let data = try self.diskStorage.value(
            forKey: computedKey,
            forcedExtension: options.forcedExtension,
            extendingExpiration: options.diskCacheAccessExtendingExpiration
        ) {
            // CHECK 2: Disk read completed, but deserialization has not started.
            // Catches staleness that occurred during a slow disk read.
            if let checker = options.sourceTaskIdentifierChecker, !checker() {
                callbackQueue.execute { completionHandler(.success(nil)) }
                return
            }
            image = options.cacheSerializer.image(with: data, options: options)
        }
        if options.backgroundDecode {
            image = image?.kf.decoded(scale: options.scaleFactor)
        }
        callbackQueue.execute { [image] in completionHandler(.success(image)) }
    } catch let error as KingfisherError {
        callbackQueue.execute { completionHandler(.failure(error)) }
    } catch {
        assertionFailure("The internal thrown error should be a `KingfisherError`.")
    }
}

Why two checks:

  • CHECK 1 fires for queued blocks whose task became stale while waiting. Skips both disk I/O and decode. This is the high-hit-rate path during fast scrolling.
  • CHECK 2 fires when disk read was slow (large file) and the task became stale during the read. Skips only decode.

Verification: Build succeeds. Test with a fast-scrolling collection view and Instruments to confirm decode skipping.

Step 4: Prevent spurious download in `retrieveImageFromCache` path 2

File: Sources/General/KingfisherManager.swift
Location: Line 724-738, inside the originalCache.retrieveImage callback in retrieveImageFromCache

Change from:

swift
result.match(
    onSuccess: { cacheResult in
        guard let image = cacheResult.image else {
            if options.onlyFromCache {
                let error = KingfisherError.cacheError(reason: .imageNotExisting(key: key))
                options.callbackQueue.execute { completionHandler?(.failure(error)) }
            } else {
                let task = self.loadAndCacheImage(
                    source: source,
                    context: context,
                    completionHandler: completionHandler
                )
                downloadTaskUpdated?(task?.value)
            }
            return
        }

To:

swift
result.match(
    onSuccess: { cacheResult in
        guard let image = cacheResult.image else {
            // If the task is stale (cancelled by a newer setImage call),
            // report cache miss instead of triggering a network download.
            if let checker = options.sourceTaskIdentifierChecker, !checker() {
                let error = KingfisherError.cacheError(reason: .imageNotExisting(key: key))
                options.callbackQueue.execute { completionHandler?(.failure(error)) }
                return
            }

            if options.onlyFromCache {
                let error = KingfisherError.cacheError(reason: .imageNotExisting(key: key))
                options.callbackQueue.execute { completionHandler?(.failure(error)) }
            } else {
                let task = self.loadAndCacheImage(
                    source: source,
                    context: context,
                    completionHandler: completionHandler
                )
                downloadTaskUpdated?(task?.value)
            }
            return
        }

Why this is needed: Path 2 handles original-image fallback with custom processors. When the original image's decode is skipped (returning .none), the existing code falls through to loadAndCacheImage, triggering a network download. The check prevents this wasted request while still calling completionHandler to preserve the contract.

Verification: Build succeeds. Existing tests pass. For targeted testing: configure a custom ImageProcessor with disk-cached original images, fast-scroll, and verify no spurious network requests in Charles/Instruments.

Step 5: Testing

Unit Tests

Add test cases in Tests/KingfisherTests/:

  1. Cache retrieval skip on stale identifier: Set up a disk-cached image. Simulate a stale sourceTaskIdentifierChecker (returns false). Verify retrieveImageInDiskCache returns .success(nil) without invoking CacheSerializer.image(with:).

  2. Completion handler contract: Call setImage, then immediately call setImage again on the same view. Verify the first call's completion receives .notCurrentSourceTask error.

  3. Valid identifier proceeds normally: Set up a disk-cached image. Provide a checker that returns true. Verify the image is decoded and returned normally.

  4. Path 2 download prevention: Configure a custom processor, cache only the original image on disk. Provide a stale checker. Verify loadAndCacheImage is NOT called and completion receives .imageNotExisting.

Manual / Integration Testing

  • Instruments: Profile a fast-scrolling collection view with WebP images. Compare memory peak before and after the change.
  • Functional: Verify images display correctly at all scrolling speeds. No blank or stuck cells.
  • Edge cases: Test with loadDiskFileSynchronously = true, fromMemoryCacheOrRefresh = true, and alternativeSources configured.

Execution Order

text
Step 1 (add property)
  └─ Step 2 (wire checker into options)
       └─ Step 3 (cache-level checks)   ── can be done in parallel ──  Step 4 (path 2 guard)
                                                                           │
                                                                       Step 5 (tests)

Steps 1→2 are prerequisites. Steps 3 and 4 are independent of each other. Step 5 should cover all changes.

Review From Codex

Overall Assessment

I agree with the root cause analysis in this document:

  • The main issue is not cancelDownloadTask() itself, but that the disk-cache retrieval path has no cancellation point once work is enqueued.
  • The current disk-hit behavior eagerly deserializes the image and then promotes it into memory cache, even when the requesting view has already moved on to another source.
  • The existing taskIdentifier check at the view layer happens too late. It prevents stale results from being applied to the view, but it does not prevent the expensive decode work from already happening.

These points are consistent with the current implementation in:

  • Sources/Extensions/ImageView+Kingfisher.swift
  • Sources/General/KingfisherManager.swift
  • Sources/Cache/ImageCache.swift
  • Sources/Networking/ImageDownloader.swift

Main Concern With the Proposed Fix

I do not recommend shipping the proposed solution exactly as written.

The main problem is that it represents a stale request as .success(nil) inside retrieveImageInDiskCache, which then becomes a normal cache miss (.none / .imageNotExisting) upstream.

That encoding leaks the wrong semantics into higher layers:

  • ImageCache.retrieveImage(forKey:options:...) treats nil as a normal disk miss.
  • KingfisherManager.retrieveImageFromCache then treats that result as an ordinary cache failure path.
  • At the manager level, ordinary failures can still flow into retry logic or alternativeSources.

So the side effect is broader than only "path 2 may trigger a spurious download". Both retry and alternative-source logic can be activated by a stale request that should have been short-circuited as "no longer relevant", not "cache miss".

This is the strongest reason I would not merge the current proposal unchanged.

Thread-Safety Concern

The document's thread-safety discussion is somewhat optimistic.

Today, the checker closure reads taskIdentifier from a background queue, while writes happen on @MainActor. The storage behind taskIdentifier is implemented with associated objects and OBJC_ASSOCIATION_RETAIN_NONATOMIC.

Changing that association to atomic might reduce some risk, but it does not really solve the larger issue: a background queue is still observing UI-associated state across actor boundaries.

If this optimization is added, I suggest tightening this part of the design as well, instead of relying on the current associated-object pattern more heavily than it already is.

Additional Observation

The proposed check points are directionally correct, but there is one more cheap place worth checking:

  • after disk read
  • before serializer decode
  • before backgroundDecode

The dominant cost for WebP is likely already inside the serializer path, so this does not change the main conclusion. Still, adding a final stale check before backgroundDecode is nearly free and keeps the behavior more consistent.

I recommend keeping the overall approach, but changing the internal representation of the outcome.

Instead of encoding "stale request" as .success(nil) or .imageNotExisting, use an explicit internal stale state, for example an internal retrieval result such as:

swift
enum DiskRetrievalResult {
    case image(KFCrossPlatformImage)
    case none
    case stale
}

Then:

  • ImageCache can stop work early and report .stale internally.
  • KingfisherManager can short-circuit .stale without entering retry logic.
  • retrieveImageFromCache path 2 can avoid download fallback for stale work.
  • completion behavior at the view layer remains unchanged, because the view will still eventually surface .notCurrentSourceTask for the outdated request.

This preserves the optimization benefit without polluting existing cache-miss semantics.

I would also prefer that the checker read from a dedicated thread-safe token holder instead of reading the view's associated-object-backed taskIdentifier directly from a background queue.

Using sourceTaskIdentifierChecker as an internal option is fine. The weak point is not the propagation mechanism; it is the current state source used by the checker.

Memory-Model Note

This issue is primarily about avoiding stale decode work, and that is worth fixing first.

However, the current memory spike is amplified by another design choice as well: on disk hit, the decoded image is unconditionally promoted back into memory cache. If memory pressure reports continue even after stale decode skipping is added, the next place to revisit is whether disk-hit promotion should always be eager.

That is a larger architectural question and should probably remain separate from the narrow fix for #2495.

Documentation Note

One minor accuracy issue in the implementation plan: the file name should be Sources/General/KingfisherOptionsInfo.swift, not KingfisherOptionsInfoItems.swift.

Merge Recommendation

My recommendation is:

  1. Keep the idea of checking staleness before expensive disk-cache deserialization.
  2. Do not encode stale work as a normal cache miss.
  3. Introduce an internal explicit stale outcome and short-circuit it in KingfisherManager.
  4. Tighten the checker state source so it does not depend on reading UI-associated state from a background queue.
  5. Add regression tests for retry strategy and alternativeSources, not only for path 2 fallback.

With those adjustments, the fix looks worthwhile and technically sound. Without them, the current proposal is likely to reduce memory pressure in the target scenario, but it also introduces semantic leakage into error handling and recovery paths.

Response From Claude

Overall

The Codex review is thorough and technically accurate. All five recommendations are accepted and should be incorporated into the final implementation.

On the Main Concern (`.success(nil)` Semantic Leakage)

This is the most valuable point in the review, and it identifies a flaw that our original analysis underestimated.

Section 6.2 of this document assessed the alternativeSources interaction as "Low" severity. That assessment was incomplete. The retry strategy interaction is worse: a stale request encoded as .imageNotExisting enters handler (KingfisherManager.swift:394), which consults retryStrategy. If the user has configured a retry strategy (e.g., DelayRetryStrategy(maxRetryCount: 3)), the stale request loops through startNewRetrieveTask → imageCachedType (still cached) → retrieveImage → ioQueue → checker (still stale) → .imageNotExisting → retry again, up to maxRetryCount times. Each cycle is individually cheap (the checker skips decode), but the loop is semantically wrong and wastes work.

The DiskRetrievalResult enum approach cleanly eliminates this. By distinguishing .stale from .none at the ImageCache boundary, KingfisherManager can short-circuit before entering retry or alternative-source logic. The additional complexity is minimal (~10 lines for the enum + ~10 lines for handling), and it makes the control flow explicit and correct.

On Thread Safety

The review correctly notes that our thread-safety discussion is optimistic. The practical risk is indeed low — objc_getAssociatedObject uses an internal runtime lock, and the Box value is immediately copied — but "probably safe due to runtime implementation details" is not a strong engineering argument for a library consumed by thousands of apps.

More importantly, we are not introducing this pattern — the existing onShouldApply in ImageProgressiveProvider already reads taskIdentifier from a background thread. Our change would amplify the reliance on this pattern (every disk cache retrieval instead of only progressive loads), which makes the case for fixing it stronger.

A CancellationToken (~15 lines) replaces the cross-actor read with a lock-protected Bool. The checker becomes { !token.isCancelled } instead of { issuedIdentifier == self.taskIdentifier }. This is self-evidently correct and does not depend on ObjC runtime internals. It should be included in the initial PR rather than deferred, since the incremental cost is small and the design becomes clean from the start.

On `backgroundDecode` Check

Agreed. decoded(scale:) forces bitmap rendering into a new context. The cost is secondary to WebP deserialization, but the check is nearly free and keeps the pattern consistent. Include it.

On Regression Tests

The original test plan (Section 8, Step 5) covered the basic cases but missed two important scenarios:

  1. Retry strategy with stale request: Verify that a stale disk cache retrieval does not enter the retry loop. With DiskRetrievalResult.stale, the handler should short-circuit before consulting retryStrategy.

  2. Alternative sources with stale request: Verify that a stale result does not trigger popAlternativeSource(). The .stale result should bypass failCurrentSource entirely.

These should be added to the test plan.

On the Documentation Note

Confirmed. Section 3.1 and the Summary table in Section 3 reference KingfisherOptionsInfoItems.swift. The correct file name is KingfisherOptionsInfo.swift. This will be corrected when the implementation plan is finalized.

Summary of Accepted Changes

All five recommendations are accepted for the initial PR:

# Recommendation Rationale Additional Code
1 Keep staleness checking before deserialization Core optimization, already planned 0
2 Use DiskRetrievalResult instead of .success(nil) Prevents semantic leakage into retry and alternative-source logic ~10 lines
3 Short-circuit .stale in KingfisherManager Avoids retry loops and spurious downloads for stale requests ~10 lines
4 CancellationToken for thread-safe state Eliminates cross-actor associated-object reads from ioQueue ~15 lines
5 Regression tests for retry and alternativeSources Covers the interaction that the original test plan missed Test code

Total additional production code over the original proposal: ~35 lines. The overall change remains small and focused.


File: docs/project-overview.md

Kingfisher Project Overview

Project Purpose

Kingfisher is a powerful, pure-Swift library for downloading and caching images from the web. It provides an elegant, asynchronous API for managing remote images in iOS, macOS, tvOS, watchOS, and visionOS applications. The library handles the complete lifecycle of image loading - from network downloading to multi-layer caching (memory and disk), with built-in image processing capabilities and extensive platform-specific UI component integrations.

The framework follows a modular architecture with clear separation of concerns, allowing developers to use individual components (downloader, cache, processors) independently or as a unified solution. Through its namespace wrapper pattern (.kf property) and builder pattern (KF.url()), Kingfisher offers both UIKit and SwiftUI support with minimal code overhead.

Main Entry Points

Core Configuration

  • Package.swift - Swift Package Manager manifest defining library targets and platform requirements
  • Kingfisher.podspec - CocoaPods specification (version 8.3.2)
  • Sources/General/Kingfisher.swift - Core type definitions and protocol declarations
  • Sources/General/KingfisherManager.swift - Central coordinator managing download and cache operations

Primary APIs

  • Sources/General/KF.swift - Builder pattern entry point for fluent API
  • Sources/Extensions/ImageView+Kingfisher.swift - UIImageView/NSImageView extensions
  • Sources/SwiftUI/KFImage.swift - SwiftUI image component
  • Sources/SwiftUI/KFAnimatedImage.swift - SwiftUI animated image support

Technology Stack

Core Components

  • Image Downloading: Sources/Networking/ImageDownloader.swift - URLSession-based networking layer
  • Cache System:
    • Sources/Cache/ImageCache.swift - Dual-layer cache coordinator
    • Sources/Cache/MemoryStorage.swift - In-memory cache implementation
    • Sources/Cache/DiskStorage.swift - Persistent disk storage
  • Image Processing: Sources/Image/ImageProcessor.swift - Transformation pipeline with filters
  • Format Support: Sources/Image/ImageFormat.swift - Multi-format detection (JPEG, PNG, GIF, WebP)

Platform Integrations

  • UIKit Extensions: Sources/Extensions/UIButton+Kingfisher.swift, Sources/Extensions/NSTextAttachment+Kingfisher.swift
  • SwiftUI Components: Sources/SwiftUI/KFImageProtocol.swift, Sources/SwiftUI/ImageBinder.swift
  • Specialized Views: Sources/Views/AnimatedImageView.swift, Sources/Extensions/PHLivePhotoView+Kingfisher.swift

Build & Testing

  • Fastlane: fastlane/Fastfile - Primary build automation
  • Test Suite: Tests/KingfisherTests/ - XCTest-based unit tests with Nocilla HTTP stubbing
  • Documentation: Sources/Documentation.docc/ - DocC integrated documentation

Platform Support

Minimum Requirements

  • Swift: 5.9+ (Swift 6 strict concurrency ready)
  • UIKit/AppKit:
    • iOS 13.0+ (#if os(iOS))
    • macOS 10.15+ (#if os(macOS))
    • tvOS 13.0+ (#if os(tvOS))
    • watchOS 6.0+ (#if os(watchOS))
    • visionOS 1.0+ (#if os(visionOS))
  • SwiftUI: iOS 14.0+ / macOS 11.0+ / tvOS 14.0+ / watchOS 7.0+ / visionOS 1.0+

Platform-Specific Files

  • macOS: Sources/Extensions/NSButton+Kingfisher.swift - NSButton image loading
  • iOS/tvOS: Sources/Extensions/UIButton+Kingfisher.swift - UIButton extensions
  • watchOS: Sources/Extensions/WKInterfaceImage+Kingfisher.swift - WatchKit support
  • CarPlay: Sources/Extensions/CPListItem+Kingfisher.swift - CarPlay list items (iOS 14.0+)
  • tvOS: Sources/Extensions/TVMonogramView+Kingfisher.swift - Apple TV monogram views

File: docs/testing.md

Kingfisher Testing Documentation

Overview

Kingfisher's test suite is located in the Tests/KingfisherTests/ directory and provides comprehensive coverage for all major components of the library. The test suite uses XCTest framework with custom helper utilities and the Nocilla dependency for HTTP request stubbing.

Test Infrastructure

  • Test Framework: XCTest
  • Network Mocking: Nocilla (located at Tests/Dependency/Nocilla/)
  • Test Helper: Tests/KingfisherTests/KingfisherTestHelper.swift
  • Stub Utilities: Tests/KingfisherTests/Utils/StubHelpers.swift
  • Test Assets:
    • Tests/KingfisherTests/dancing-banana.gif - Animated GIF for testing
    • Tests/KingfisherTests/single-frame.gif - Single frame GIF for testing

Test Categories

1. Core Component Tests

Cache Layer Tests

  • Tests/KingfisherTests/ImageCacheTests.swift - Tests for the main ImageCache functionality
  • Tests/KingfisherTests/MemoryStorageTests.swift - Memory cache specific tests
  • Tests/KingfisherTests/DiskStorageTests.swift - Disk storage specific tests
  • Tests/KingfisherTests/StorageExpirationTests.swift - Cache expiration policy tests

Networking Tests

  • Tests/KingfisherTests/ImageDownloaderTests.swift - Image downloading and session management
  • Tests/KingfisherTests/ImagePrefetcherTests.swift - Batch image prefetching functionality
  • Tests/KingfisherTests/DataReceivingSideEffectTests.swift - Data processing side effects

Manager Tests

  • Tests/KingfisherTests/KingfisherManagerTests.swift - Central coordinator tests
  • Tests/KingfisherTests/KingfisherOptionsInfoTests.swift - Configuration options tests

2. Image Processing Tests

  • Tests/KingfisherTests/ImageProcessorTests.swift - Image transformation pipeline tests
  • Tests/KingfisherTests/ImageDrawingTests.swift - Image drawing and rendering tests
  • Tests/KingfisherTests/ImageExtensionTests.swift - Core image extensions tests
  • Tests/KingfisherTests/ImageModifierTests.swift - Request modifier tests

3. UI Integration Tests

UIKit Tests

  • Tests/KingfisherTests/ImageViewExtensionTests.swift - UIImageView extension tests
  • Tests/KingfisherTests/UIButtonExtensionTests.swift - UIButton extension tests

AppKit Tests

  • Tests/KingfisherTests/NSButtonExtensionTests.swift - NSButton extension tests (macOS)

4. Specialized Feature Tests

  • Tests/KingfisherTests/LivePhotoSourceTests.swift - Live Photo support tests
  • Tests/KingfisherTests/ImageDataProviderTests.swift - Custom data provider tests
  • Tests/KingfisherTests/RetryStrategyTests.swift - Network retry strategy tests
  • Tests/KingfisherTests/StringExtensionTests.swift - String utility extension tests

Running Tests

Using Fastlane

bash
# Install dependencies first
bundle install

# Run all tests across all platforms
bundle exec fastlane tests

# Expected output:
# [08:30:00]: ------------------------------
# [08:30:00]: --- Step: default_platform ---
# [08:30:00]: ------------------------------
# [08:30:00]: Driving the lane 'ios tests' 🚀
# [08:30:01]: ------------------
# [08:30:01]: --- Step: scan ---
# [08:30:01]: ------------------
# [08:30:01]: Running Tests: ▸ Touching Kingfisher.framework
# [08:30:45]: Test Succeeded

# Run tests for specific platform
bundle exec fastlane test destination:"platform=iOS Simulator,name=iPhone 15"
bundle exec fastlane test destination:"platform=macOS"
bundle exec fastlane test destination:"platform=tvOS Simulator,name=Apple TV"

# CI-specific test command (used in continuous integration)
DESTINATION="platform=iOS Simulator,name=iPhone 15,OS=17.5" bundle exec fastlane test_ci

# Build only (for watchOS where full testing isn't supported)
bundle exec fastlane build destination:"platform=watchOS Simulator,name=Apple Watch Series 9 (41mm)"

Using Xcode

bash
# Open workspace in Xcode
open Kingfisher.xcworkspace

# Then use Xcode's test navigator or press Cmd+U to run all tests
# Or use xcodebuild directly:
xcodebuild test -workspace Kingfisher.xcworkspace -scheme Kingfisher -destination "platform=iOS Simulator,name=iPhone 15"

Test File Organization Reference

Directory Structure

text
Tests/
├── Dependency/
│   └── Nocilla/              # HTTP stubbing framework
│       ├── LICENSE
│       ├── README.md
│       └── Nocilla/          # Nocilla source files
│           ├── Categories/   # NSData and NSString extensions
│           ├── DSL/          # Domain-specific language for stubbing
│           ├── Diff/         # Request diff utilities
│           ├── Hooks/        # HTTP client hooks (NSURLSession, etc.)
│           ├── Matchers/     # Request matching logic
│           ├── Model/        # HTTP request/response models
│           └── Stubs/        # Stub implementation
└── KingfisherTests/
    ├── Info.plist
    ├── KingfisherTestHelper.swift      # Main test helper utilities
    ├── KingfisherTests-Bridging-Header.h  # Objective-C bridging
    ├── Utils/
    │   └── StubHelpers.swift           # Network stubbing helpers
    ├── dancing-banana.gif              # Animated test image
    ├── single-frame.gif                # Static test image
    └── *Tests.swift                    # Individual test files

Build System Test Targets

Xcode Scheme: Kingfisher.xcscheme

  • Configured for testing on all supported platforms
  • Includes code coverage collection
  • Uses parallel testing when available

Fastlane Configuration: fastlane/Fastfile

  • tests lane: Runs tests on all platforms
  • test_ci lane: CI-specific testing with environment-based destination
  • test lane: Core test execution with scan action
  • build lane: Build-only verification (used for watchOS)

Platform Test Destinations:

  • iOS: platform=iOS Simulator,name=iPhone 15,OS=17.5
  • macOS: platform=macOS
  • tvOS: platform=tvOS Simulator,name=Apple TV,OS=17.5
  • watchOS: platform=watchOS Simulator,name=Apple Watch Series 9 (41mm),OS=10.5 (build only)

Test Helper Utilities

The KingfisherTestHelper.swift provides:

  • Pre-encoded test image data in various formats (PNG, JPEG, GIF, HEIC, MOV)
  • Test URLs and keys for stubbing
  • Cache cleanup utilities (cleanDefaultCache, clearCaches)
  • Image comparison with tolerance (renderEqual)
  • Timing utilities (delay)
  • Platform-specific test helpers

The StubHelpers.swift provides:

  • stub() - Create HTTP response stubs with custom data and headers
  • delayedStub() - Create delayed response stubs for timing tests
  • Network error stubbing capabilities

File: README-LLM.md

Kingfisher

Kingfisher is a powerful, pure-Swift library for downloading and caching images from the web, providing elegant async APIs for iOS, macOS, tvOS, watchOS, and visionOS applications. The library handles the complete image lifecycle with multi-layer caching, built-in processing, and extensive UI component integrations.

Quick Start

Core API Entry Points:

  • Sources/General/KingfisherManager.swift - Central coordinator
  • Sources/General/KF.swift - Builder pattern API (KF.url()...)
  • Sources/Extensions/ImageView+Kingfisher.swift - UIKit/AppKit extensions
  • Sources/SwiftUI/KFImage.swift - SwiftUI components

Essential Build Commands:

bash
# Install dependencies and run all tests
bundle install && bundle exec fastlane tests

# Build for specific platform
swift build

# Full release workflow
bundle exec fastlane release version:X.X.X

Documentation

For LLMs and Developers:

  • Project Overview - What Kingfisher does, core purpose, technology stack, and platform support
  • Architecture - System organization, component map, key files, and data flow with specific file references
  • Build System - Swift Package Manager and Fastlane workflows, platform setup, and troubleshooting
  • Testing - Test categories, running tests, and test infrastructure with file locations
  • Development - Code style, implementation patterns, workflows, and common solutions
  • Deployment - Package types, platform deployment, release management, and CI/CD
  • File Catalog - Comprehensive file organization with specific file purposes and relationships

Configuration Files:

  • Package.swift - Swift Package Manager manifest
  • Kingfisher.podspec - CocoaPods specification
  • fastlane/Fastfile - Build automation
  • Sources/Documentation.docc/ - DocC documentation

Key Patterns:

  • Namespace wrapper (.kf property) in Sources/General/Kingfisher.swift
  • Builder pattern API in Sources/General/KF.swift
  • Options system in Sources/General/KingfisherOptionsInfo.swift
  • Protocol-oriented design throughout Sources/Image/ImageProcessor.swift

Requirements

  • Swift 5.9+ (Swift 6 strict concurrency ready)
  • iOS 13.0+ / macOS 10.15+ / tvOS 13.0+ / watchOS 6.0+ / visionOS 1.0+
  • SwiftUI support: iOS 14.0+ / macOS 11.0+ / tvOS 14.0+ / watchOS 7.0+ / visionOS 1.0+