-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Kingfisher 5.0 Migration Guide
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.
Some most notable breaking changes in Kingfisher's APIs.
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:
- 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.
- 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)")
}
}
- Only call
setImage
, but explictly writenil
forcompletionHandler
:
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.
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.
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() }
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? { /* */ }
Kingfisher provides a kf
namespace when using extension methods:
imageView.kf.setImage(with: url)
In version 4, kf
returned a class
value, so you can set its properties on it without considering the mutability:
// Works in version 4. But not 5.
let imageView = UIImageView()
imageView.kf.indicatorType = .activity
Now in version 5, kf
returns a struct
instead for better performance. That means you cannot set for a immutable variable declared with let
anymore. Instead, you need to change it to var
:
// Works in version 5.
var imageView = UIImageView()
imageView.kf.indicatorType = .activity
Similar, if you were defining an extension to manipulate kf
, you need to make the value a var
first:
extension UIImageView {
func setupIndicatorType() {
var kf = self.kf
kf.indicatorType = .activity
}
}
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.
If you have any questions on migrating, open a ticket and we'd glad to help!
Installation - Cheat Sheet - FAQ - SwiftUI Support - API Reference - Donate