Skip to content

Kingfisher 5.0 Migration Guide

onevcat edited this page Dec 7, 2018 · 6 revisions

Well, you may ask, why a major version? What happened to Kingfisher 4?

Kingfisher 4 is good. But we can make it better. I rewrote most of the legacy code and Kingfisher 5 brings huge improvement on both stability and performance. At the same time, several outdated APIs are deprecated. I tried to keep the public APIs compatible as much as possible. However, it still contains some minor breaking changes from version 4, which causes you need to do a migration.

Depending on your use cases of Kingfisher 4, it may take no effort or at most several minutes to fix errors and warnings after upgrading. The upgrading should be easy enough for most users. We will talk about it soon.

If you want to know the background story of Kingfisher 5 and what are the new things, check the new features and improvements article. If you want to know how to upgrade to Kingfisher 5, keep reading.

Breaking Changes

Some most notable breaking changes in Kingfisher's APIs.

Result-based API Callback

In Kingfisher 4, a tuple is used as the callback parameter:

public typealias CompletionHandler =
    ((_ image: Image?, _ error: NSError?, _ cacheType: CacheType, _ imageURL: URL?) -> Void)

// Image view extension
public func setImage(
    with resource: Resource?,
    ...
    completionHandler: CompletionHandler?) -> DownloadTask?

This is not extendable well, so they are deprecated. Now since Result type was accepted and will be added to standard library soon, we use it for all the completion callbacks from Kingfisher 5:

// Image view extension
public func setImage(
    with source: Source?,
    ...
    completionHandler: ((Result<RetrieveImageResult, KingfisherError>) -> Void)? = nil) -> DownloadTask?

Result<RetrieveImageResult, KingfisherError> is a result containing either .success(Success) or .failure(Failure) value. In this case, when image setting finishes without problem, a RetrieveImageResult value will be associated to the result .success case. Otherwise, you will get a KingfisherError in .failure.

Usually, there might be three kind of using cases in your current project:

  1. Only call setImage, without handling the tuple-based callback:
  imageView.kf.setImage(with: url)
  imageView.kf.setImage(with: url, placeholder: image, options: [.forceRefresh])

In this case, there is nothing to do. Kingfisher will automatically choose the correct version for you.

  1. Calling setImage, with handling in tuple-based callback:
imageView.kf.setImage(with: url) { (image, error, cacheType, imageURL) in
    if let error = error {
        print("Error: \(error)")
    } else if let image = image {
        print("Image: \(image). Got from: \(cacheType)")
    }
    //...
}

In Kingfisher 5, this code will trigger a warning ("Use Result based callback instead."). You can fix it by adapting to the Result callback:

imageView.kf.setImage(with: url) { result in
    switch result {
    case .success(let value):
        print("Image: \(value.image). Got from: \(value.cacheType)")
    case .failure(let error):
        print("Error: \(error)")
    }
}
  1. Only call setImage, but explictly write nil for completionHandler:
imageView.kf.setImage(with: url, completionHandler: nil)

This will cause an error saying there is ambiguity in code. You could just remove the completionHandler to fix it:

imageView.kf.setImage(with: url)

The RetrieveImageResult type contains information like image, cacheType and url in source. The new Result-based API is a superset of the old tuple APIs.

Error Handling with KingfisherError

In Kingfisher 4, we had a very naive error definition:

public enum KingfisherError: Int {
    case badData = 10000
    case notModified = 10001
    case invalidStatusCode = 10002
    case notCached = 10003
    case invalidURL = 20000
    case downloadCancelledBeforeStarting = 30000
}

This does not serve well when we need to know more about what happened inside the framework. A step further, it does not provide useful information to help framework users to recovery from error or track and solve problems in practice.

In Kingfisher 5, we redefined the KingfisherError. Now it is a type conforms to Error with several nested Reason sub-enums:

public enum KingfisherError: Error {
    public enum RequestErrorReason {
        case emptyRequest
        case invalidURL(request: URLRequest)
        case taskCancelled(task: SessionDataTask, token: SessionDataTask.CancelToken)
    }

    public enum ResponseErrorReason {
        case invalidHTTPStatusCode(response: HTTPURLResponse)
        case URLSessionError(error: Error)
        //...
    }

    public enum CacheErrorReason { /* ... */ }

    //...

    case requestError(reason: RequestErrorReason)
    case responseError(reason: ResponseErrorReason)
    case cacheError(reason: CacheErrorReason)
    case //...
}

This fully defines every possible error might happen in Kingfisher 5. Instead of only giving out an error code, context and related values of the error are associated to the reason case. All of the errors also conforms to LocalizedError and CustomNSError. That means you can get a human-readable representation by accessing error.errorDescription or a stable error.errorCode as Int value.

To make error handling easier, there are some helper getters and methods to identify an error quickly. For example:

let error: KingfisherError = //...

// Indicates that server returns 404 (not found) for your request.
let imageNotFound = error.isInvalidResponseStatusCode(404)

Since we are using the same error type name KingfisherError, if you are checking error number for the old KingfisherError, it might not compile anymore. For example, there is no such thing like KingfisherError.invalidStatusCode.rawValue. Instead, you need to check error.isInvalidResponseStatusCode. It might take you some time to transform your old error handling code. Read the API reference for KingfisherError to know what kinds of errors there are, and the meaning of each error case.

Easier Task Management

The image retrieving methods (including image setting methods in view extensions and some methods of KingfisherManager) now returns a DownloadTask instead of RetrieveImageTask:

public func retrieveImage(
        with resource: Resource,
        options: KingfisherOptionsInfo? = nil,
        progressBlock: DownloadProgressBlock? = nil,
        completionHandler: ((Result<RetrieveImageResult, KingfisherError>) -> Void)?) -> DownloadTask?

Previously, it was a RetrieveImageTask, which is a hybrid task for both loading image asynchronously from disk or from network. The task object is usually used for cancelling an on-going task. But we found that cancelling a disk loading task is not useful but introduces confusing about the loading model in Kingfisher. So we decide to omit it in the new version, just leave the DownloadTask for cancelling a network downloading task.

Similar to RetrieveImageTask, DownloadTask also contains a cancel method. But instead, the returned value DownloadTask is wrapped in an Optional now. If no network downloading happens (the image can be retrieved from cache or can be provided locally), nil will be returned. With the help of Swift type system, this breaking change can be caught easily. You need to decide what to do if you were storing and using the task. A possible example:

// Before
let manager = KingfisherManager.shared
var tasks: [RetrieveImageTask] = [
    manager.retrieveImage(with: url1),
    manager.retrieveImage(with: url2),
]
tasks.forEach { $0.cancel() }
// Now
let manager = KingfisherManager.shared
var tasks: [DownloadTask] = [
    manager.retrieveImage(with: url1),
    manager.retrieveImage(with: url2),
].compactMap { $0 }
tasks.forEach { $0.cancel() }

ImageProcessor conformance

If you created your own ImageProcessor or CacheSerializer types, you may find that they are now not compiling. This is because the method signature of process(item:options:) and image(with:options:) changed a bit. Now, the method accepts a KingfisherParsedOptionsInfo option instead of the original KingfisherOptionsInfo. So just change the type should make your code compile again:

// Before
func process(item: ImageProcessItem, options: KingfisherOptionsInfo) -> Image? { /* */ }
func image(with data: Data, options: KingfisherOptionsInfo) -> Image? { /* */ }
// Now
func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> Image? { /* */ }
func image(with data: Data, options: KingfisherParsedOptionsInfo) -> Image? { /* */ }

Other Minor Changes

For other changes, you should be able to get a warning with help messages when building. Follow the tips to make changes to you Kingfisher 4 code.

This is not likely to happen unless you are a heavy users of Kingfisher and had some customization. In this case, I suggest to continue reading the new features and improvements article for Kingfisher 5, if you want to know every detail. Otherwise, there is no more needed to do and you can just enjoy your future development now.

Feedback

If you have any questions on migrating, open a ticket and we'd glad to help!