switch to sdwebimage

This commit is contained in:
Ryan Nystrom
2017-05-12 16:58:42 -04:00
parent fb8498a217
commit 373b188ffa
65 changed files with 6659 additions and 4621 deletions

View File

@@ -7,7 +7,7 @@ target 'Freetime' do
# Pods for Freetime
pod 'Alamofire', '~> 4.4.0'
pod 'AlamofireImage', '~> 3.2.0'
pod 'SDWebImage', '~> 4.0.0'
pod 'IGListKit', :git => 'https://github.com/Instagram/IGListKit.git', :branch => 'master'
pod '1PasswordExtension', '~> 1.8.4'

View File

@@ -1,19 +1,20 @@
PODS:
- 1PasswordExtension (1.8.4)
- Alamofire (4.4.0)
- AlamofireImage (3.2.0):
- Alamofire (~> 4.1)
- IGListKit (2.1.0):
- IGListKit/Default (= 2.1.0)
- IGListKit/Default (2.1.0):
- IGListKit/Diffing
- IGListKit/Diffing (2.1.0)
- SDWebImage (4.0.0):
- SDWebImage/Core (= 4.0.0)
- SDWebImage/Core (4.0.0)
DEPENDENCIES:
- 1PasswordExtension (~> 1.8.4)
- Alamofire (~> 4.4.0)
- AlamofireImage (~> 3.2.0)
- IGListKit (from `https://github.com/Instagram/IGListKit.git`, branch `master`)
- SDWebImage (~> 4.0.0)
EXTERNAL SOURCES:
IGListKit:
@@ -28,9 +29,9 @@ CHECKOUT OPTIONS:
SPEC CHECKSUMS:
1PasswordExtension: e775a29310c65851e5a6cec1afc349ab4e334e47
Alamofire: dc44b1600b800eb63da6a19039a0083d62a6a62d
AlamofireImage: 157ed682cc81d3b9db4fb90c1f12180ac552d93b
IGListKit: b826c68ef7a4ae1626c09d4d3e1ea7a169e6c36e
SDWebImage: 76a6348bdc74eb5a55dd08a091ef298e56b55e41
PODFILE CHECKSUM: 456dae9d5a37ca50b70b098e5b2948e8b9fd029a
PODFILE CHECKSUM: 1c9f99d229a4ebba30490862f081749fbf54bc9a
COCOAPODS: 1.2.0

View File

@@ -1,582 +0,0 @@
# AlamofireImage
[![Build Status](https://travis-ci.org/Alamofire/AlamofireImage.svg?branch=master)](https://travis-ci.org/Alamofire/AlamofireImage)
[![CocoaPods Compatible](https://img.shields.io/cocoapods/v/AlamofireImage.svg)](https://img.shields.io/cocoapods/v/AlamofireImage.svg)
[![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
[![Platform](https://img.shields.io/cocoapods/p/AlamofireImage.svg?style=flat)](http://cocoadocs.org/docsets/AlamofireImage)
[![Twitter](https://img.shields.io/badge/twitter-@AlamofireSF-blue.svg?style=flat)](http://twitter.com/AlamofireSF)
AlamofireImage is an image component library for Alamofire.
## Features
- [x] Image Response Serializers
- [x] UIImage Extensions for Inflation / Scaling / Rounding / CoreImage
- [x] Single and Multi-Pass Image Filters
- [x] Auto-Purging In-Memory Image Cache
- [x] Prioritized Queue Order Image Downloading
- [x] Authentication with URLCredential
- [x] UIImageView Async Remote Downloads with Placeholders
- [x] UIImageView Filters and Transitions
- [x] Comprehensive Test Coverage
- [x] [Complete Documentation](http://cocoadocs.org/docsets/AlamofireImage)
## Requirements
- iOS 8.0+ / macOS 10.10+ / tvOS 9.0+ / watchOS 2.0+
- Xcode 8.0+
- Swift 3.0+
## Migration Guides
- [AlamofireImage 2.0 Migration Guide](https://github.com/Alamofire/AlamofireImage/blob/master/Documentation/AlamofireImage%202.0%20Migration%20Guide.md)
- [AlamofireImage 3.0 Migration Guide](https://github.com/Alamofire/AlamofireImage/blob/master/Documentation/AlamofireImage%203.0%20Migration%20Guide.md)
## Dependencies
- [Alamofire 4.0+](https://github.com/Alamofire/Alamofire)
## Communication
- If you **need help**, use [Stack Overflow](http://stackoverflow.com/questions/tagged/alamofire). (Tag 'alamofire')
- If you'd like to **ask a general question**, use [Stack Overflow](http://stackoverflow.com/questions/tagged/alamofire).
- If you **found a bug**, open an issue.
- If you **have a feature request**, open an issue.
- If you **want to contribute**, submit a pull request.
## Installation
### CocoaPods
[CocoaPods](http://cocoapods.org) is a dependency manager for Cocoa projects. You can install it with the following command:
```bash
$ gem install cocoapods
```
> CocoaPods 1.1.0+ is required.
To integrate AlamofireImage into your Xcode project using CocoaPods, specify it in your `Podfile`:
```ruby
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '10.0'
use_frameworks!
target '<Your Target Name>' do
pod 'AlamofireImage', '~> 3.1'
end
```
Then, run the following command:
```bash
$ pod install
```
### Carthage
[Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks.
You can install Carthage with [Homebrew](http://brew.sh/) using the following command:
```bash
$ brew update
$ brew install carthage
```
To integrate AlamofireImage into your Xcode project using Carthage, specify it in your `Cartfile`:
```ogdl
github "Alamofire/AlamofireImage" ~> 3.1
```
Run `carthage update` to build the framework and drag the built `AlamofireImage.framework` into your Xcode project.
### Manually
If you prefer not to use either of the aforementioned dependency managers, you can integrate AlamofireImage into your project manually.
#### Embedded Framework
- Open up Terminal, `cd` into your top-level project directory, and run the following command "if" your project is not initialized as a git repository:
```bash
$ git init
```
- Add AlamofireImage as a git [submodule](http://git-scm.com/docs/git-submodule) by running the following command:
```bash
$ git submodule add https://github.com/Alamofire/AlamofireImage.git
```
- Open the new `AlamofireImage` folder, and drag the `AlamofireImage.xcodeproj` into the Project Navigator of your application's Xcode project.
> It should appear nested underneath your application's blue project icon. Whether it is above or below all the other Xcode groups does not matter.
- Select the `AlamofireImage.xcodeproj` in the Project Navigator and verify the deployment target matches that of your application target.
- Next, select your application project in the Project Navigator (blue project icon) to navigate to the target configuration window and select the application target under the "Targets" heading in the sidebar.
- In the tab bar at the top of that window, open the "General" panel.
- Click on the `+` button under the "Embedded Binaries" section.
- You will see two different `AlamofireImage.xcodeproj` folders each with two different versions of the `AlamofireImage.framework` nested inside a `Products` folder.
> It does not matter which `Products` folder you choose from, but it does matter whether you choose the top or bottom `AlamofireImage.framework`.
- Select the top `AlamofireImage.framework` for iOS and the bottom one for OS X.
> You can verify which one you selected by inspecting the build log for your project. The build target for `AlamofireImage` will be listed as either `AlamofireImage iOS`, `AlamofireImage macOS`, `AlamofireImage tvOS` or `AlamofireImage watchOS`.
- And that's it!
> The `AlamofireImage.framework` is automagically added as a target dependency, linked framework and embedded framework in a copy files build phase which is all you need to build on the simulator and a device.
---
## Usage
### Image Response Serializers
```swift
import AlamofireImage
Alamofire.request("https://httpbin.org/image/png").responseImage { response in
debugPrint(response)
print(response.request)
print(response.response)
debugPrint(response.result)
if let image = response.result.value {
print("image downloaded: \(image)")
}
}
```
The AlamofireImage response image serializers support a wide range of image types including:
- `image/png`
- `image/jpeg`
- `image/tiff`
- `image/gif`
- `image/ico`
- `image/x-icon`
- `image/bmp`
- `image/x-bmp`
- `image/x-xbitmap`
- `image/x-win-bitmap`
> If the image you are attempting to download is an invalid MIME type not in the list, you can add custom acceptable content types using the `addAcceptableImageContentTypes` extension on the `DataRequest` type.
### UIImage Extensions
There are several `UIImage` extensions designed to make the common image manipulation operations as simple as possible.
#### Inflation
```swift
let url = Bundle.main.url(forResource: "unicorn", withExtension: "png")!
let data = try! Data(contentsOf: url)
let image = UIImage(data: data, scale: UIScreen.main.scale)!
image.af_inflate()
```
> Inflating compressed image formats (such as PNG or JPEG) in a background queue can significantly improve drawing performance on the main thread.
#### Scaling
```swift
let image = UIImage(named: "unicorn")!
let size = CGSize(width: 100.0, height: 100.0)
// Scale image to size disregarding aspect ratio
let scaledImage = image.af_imageScaled(to: size)
// Scale image to fit within specified size while maintaining aspect ratio
let aspectScaledToFitImage = image.af_imageAspectScaled(toFit: size)
// Scale image to fill specified size while maintaining aspect ratio
let aspectScaledToFillImage = image.af_imageAspectScaled(toFill: size)
```
#### Rounded Corners
```swift
let image = UIImage(named: "unicorn")!
let radius: CGFloat = 20.0
let roundedImage = image.af_imageRounded(withCornerRadius: radius)
let circularImage = image.af_imageRoundedIntoCircle()
```
#### Core Image Filters
```swift
let image = UIImage(named: "unicorn")!
let sepiaImage = image.af_imageFiltered(withCoreImageFilter: "CISepiaTone")
let blurredImage = image.af_imageFiltered(
withCoreImageFilter: "CIGuassianBlur",
parameters: ["inputRadius": 25]
)
```
### Image Filters
The `ImageFilter` protocol was designed to make it easy to apply a filter operation and cache the result after an image finished downloading. It defines two properties to facilitate this functionality.
```swift
public protocol ImageFilter {
var filter: Image -> Image { get }
var identifier: String { get }
}
```
The `filter` closure contains the operation used to create a modified version of the specified image. The `identifier` property is a string used to uniquely identify the filter operation. This is useful when adding filtered versions of an image to a cache. All identifier properties inside AlamofireImage are implemented using protocol extensions.
#### Single Pass
The single pass image filters only perform a single operation on the specified image.
```swift
let image = UIImage(named: "unicorn")!
let imageFilter = RoundedCornersFilter(radius: 10.0)
let roundedImage = imageFilter.filter(image)
```
The current list of single pass image filters includes:
- `ScaledToSizeFilter` - Scales an image to a specified size.
- `AspectScaledToFitSizeFilter` - Scales an image from the center while maintaining the aspect ratio to fit within a specified size.
- `AspectScaledToFillSizeFilter` - Scales an image from the center while maintaining the aspect ratio to fill a specified size. Any pixels that fall outside the specified size are clipped.
- `RoundedCornersFilter` - Rounds the corners of an image to the specified radius.
- `CircleFilter` - Rounds the corners of an image into a circle.
- `BlurFilter` - Blurs an image using a `CIGaussianBlur` filter with the specified blur radius.
> Each image filter is built ontop of the `UIImage` extensions.
#### Multi-Pass
The multi-pass image filters perform multiple operations on the specified image.
```swift
let image = UIImage(named: "avatar")!
let size = CGSize(width: 100.0, height: 100.0)
let imageFilter = AspectScaledToFillSizeCircleFilter(size: size)
let avatarImage = imageFilter.filter(image)
```
The current list of multi-pass image filters includes:
- `ScaledToSizeWithRoundedCornersFilter` - Scales an image to a specified size, then rounds the corners to the specified radius.
- `AspectScaledToFillSizeWithRoundedCornersFilter` - Scales an image from the center while maintaining the aspect ratio to fit within a specified size, then rounds the corners to the specified radius.
- `ScaledToSizeCircleFilter` - Scales an image to a specified size, then rounds the corners into a circle.
- `AspectScaledToFillSizeCircleFilter` - Scales an image from the center while maintaining the aspect ratio to fit within a specified size, then rounds the corners into a circle.
### Image Cache
Image caching can become complicated when it comes to network images. `URLCache` is quite powerful and does a great job reasoning through the various cache policies and `Cache-Control` headers. However, it is not equiped to handle caching multiple modified versions of those images.
For example, let's say you need to download an album of images. Your app needs to display both the thumbnail version as well as the full size version at various times. Due to performance issues, you want to scale down the thumbnails to a reasonable size before rendering them on-screen. You also need to apply a global CoreImage filter to the full size images when displayed. While `URLCache` can easily handle storing the original downloaded image, it cannot store these different variants. What you really need is another caching layer designed to handle these different variants.
```swift
let imageCache = AutoPurgingImageCache(
memoryCapacity: 100_000_000,
preferredMemoryUsageAfterPurge: 60_000_000
)
```
The `AutoPurgingImageCache` in AlamofireImage fills the role of that additional caching layer. It is an in-memory image cache used to store images up to a given memory capacity. When the memory capacity is reached, the image cache is sorted by last access date, then the oldest image is continuously purged until the preferred memory usage after purge is met. Each time an image is accessed through the cache, the internal access date of the image is updated.
#### Add / Remove / Fetch Images
Interacting with the `ImageCache` protocol APIs is very straightforward.
```swift
let imageCache = AutoPurgingImageCache()
let avatarImage = UIImage(data: data)!
// Add
imageCache.add(avatarImage, withIdentifier: "avatar")
// Fetch
let cachedAvatar = imageCache.image(withIdentifier: "avatar")
// Remove
imageCache.removeImage(withIdentifier: "avatar")
```
#### URL Requests
The `ImageRequestCache` protocol extends the `ImageCache` protocol by adding support for `URLRequest` caching. This allows a `URLRequest` and an additional identifier to generate the unique identifier for the image in the cache.
```swift
let imageCache = AutoPurgingImageCache()
let urlRequest = URLRequest(url: URL(string: "https://httpbin.org/image/png")!)
let avatarImage = UIImage(named: "avatar")!.af_imageRoundedIntoCircle()
// Add
imageCache.add(avatarImage, for: urlRequest, withIdentifier: "circle")
// Fetch
let cachedAvatarImage = imageCache.image(for: urlRequest, withIdentifier: "circle")
// Remove
imageCache.removeImage(for: urlRequest, withIdentifier: "circle")
```
#### Auto-Purging
Each time an image is fetched from the cache, the cache internally updates the last access date for that image.
```swift
let avatar = imageCache.image(withIdentifier: "avatar")
let circularAvatar = imageCache.image(for: urlRequest, withIdentifier: "circle")
```
By updating the last access date for each image, the image cache can make more informed decisions about which images to purge when the memory capacity is reached. The `AutoPurgingImageCache` automatically evicts images from the cache in order from oldest last access date to newest until the memory capacity drops below the `preferredMemoryCapacityAfterPurge`.
> It is important to set reasonable default values for the `memoryCapacity` and `preferredMemoryCapacityAfterPurge` when you are initializing your image cache. By default, the `memoryCapacity` equals 100 MB and the `preferredMemoryCapacityAfterPurge` equals 60 MB.
#### Memory Warnings
The `AutoPurgingImageCache` also listens for memory warnings from your application and will purge all images from the cache if a memory warning is observed.
### Image Downloader
The `ImageDownloader` class is responsible for downloading images in parallel on a prioritized queue. It uses an internal Alamofire `SessionManager` instance to handle all the downloading and response image serialization. By default, the initialization of an `ImageDownloader` uses a default `URLSessionConfiguration` with the most common parameter values.
```swift
let imageDownloader = ImageDownloader(
configuration: ImageDownloader.defaultURLSessionConfiguration(),
downloadPrioritization: .fifo,
maximumActiveDownloads: 4,
imageCache: AutoPurgingImageCache()
)
```
> If you need to customize the `URLSessionConfiguration` type or parameters, then simply provide your own rather than using the default.
#### Downloading an Image
```swift
let downloader = ImageDownloader()
let urlRequest = URLRequest(url: URL(string: "https://httpbin.org/image/jpeg")!)
downloader.download(urlRequest) { response in
print(response.request)
print(response.response)
debugPrint(response.result)
if let image = response.result.value {
print(image)
}
}
```
> Make sure to keep a strong reference to the `ImageDownloader` instance, otherwise the `completion` closure will not be called because the `downloader` reference will go out of scope before the `completion` closure can be called.
#### Applying an ImageFilter
```swift
let downloader = ImageDownloader()
let urlRequest = URLRequest(url: URL(string: "https://httpbin.org/image/jpeg")!)
let filter = AspectScaledToFillSizeCircleFilter(size: CGSize(width: 100.0, height: 100.0))
downloader.download(urlRequest, filter: filter) { response in
print(response.request)
print(response.response)
debugPrint(response.result)
if let image = response.result.value {
print(image)
}
}
```
#### Authentication
If your images are behind HTTP Basic Auth, you can append the `user:password:` or the `credential` to the `ImageDownloader` instance. The credentials will be applied to all future download requests.
```swift
let downloader = ImageDownloader()
downloader.addAuthentication(user: "username", password: "password")
```
#### Download Prioritization
The `ImageDownloader` maintains an internal queue of pending download requests. Depending on your situation, you may want incoming downloads to be inserted at the front or the back of the queue. The `DownloadPrioritization` enumeration allows you to specify which behavior you would prefer.
```swift
public enum DownloadPrioritization {
case fifo, lifo
}
```
> The `ImageDownloader` is initialized with a `.fifo` queue by default.
#### Image Caching
The `ImageDownloader` uses a combination of an `URLCache` and `AutoPurgingImageCache` to create a very robust, high performance image caching system.
##### URLCache
The `URLCache` is used to cache all the original image content downloaded from the server. By default, it is initialized with a memory capacity of 20 MB and a disk capacity of 150 MB. This allows up to 150 MB of original image data to be stored on disk at any given time. While these defaults have been carefully set, it is very important to consider your application's needs and performance requirements and whether these values are right for you.
> If you wish to disable this caching layer, create a custom `URLSessionConfiguration` with the `urlCache` property set to `nil` and use that configuration when initializing the `ImageDownloader`.
##### Image Cache
The `ImageCache` is used to cache all the potentially filtered image content after it has been downloaded from the server. This allows multiple variants of the same image to also be cached, rather than having to re-apply the image filters to the original image each time it is required. By default, an `AutoPurgingImageCache` is initialized with a memory capacity of 100 MB and a preferred memory usage after purge limit of 60 MB. This allows up to 100 MB of most recently accessed filtered image content to be stored in-memory at a given time.
##### Setting Ideal Capacity Limits
Determining the ideal the in-memory and on-disk capacity limits of the `URLCache` and `AutoPurgingImageCache` requires a bit of forethought. You must carefully consider your application's needs, and tailor the limits accordingly. By default, the combination of caches offers the following storage capacities:
- 150 MB of on-disk storage
- 20 MB of in-memory original image data storage
- 100 MB of in-memory storage of filtered image content
- 60 MB preferred memory capacity after purge of filtered image content
> If you do not use image filters, it is advised to set the memory capacity of the `URLCache` to zero to avoid storing the same content in-memory twice.
#### Duplicate Downloads
Sometimes application logic can end up attempting to download an image more than once before the initial download request is complete. Most often, this results in the image being downloaded more than once. AlamofireImage handles this case elegantly by merging the duplicate downloads. The image will only be downloaded once, yet both completion handlers will be called.
##### Image Filter Reuse
In addition to merging duplicate downloads, AlamofireImage can also merge duplicate image filters. If two image filters with the same identifier are attached to the same download, the image filter is only executed once and both completion handlers are called with the same resulting image. This can save large amounts of time and resources for computationally expensive filters such as ones leveraging CoreImage.
##### Request Receipts
Sometimes it is necessary to cancel an image download for various reasons. AlamofireImage can intelligently handle cancellation logic in the `ImageDownloader` by leveraging the `RequestReceipt` type along with the `cancelRequestForRequestReceipt` method. Each download request vends a `RequestReceipt` which can be later used to cancel the request.
By cancelling the request through the `ImageDownloader` using the `RequestReceipt`, AlamofireImage is able to determine how to best handle the cancellation. The cancelled download will always receive a cancellation error, while duplicate downloads are allowed to complete. If the download is already active, it is allowed to complete even though the completion handler will be called with a cancellation error. This greatly improves performance of table and collection views displaying large amounts of images.
> It is NOT recommended to directly call `cancel` on the `request` in the `RequestReceipt`. Doing so can lead to issues such as duplicate downloads never being allowed to complete.
### UIImageView Extension
The [UIImage Extensions](#uiimage-extensions), [Image Filters](#image-filters), [Image Cache](#image-cache) and [Image Downloader](#image-downloader) were all designed to be flexible and standalone, yet also to provide the foundation of the `UIImageView` extension. Due to the powerful support of these classes, protocols and extensions, the `UIImageView` APIs are concise, easy to use and contain a large amount of functionality.
#### Setting Image with URL
Setting the image with a URL will asynchronously download the image and set it once the request is finished.
```swift
let imageView = UIImageView(frame: frame)
let url = URL(string: "https://httpbin.org/image/png")!
imageView.af_setImage(withURL: url)
```
> If the image is cached locally, the image is set immediately.
#### Placeholder Images
By specifying a placeholder image, the image view uses the placeholder image until the remote image is downloaded.
```swift
let imageView = UIImageView(frame: frame)
let url = URL(string: "https://httpbin.org/image/png")!
let placeholderImage = UIImage(named: "placeholder")!
imageView.af_setImage(withURL: url, placeholderImage: placeholderImage)
```
> If the remote image is cached locally, the placeholder image is never set.
#### Image Filters
If an image filter is specified, it is applied asynchronously after the remote image is downloaded. Once the filter execution is complete, the resulting image is set on the image view.
```swift
let imageView = UIImageView(frame: frame)
let url = URL(string: "https://httpbin.org/image/png")!
let placeholderImage = UIImage(named: "placeholder")!
let filter = AspectScaledToFillSizeWithRoundedCornersFilter(
size: imageView.frame.size,
radius: 20.0
)
imageView.af_setImage(
withURL: url,
placeholderImage: placeholderImage,
filter: filter
)
```
> If the remote image with the applied filter is cached locally, the image is set immediately.
#### Image Transitions
By default, there is no image transition animation when setting the image on the image view. If you wish to add a cross dissolve or flip-from-bottom animation, then specify an `ImageTransition` with the preferred duration.
```swift
let imageView = UIImageView(frame: frame)
let url = URL(string: "https://httpbin.org/image/png")!
let placeholderImage = UIImage(named: "placeholder")!
let filter = AspectScaledToFillSizeWithRoundedCornersFilter(
size: imageView.frame.size,
radius: 20.0
)
imageView.af_setImage(
withURL: url,
placeholderImage: placeholderImage,
filter: filter,
imageTransition: .crossDissolve(0.2)
)
```
> If the remote image is cached locally, the image transition is ignored.
#### Image Downloader
The `UIImageView` extension is powered by the default `ImageDownloader` instance. To customize cache capacities, download priorities, request cache policies, timeout durations, etc., please refer to the [Image Downloader](#image-downloader) documentation.
##### Authentication
If an image requires and authentication credential from the `UIImageView` extension, it can be provided as follows:
```swift
ImageDownloader.default.addAuthentication(user: "user", password: "password")
```
---
## Credits
Alamofire is owned and maintained by the [Alamofire Software Foundation](http://alamofire.org). You can follow them on Twitter at [@AlamofireSF](https://twitter.com/AlamofireSF) for project updates and releases.
### Security Disclosure
If you believe you have identified a security vulnerability with AlamofireImage, you should report it as soon as possible via email to security@alamofire.org. Please do not post it to a public issue tracker.
## Donations
The [ASF](https://github.com/Alamofire/Foundation#members) is looking to raise money to officially register as a federal non-profit organization. Registering will allow us members to gain some legal protections and also allow us to put donations to use, tax free. Donating to the ASF will enable us to:
* Pay our legal fees to register as a federal non-profit organization
* Pay our yearly legal fees to keep the non-profit in good status
* Pay for our mail servers to help us stay on top of all questions and security issues
* Potentially fund test servers to make it easier for us to test the edge cases
* Potentially fund developers to work on one of our projects full-time
The community adoption of the ASF libraries has been amazing. We are greatly humbled by your enthusiam around the projects, and want to continue to do everything we can to move the needle forward. With your continued support, the ASF will be able to improve its reach and also provide better legal safety for the core members. If you use any of our libraries for work, see if your employers would be interested in donating. Our initial goal is to raise $1000 to get all our legal ducks in a row and kickstart this campaign. Any amount you can donate today to help us reach our goal would be greatly appreciated.
<a href='https://pledgie.com/campaigns/31474'><img alt='Click here to lend your support to: Alamofire Software Foundation and make a donation at pledgie.com !' src='https://pledgie.com/campaigns/31474.png?skin_name=chrome' border='0' ></a>
## License
AlamofireImage is released under the MIT license. See LICENSE for details.

View File

@@ -1,63 +0,0 @@
//
// AFIError.swift
//
// Copyright (c) 2015-2016 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
/// `AFIError` is the error type returned by AlamofireImage.
///
/// - requestCancelled: The request was explicitly cancelled.
/// - imageSerializationFailed: Response data could not be serialized into an image.
public enum AFIError: Error {
case requestCancelled
case imageSerializationFailed
}
// MARK: - Error Booleans
extension AFIError {
/// Returns `true` if the `AFIError` is a request cancellation error, `false` otherwise.
public var isRequestCancelledError: Bool {
if case .requestCancelled = self { return true }
return false
}
/// Returns `true` if the `AFIError` is an image serialization error, `false` otherwise.
public var isImageSerializationFailedError: Bool {
if case .imageSerializationFailed = self { return true }
return false
}
}
// MARK: - Error Descriptions
extension AFIError: LocalizedError {
public var errorDescription: String? {
switch self {
case .requestCancelled:
return "The request was explicitly cancelled."
case .imageSerializationFailed:
return "Response data could not be serialized into an image."
}
}
}

View File

@@ -1,33 +0,0 @@
//
// Image.swift
//
// Copyright (c) 2015-2016 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
#if os(iOS) || os(tvOS) || os(watchOS)
import UIKit
public typealias Image = UIImage
#elseif os(macOS)
import Cocoa
public typealias Image = NSImage
#endif

View File

@@ -1,345 +0,0 @@
//
// ImageCache.swift
//
// Copyright (c) 2015-2016 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Alamofire
import Foundation
#if os(iOS) || os(tvOS) || os(watchOS)
import UIKit
#elseif os(macOS)
import Cocoa
#endif
// MARK: ImageCache
/// The `ImageCache` protocol defines a set of APIs for adding, removing and fetching images from a cache.
public protocol ImageCache {
/// Adds the image to the cache with the given identifier.
func add(_ image: Image, withIdentifier identifier: String)
/// Removes the image from the cache matching the given identifier.
func removeImage(withIdentifier identifier: String) -> Bool
/// Removes all images stored in the cache.
@discardableResult
func removeAllImages() -> Bool
/// Returns the image in the cache associated with the given identifier.
func image(withIdentifier identifier: String) -> Image?
}
/// The `ImageRequestCache` protocol extends the `ImageCache` protocol by adding methods for adding, removing and
/// fetching images from a cache given an `URLRequest` and additional identifier.
public protocol ImageRequestCache: ImageCache {
/// Adds the image to the cache using an identifier created from the request and identifier.
func add(_ image: Image, for request: URLRequest, withIdentifier identifier: String?)
/// Removes the image from the cache using an identifier created from the request and identifier.
func removeImage(for request: URLRequest, withIdentifier identifier: String?) -> Bool
/// Returns the image from the cache associated with an identifier created from the request and identifier.
func image(for request: URLRequest, withIdentifier identifier: String?) -> Image?
}
// MARK: -
/// The `AutoPurgingImageCache` in an in-memory image cache used to store images up to a given memory capacity. When
/// the memory capacity is reached, the image cache is sorted by last access date, then the oldest image is continuously
/// purged until the preferred memory usage after purge is met. Each time an image is accessed through the cache, the
/// internal access date of the image is updated.
open class AutoPurgingImageCache: ImageRequestCache {
class CachedImage {
let image: Image
let identifier: String
let totalBytes: UInt64
var lastAccessDate: Date
init(_ image: Image, identifier: String) {
self.image = image
self.identifier = identifier
self.lastAccessDate = Date()
self.totalBytes = {
#if os(iOS) || os(tvOS) || os(watchOS)
let size = CGSize(width: image.size.width * image.scale, height: image.size.height * image.scale)
#elseif os(macOS)
let size = CGSize(width: image.size.width, height: image.size.height)
#endif
let bytesPerPixel: CGFloat = 4.0
let bytesPerRow = size.width * bytesPerPixel
let totalBytes = UInt64(bytesPerRow) * UInt64(size.height)
return totalBytes
}()
}
func accessImage() -> Image {
lastAccessDate = Date()
return image
}
}
// MARK: Properties
/// The current total memory usage in bytes of all images stored within the cache.
open var memoryUsage: UInt64 {
var memoryUsage: UInt64 = 0
synchronizationQueue.sync { memoryUsage = self.currentMemoryUsage }
return memoryUsage
}
/// The total memory capacity of the cache in bytes.
open let memoryCapacity: UInt64
/// The preferred memory usage after purge in bytes. During a purge, images will be purged until the memory
/// capacity drops below this limit.
open let preferredMemoryUsageAfterPurge: UInt64
private let synchronizationQueue: DispatchQueue
private var cachedImages: [String: CachedImage]
private var currentMemoryUsage: UInt64
// MARK: Initialization
/// Initialies the `AutoPurgingImageCache` instance with the given memory capacity and preferred memory usage
/// after purge limit.
///
/// Please note, the memory capacity must always be greater than or equal to the preferred memory usage after purge.
///
/// - parameter memoryCapacity: The total memory capacity of the cache in bytes. `100 MB` by default.
/// - parameter preferredMemoryUsageAfterPurge: The preferred memory usage after purge in bytes. `60 MB` by default.
///
/// - returns: The new `AutoPurgingImageCache` instance.
public init(memoryCapacity: UInt64 = 100_000_000, preferredMemoryUsageAfterPurge: UInt64 = 60_000_000) {
self.memoryCapacity = memoryCapacity
self.preferredMemoryUsageAfterPurge = preferredMemoryUsageAfterPurge
precondition(
memoryCapacity >= preferredMemoryUsageAfterPurge,
"The `memoryCapacity` must be greater than or equal to `preferredMemoryUsageAfterPurge`"
)
self.cachedImages = [:]
self.currentMemoryUsage = 0
self.synchronizationQueue = {
let name = String(format: "org.alamofire.autopurgingimagecache-%08x%08x", arc4random(), arc4random())
return DispatchQueue(label: name, attributes: .concurrent)
}()
#if os(iOS) || os(tvOS)
NotificationCenter.default.addObserver(
self,
selector: #selector(AutoPurgingImageCache.removeAllImages),
name: Notification.Name.UIApplicationDidReceiveMemoryWarning,
object: nil
)
#endif
}
deinit {
NotificationCenter.default.removeObserver(self)
}
// MARK: Add Image to Cache
/// Adds the image to the cache using an identifier created from the request and optional identifier.
///
/// - parameter image: The image to add to the cache.
/// - parameter request: The request used to generate the image's unique identifier.
/// - parameter identifier: The additional identifier to append to the image's unique identifier.
open func add(_ image: Image, for request: URLRequest, withIdentifier identifier: String? = nil) {
let requestIdentifier = imageCacheKey(for: request, withIdentifier: identifier)
add(image, withIdentifier: requestIdentifier)
}
/// Adds the image to the cache with the given identifier.
///
/// - parameter image: The image to add to the cache.
/// - parameter identifier: The identifier to use to uniquely identify the image.
open func add(_ image: Image, withIdentifier identifier: String) {
synchronizationQueue.async(flags: [.barrier]) {
let cachedImage = CachedImage(image, identifier: identifier)
if let previousCachedImage = self.cachedImages[identifier] {
self.currentMemoryUsage -= previousCachedImage.totalBytes
}
self.cachedImages[identifier] = cachedImage
self.currentMemoryUsage += cachedImage.totalBytes
}
synchronizationQueue.async(flags: [.barrier]) {
if self.currentMemoryUsage > self.memoryCapacity {
let bytesToPurge = self.currentMemoryUsage - self.preferredMemoryUsageAfterPurge
var sortedImages = self.cachedImages.map{$1}
sortedImages.sort {
let date1 = $0.lastAccessDate
let date2 = $1.lastAccessDate
return date1.timeIntervalSince(date2) < 0.0
}
var bytesPurged = UInt64(0)
for cachedImage in sortedImages {
self.cachedImages.removeValue(forKey: cachedImage.identifier)
bytesPurged += cachedImage.totalBytes
if bytesPurged >= bytesToPurge {
break
}
}
self.currentMemoryUsage -= bytesPurged
}
}
}
// MARK: Remove Image from Cache
/// Removes the image from the cache using an identifier created from the request and optional identifier.
///
/// - parameter request: The request used to generate the image's unique identifier.
/// - parameter identifier: The additional identifier to append to the image's unique identifier.
///
/// - returns: `true` if the image was removed, `false` otherwise.
@discardableResult
open func removeImage(for request: URLRequest, withIdentifier identifier: String?) -> Bool {
let requestIdentifier = imageCacheKey(for: request, withIdentifier: identifier)
return removeImage(withIdentifier: requestIdentifier)
}
/// Removes all images from the cache created from the request.
///
/// - parameter request: The request used to generate the image's unique identifier.
///
/// - returns: `true` if any images were removed, `false` otherwise.
@discardableResult
open func removeImages(matching request: URLRequest) -> Bool {
let requestIdentifier = imageCacheKey(for: request, withIdentifier: nil)
var removed = false
synchronizationQueue.sync {
for key in self.cachedImages.keys where key.hasPrefix(requestIdentifier) {
if let cachedImage = self.cachedImages.removeValue(forKey: key) {
self.currentMemoryUsage -= cachedImage.totalBytes
removed = true
}
}
}
return removed
}
/// Removes the image from the cache matching the given identifier.
///
/// - parameter identifier: The unique identifier for the image.
///
/// - returns: `true` if the image was removed, `false` otherwise.
@discardableResult
open func removeImage(withIdentifier identifier: String) -> Bool {
var removed = false
synchronizationQueue.sync {
if let cachedImage = self.cachedImages.removeValue(forKey: identifier) {
self.currentMemoryUsage -= cachedImage.totalBytes
removed = true
}
}
return removed
}
/// Removes all images stored in the cache.
///
/// - returns: `true` if images were removed from the cache, `false` otherwise.
@discardableResult @objc
open func removeAllImages() -> Bool {
var removed = false
synchronizationQueue.sync {
if !self.cachedImages.isEmpty {
self.cachedImages.removeAll()
self.currentMemoryUsage = 0
removed = true
}
}
return removed
}
// MARK: Fetch Image from Cache
/// Returns the image from the cache associated with an identifier created from the request and optional identifier.
///
/// - parameter request: The request used to generate the image's unique identifier.
/// - parameter identifier: The additional identifier to append to the image's unique identifier.
///
/// - returns: The image if it is stored in the cache, `nil` otherwise.
open func image(for request: URLRequest, withIdentifier identifier: String? = nil) -> Image? {
let requestIdentifier = imageCacheKey(for: request, withIdentifier: identifier)
return image(withIdentifier: requestIdentifier)
}
/// Returns the image in the cache associated with the given identifier.
///
/// - parameter identifier: The unique identifier for the image.
///
/// - returns: The image if it is stored in the cache, `nil` otherwise.
open func image(withIdentifier identifier: String) -> Image? {
var image: Image?
synchronizationQueue.sync {
if let cachedImage = self.cachedImages[identifier] {
image = cachedImage.accessImage()
}
}
return image
}
// MARK: Image Cache Keys
/// Returns the unique image cache key for the specified request and additional identifier.
///
/// - parameter request: The request.
/// - parameter identifier: The additional identifier.
///
/// - returns: The unique image cache key.
open func imageCacheKey(for request: URLRequest, withIdentifier identifier: String?) -> String {
var key = request.url?.absoluteString ?? ""
if let identifier = identifier {
key += "-\(identifier)"
}
return key
}
}

View File

@@ -1,553 +0,0 @@
//
// ImageDownloader.swift
//
// Copyright (c) 2015-2016 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Alamofire
import Foundation
#if os(iOS) || os(tvOS) || os(watchOS)
import UIKit
#elseif os(macOS)
import Cocoa
#endif
/// The `RequestReceipt` is an object vended by the `ImageDownloader` when starting a download request. It can be used
/// to cancel active requests running on the `ImageDownloader` session. As a general rule, image download requests
/// should be cancelled using the `RequestReceipt` instead of calling `cancel` directly on the `request` itself. The
/// `ImageDownloader` is optimized to handle duplicate request scenarios as well as pending versus active downloads.
open class RequestReceipt {
/// The download request created by the `ImageDownloader`.
open let request: Request
/// The unique identifier for the image filters and completion handlers when duplicate requests are made.
open let receiptID: String
init(request: Request, receiptID: String) {
self.request = request
self.receiptID = receiptID
}
}
// MARK: -
/// The `ImageDownloader` class is responsible for downloading images in parallel on a prioritized queue. Incoming
/// downloads are added to the front or back of the queue depending on the download prioritization. Each downloaded
/// image is cached in the underlying `NSURLCache` as well as the in-memory image cache that supports image filters.
/// By default, any download request with a cached image equivalent in the image cache will automatically be served the
/// cached image representation. Additional advanced features include supporting multiple image filters and completion
/// handlers for a single request.
open class ImageDownloader {
/// The completion handler closure used when an image download completes.
public typealias CompletionHandler = (DataResponse<Image>) -> Void
/// The progress handler closure called periodically during an image download.
public typealias ProgressHandler = DataRequest.ProgressHandler
// MARK: Helper Types
/// Defines the order prioritization of incoming download requests being inserted into the queue.
///
/// - fifo: All incoming downloads are added to the back of the queue.
/// - lifo: All incoming downloads are added to the front of the queue.
public enum DownloadPrioritization {
case fifo, lifo
}
class ResponseHandler {
let urlID: String
let handlerID: String
let request: DataRequest
var operations: [(receiptID: String, filter: ImageFilter?, completion: CompletionHandler?)]
init(
request: DataRequest,
handlerID: String,
receiptID: String,
filter: ImageFilter?,
completion: CompletionHandler?)
{
self.request = request
self.urlID = ImageDownloader.urlIdentifier(for: request.request!)
self.handlerID = handlerID
self.operations = [(receiptID: receiptID, filter: filter, completion: completion)]
}
}
// MARK: Properties
/// The image cache used to store all downloaded images in.
open let imageCache: ImageRequestCache?
/// The credential used for authenticating each download request.
open private(set) var credential: URLCredential?
/// Response serializer used to convert the image data to UIImage.
public var imageResponseSerializer = DataRequest.imageResponseSerializer()
/// The underlying Alamofire `Manager` instance used to handle all download requests.
open let sessionManager: SessionManager
let downloadPrioritization: DownloadPrioritization
let maximumActiveDownloads: Int
var activeRequestCount = 0
var queuedRequests: [Request] = []
var responseHandlers: [String: ResponseHandler] = [:]
private let synchronizationQueue: DispatchQueue = {
let name = String(format: "org.alamofire.imagedownloader.synchronizationqueue-%08x%08x", arc4random(), arc4random())
return DispatchQueue(label: name)
}()
private let responseQueue: DispatchQueue = {
let name = String(format: "org.alamofire.imagedownloader.responsequeue-%08x%08x", arc4random(), arc4random())
return DispatchQueue(label: name, attributes: .concurrent)
}()
// MARK: Initialization
/// The default instance of `ImageDownloader` initialized with default values.
open static let `default` = ImageDownloader()
/// Creates a default `URLSessionConfiguration` with common usage parameter values.
///
/// - returns: The default `URLSessionConfiguration` instance.
open class func defaultURLSessionConfiguration() -> URLSessionConfiguration {
let configuration = URLSessionConfiguration.default
configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
configuration.httpShouldSetCookies = true
configuration.httpShouldUsePipelining = false
configuration.requestCachePolicy = .useProtocolCachePolicy
configuration.allowsCellularAccess = true
configuration.timeoutIntervalForRequest = 60
configuration.urlCache = ImageDownloader.defaultURLCache()
return configuration
}
/// Creates a default `URLCache` with common usage parameter values.
///
/// - returns: The default `URLCache` instance.
open class func defaultURLCache() -> URLCache {
return URLCache(
memoryCapacity: 20 * 1024 * 1024, // 20 MB
diskCapacity: 150 * 1024 * 1024, // 150 MB
diskPath: "org.alamofire.imagedownloader"
)
}
/// Initializes the `ImageDownloader` instance with the given configuration, download prioritization, maximum active
/// download count and image cache.
///
/// - parameter configuration: The `URLSessionConfiguration` to use to create the underlying Alamofire
/// `SessionManager` instance.
/// - parameter downloadPrioritization: The download prioritization of the download queue. `.fifo` by default.
/// - parameter maximumActiveDownloads: The maximum number of active downloads allowed at any given time.
/// - parameter imageCache: The image cache used to store all downloaded images in.
///
/// - returns: The new `ImageDownloader` instance.
public init(
configuration: URLSessionConfiguration = ImageDownloader.defaultURLSessionConfiguration(),
downloadPrioritization: DownloadPrioritization = .fifo,
maximumActiveDownloads: Int = 4,
imageCache: ImageRequestCache? = AutoPurgingImageCache())
{
self.sessionManager = SessionManager(configuration: configuration)
self.sessionManager.startRequestsImmediately = false
self.downloadPrioritization = downloadPrioritization
self.maximumActiveDownloads = maximumActiveDownloads
self.imageCache = imageCache
}
/// Initializes the `ImageDownloader` instance with the given session manager, download prioritization, maximum
/// active download count and image cache.
///
/// - parameter sessionManager: The Alamofire `SessionManager` instance to handle all download requests.
/// - parameter downloadPrioritization: The download prioritization of the download queue. `.fifo` by default.
/// - parameter maximumActiveDownloads: The maximum number of active downloads allowed at any given time.
/// - parameter imageCache: The image cache used to store all downloaded images in.
///
/// - returns: The new `ImageDownloader` instance.
public init(
sessionManager: SessionManager,
downloadPrioritization: DownloadPrioritization = .fifo,
maximumActiveDownloads: Int = 4,
imageCache: ImageRequestCache? = AutoPurgingImageCache())
{
self.sessionManager = sessionManager
self.sessionManager.startRequestsImmediately = false
self.downloadPrioritization = downloadPrioritization
self.maximumActiveDownloads = maximumActiveDownloads
self.imageCache = imageCache
}
// MARK: Authentication
/// Associates an HTTP Basic Auth credential with all future download requests.
///
/// - parameter user: The user.
/// - parameter password: The password.
/// - parameter persistence: The URL credential persistence. `.forSession` by default.
open func addAuthentication(
user: String,
password: String,
persistence: URLCredential.Persistence = .forSession)
{
let credential = URLCredential(user: user, password: password, persistence: persistence)
addAuthentication(usingCredential: credential)
}
/// Associates the specified credential with all future download requests.
///
/// - parameter credential: The credential.
open func addAuthentication(usingCredential credential: URLCredential) {
synchronizationQueue.sync {
self.credential = credential
}
}
// MARK: Download
/// Creates a download request using the internal Alamofire `SessionManager` instance for the specified URL request.
///
/// If the same download request is already in the queue or currently being downloaded, the filter and completion
/// handler are appended to the already existing request. Once the request completes, all filters and completion
/// handlers attached to the request are executed in the order they were added. Additionally, any filters attached
/// to the request with the same identifiers are only executed once. The resulting image is then passed into each
/// completion handler paired with the filter.
///
/// You should not attempt to directly cancel the `request` inside the request receipt since other callers may be
/// relying on the completion of that request. Instead, you should call `cancelRequestForRequestReceipt` with the
/// returned request receipt to allow the `ImageDownloader` to optimize the cancellation on behalf of all active
/// callers.
///
/// - parameter urlRequest: The URL request.
/// - parameter receiptID: The `identifier` for the `RequestReceipt` returned. Defaults to a new, randomly
/// generated UUID.
/// - parameter filter: The image filter to apply to the image after the download is complete. Defaults
/// to `nil`.
/// - parameter progress: The closure to be executed periodically during the lifecycle of the request.
/// Defaults to `nil`.
/// - parameter progressQueue: The dispatch queue to call the progress closure on. Defaults to the main queue.
/// - parameter completion: The closure called when the download request is complete. Defaults to `nil`.
///
/// - returns: The request receipt for the download request if available. `nil` if the image is stored in the image
/// cache and the URL request cache policy allows the cache to be used.
@discardableResult
open func download(
_ urlRequest: URLRequestConvertible,
receiptID: String = UUID().uuidString,
filter: ImageFilter? = nil,
progress: ProgressHandler? = nil,
progressQueue: DispatchQueue = DispatchQueue.main,
completion: CompletionHandler?)
-> RequestReceipt?
{
var request: DataRequest!
synchronizationQueue.sync {
// 1) Append the filter and completion handler to a pre-existing request if it already exists
let urlID = ImageDownloader.urlIdentifier(for: urlRequest)
if let responseHandler = self.responseHandlers[urlID] {
responseHandler.operations.append(receiptID: receiptID, filter: filter, completion: completion)
request = responseHandler.request
return
}
// 2) Attempt to load the image from the image cache if the cache policy allows it
if let request = urlRequest.urlRequest {
switch request.cachePolicy {
case .useProtocolCachePolicy, .returnCacheDataElseLoad, .returnCacheDataDontLoad:
if let image = self.imageCache?.image(for: request, withIdentifier: filter?.identifier) {
DispatchQueue.main.async {
let response = DataResponse<Image>(
request: urlRequest.urlRequest,
response: nil,
data: nil,
result: .success(image)
)
completion?(response)
}
return
}
default:
break
}
}
// 3) Create the request and set up authentication, validation and response serialization
request = self.sessionManager.request(urlRequest)
if let credential = self.credential {
request.authenticate(usingCredential: credential)
}
request.validate()
if let progress = progress {
request.downloadProgress(queue: progressQueue, closure: progress)
}
// Generate a unique handler id to check whether the active request has changed while downloading
let handlerID = UUID().uuidString
request.response(
queue: self.responseQueue,
responseSerializer: imageResponseSerializer,
completionHandler: { [weak self] response in
guard let strongSelf = self, let request = response.request else { return }
defer {
strongSelf.safelyDecrementActiveRequestCount()
strongSelf.safelyStartNextRequestIfNecessary()
}
// Early out if the request has changed out from under us
let handler = strongSelf.safelyFetchResponseHandler(withURLIdentifier: urlID)
guard handler?.handlerID == handlerID else { return }
guard let responseHandler = strongSelf.safelyRemoveResponseHandler(withURLIdentifier: urlID) else {
return
}
switch response.result {
case .success(let image):
var filteredImages: [String: Image] = [:]
for (_, filter, completion) in responseHandler.operations {
var filteredImage: Image
if let filter = filter {
if let alreadyFilteredImage = filteredImages[filter.identifier] {
filteredImage = alreadyFilteredImage
} else {
filteredImage = filter.filter(image)
filteredImages[filter.identifier] = filteredImage
}
} else {
filteredImage = image
}
strongSelf.imageCache?.add(filteredImage, for: request, withIdentifier: filter?.identifier)
DispatchQueue.main.async {
let response = DataResponse<Image>(
request: response.request,
response: response.response,
data: response.data,
result: .success(filteredImage),
timeline: response.timeline
)
completion?(response)
}
}
case .failure:
for (_, _, completion) in responseHandler.operations {
DispatchQueue.main.async { completion?(response) }
}
}
}
)
// 4) Store the response handler for use when the request completes
let responseHandler = ResponseHandler(
request: request,
handlerID: handlerID,
receiptID: receiptID,
filter: filter,
completion: completion
)
self.responseHandlers[urlID] = responseHandler
// 5) Either start the request or enqueue it depending on the current active request count
if self.isActiveRequestCountBelowMaximumLimit() {
self.start(request)
} else {
self.enqueue(request)
}
}
if let request = request {
return RequestReceipt(request: request, receiptID: receiptID)
}
return nil
}
/// Creates a download request using the internal Alamofire `SessionManager` instance for each specified URL request.
///
/// For each request, if the same download request is already in the queue or currently being downloaded, the
/// filter and completion handler are appended to the already existing request. Once the request completes, all
/// filters and completion handlers attached to the request are executed in the order they were added.
/// Additionally, any filters attached to the request with the same identifiers are only executed once. The
/// resulting image is then passed into each completion handler paired with the filter.
///
/// You should not attempt to directly cancel any of the `request`s inside the request receipts array since other
/// callers may be relying on the completion of that request. Instead, you should call
/// `cancelRequestForRequestReceipt` with the returned request receipt to allow the `ImageDownloader` to optimize
/// the cancellation on behalf of all active callers.
///
/// - parameter urlRequests: The URL requests.
/// - parameter filter The image filter to apply to the image after each download is complete.
/// - parameter progress: The closure to be executed periodically during the lifecycle of the request. Defaults
/// to `nil`.
/// - parameter progressQueue: The dispatch queue to call the progress closure on. Defaults to the main queue.
/// - parameter completion: The closure called when each download request is complete.
///
/// - returns: The request receipts for the download requests if available. If an image is stored in the image
/// cache and the URL request cache policy allows the cache to be used, a receipt will not be returned
/// for that request.
@discardableResult
open func download(
_ urlRequests: [URLRequestConvertible],
filter: ImageFilter? = nil,
progress: ProgressHandler? = nil,
progressQueue: DispatchQueue = DispatchQueue.main,
completion: CompletionHandler? = nil)
-> [RequestReceipt]
{
return urlRequests.flatMap {
download($0, filter: filter, progress: progress, progressQueue: progressQueue, completion: completion)
}
}
/// Cancels the request in the receipt by removing the response handler and cancelling the request if necessary.
///
/// If the request is pending in the queue, it will be cancelled if no other response handlers are registered with
/// the request. If the request is currently executing or is already completed, the response handler is removed and
/// will not be called.
///
/// - parameter requestReceipt: The request receipt to cancel.
open func cancelRequest(with requestReceipt: RequestReceipt) {
synchronizationQueue.sync {
let urlID = ImageDownloader.urlIdentifier(for: requestReceipt.request.request!)
guard let responseHandler = self.responseHandlers[urlID] else { return }
if let index = responseHandler.operations.index(where: { $0.receiptID == requestReceipt.receiptID }) {
let operation = responseHandler.operations.remove(at: index)
let response: DataResponse<Image> = {
let urlRequest = requestReceipt.request.request
let error = AFIError.requestCancelled
return DataResponse(request: urlRequest, response: nil, data: nil, result: .failure(error))
}()
DispatchQueue.main.async { operation.completion?(response) }
}
if responseHandler.operations.isEmpty && requestReceipt.request.task?.state == .suspended {
requestReceipt.request.cancel()
self.responseHandlers.removeValue(forKey: urlID)
}
}
}
// MARK: Internal - Thread-Safe Request Methods
func safelyFetchResponseHandler(withURLIdentifier urlIdentifier: String) -> ResponseHandler? {
var responseHandler: ResponseHandler?
synchronizationQueue.sync {
responseHandler = self.responseHandlers[urlIdentifier]
}
return responseHandler
}
func safelyRemoveResponseHandler(withURLIdentifier identifier: String) -> ResponseHandler? {
var responseHandler: ResponseHandler?
synchronizationQueue.sync {
responseHandler = self.responseHandlers.removeValue(forKey: identifier)
}
return responseHandler
}
func safelyStartNextRequestIfNecessary() {
synchronizationQueue.sync {
guard self.isActiveRequestCountBelowMaximumLimit() else { return }
while !self.queuedRequests.isEmpty {
if let request = self.dequeue(), request.task?.state == .suspended {
self.start(request)
break
}
}
}
}
func safelyDecrementActiveRequestCount() {
self.synchronizationQueue.sync {
if self.activeRequestCount > 0 {
self.activeRequestCount -= 1
}
}
}
// MARK: Internal - Non Thread-Safe Request Methods
func start(_ request: Request) {
request.resume()
activeRequestCount += 1
}
func enqueue(_ request: Request) {
switch downloadPrioritization {
case .fifo:
queuedRequests.append(request)
case .lifo:
queuedRequests.insert(request, at: 0)
}
}
@discardableResult
func dequeue() -> Request? {
var request: Request?
if !queuedRequests.isEmpty {
request = queuedRequests.removeFirst()
}
return request
}
func isActiveRequestCountBelowMaximumLimit() -> Bool {
return activeRequestCount < maximumActiveDownloads
}
static func urlIdentifier(for urlRequest: URLRequestConvertible) -> String {
return urlRequest.urlRequest?.url?.absoluteString ?? ""
}
}

View File

@@ -1,423 +0,0 @@
//
// ImageFilter.swift
//
// Copyright (c) 2015-2016 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
#if os(iOS) || os(tvOS) || os(watchOS)
import UIKit
#elseif os(macOS)
import Cocoa
#endif
// MARK: ImageFilter
/// The `ImageFilter` protocol defines properties for filtering an image as well as identification of the filter.
public protocol ImageFilter {
/// A closure used to create an alternative representation of the given image.
var filter: (Image) -> Image { get }
/// The string used to uniquely identify the filter operation.
var identifier: String { get }
}
extension ImageFilter {
/// The unique identifier for any `ImageFilter` type.
public var identifier: String { return "\(type(of: self))" }
}
// MARK: - Sizable
/// The `Sizable` protocol defines a size property intended for use with `ImageFilter` types.
public protocol Sizable {
/// The size of the type.
var size: CGSize { get }
}
extension ImageFilter where Self: Sizable {
/// The unique idenitifier for an `ImageFilter` conforming to the `Sizable` protocol.
public var identifier: String {
let width = Int64(size.width.rounded())
let height = Int64(size.height.rounded())
return "\(type(of: self))-size:(\(width)x\(height))"
}
}
// MARK: - Roundable
/// The `Roundable` protocol defines a radius property intended for use with `ImageFilter` types.
public protocol Roundable {
/// The radius of the type.
var radius: CGFloat { get }
}
extension ImageFilter where Self: Roundable {
/// The unique idenitifier for an `ImageFilter` conforming to the `Roundable` protocol.
public var identifier: String {
let radius = Int64(self.radius.rounded())
return "\(type(of: self))-radius:(\(radius))"
}
}
// MARK: - DynamicImageFilter
/// The `DynamicImageFilter` class simplifies custom image filter creation by using a trailing closure initializer.
public struct DynamicImageFilter: ImageFilter {
/// The string used to uniquely identify the image filter operation.
public let identifier: String
/// A closure used to create an alternative representation of the given image.
public let filter: (Image) -> Image
/// Initializes the `DynamicImageFilter` instance with the specified identifier and filter closure.
///
/// - parameter identifier: The unique identifier of the filter.
/// - parameter filter: A closure used to create an alternative representation of the given image.
///
/// - returns: The new `DynamicImageFilter` instance.
public init(_ identifier: String, filter: @escaping (Image) -> Image) {
self.identifier = identifier
self.filter = filter
}
}
// MARK: - CompositeImageFilter
/// The `CompositeImageFilter` protocol defines an additional `filters` property to support multiple composite filters.
public protocol CompositeImageFilter: ImageFilter {
/// The image filters to apply to the image in sequential order.
var filters: [ImageFilter] { get }
}
public extension CompositeImageFilter {
/// The unique idenitifier for any `CompositeImageFilter` type.
var identifier: String {
return filters.map { $0.identifier }.joined(separator: "_")
}
/// The filter closure for any `CompositeImageFilter` type.
var filter: (Image) -> Image {
return { image in
return self.filters.reduce(image) { $1.filter($0) }
}
}
}
// MARK: - DynamicCompositeImageFilter
/// The `DynamicCompositeImageFilter` class is a composite image filter based on a specified array of filters.
public struct DynamicCompositeImageFilter: CompositeImageFilter {
/// The image filters to apply to the image in sequential order.
public let filters: [ImageFilter]
/// Initializes the `DynamicCompositeImageFilter` instance with the given filters.
///
/// - parameter filters: The filters taking part in the composite image filter.
///
/// - returns: The new `DynamicCompositeImageFilter` instance.
public init(_ filters: [ImageFilter]) {
self.filters = filters
}
/// Initializes the `DynamicCompositeImageFilter` instance with the given filters.
///
/// - parameter filters: The filters taking part in the composite image filter.
///
/// - returns: The new `DynamicCompositeImageFilter` instance.
public init(_ filters: ImageFilter...) {
self.init(filters)
}
}
#if os(iOS) || os(tvOS) || os(watchOS)
// MARK: - Single Pass Image Filters (iOS, tvOS and watchOS only) -
/// Scales an image to a specified size.
public struct ScaledToSizeFilter: ImageFilter, Sizable {
/// The size of the filter.
public let size: CGSize
/// Initializes the `ScaledToSizeFilter` instance with the given size.
///
/// - parameter size: The size.
///
/// - returns: The new `ScaledToSizeFilter` instance.
public init(size: CGSize) {
self.size = size
}
/// The filter closure used to create the modified representation of the given image.
public var filter: (Image) -> Image {
return { image in
return image.af_imageScaled(to: self.size)
}
}
}
// MARK: -
/// Scales an image from the center while maintaining the aspect ratio to fit within a specified size.
public struct AspectScaledToFitSizeFilter: ImageFilter, Sizable {
/// The size of the filter.
public let size: CGSize
/// Initializes the `AspectScaledToFitSizeFilter` instance with the given size.
///
/// - parameter size: The size.
///
/// - returns: The new `AspectScaledToFitSizeFilter` instance.
public init(size: CGSize) {
self.size = size
}
/// The filter closure used to create the modified representation of the given image.
public var filter: (Image) -> Image {
return { image in
return image.af_imageAspectScaled(toFit: self.size)
}
}
}
// MARK: -
/// Scales an image from the center while maintaining the aspect ratio to fill a specified size. Any pixels that fall
/// outside the specified size are clipped.
public struct AspectScaledToFillSizeFilter: ImageFilter, Sizable {
/// The size of the filter.
public let size: CGSize
/// Initializes the `AspectScaledToFillSizeFilter` instance with the given size.
///
/// - parameter size: The size.
///
/// - returns: The new `AspectScaledToFillSizeFilter` instance.
public init(size: CGSize) {
self.size = size
}
/// The filter closure used to create the modified representation of the given image.
public var filter: (Image) -> Image {
return { image in
return image.af_imageAspectScaled(toFill: self.size)
}
}
}
// MARK: -
/// Rounds the corners of an image to the specified radius.
public struct RoundedCornersFilter: ImageFilter, Roundable {
/// The radius of the filter.
public let radius: CGFloat
/// Whether to divide the radius by the image scale.
public let divideRadiusByImageScale: Bool
/// Initializes the `RoundedCornersFilter` instance with the given radius.
///
/// - parameter radius: The radius.
/// - parameter divideRadiusByImageScale: Whether to divide the radius by the image scale. Set to `true` when the
/// image has the same resolution for all screen scales such as @1x, @2x and
/// @3x (i.e. single image from web server). Set to `false` for images loaded
/// from an asset catalog with varying resolutions for each screen scale.
/// `false` by default.
///
/// - returns: The new `RoundedCornersFilter` instance.
public init(radius: CGFloat, divideRadiusByImageScale: Bool = false) {
self.radius = radius
self.divideRadiusByImageScale = divideRadiusByImageScale
}
/// The filter closure used to create the modified representation of the given image.
public var filter: (Image) -> Image {
return { image in
return image.af_imageRounded(
withCornerRadius: self.radius,
divideRadiusByImageScale: self.divideRadiusByImageScale
)
}
}
/// The unique idenitifier for an `ImageFilter` conforming to the `Roundable` protocol.
public var identifier: String {
let radius = Int64(self.radius.rounded())
return "\(type(of: self))-radius:(\(radius))-divided:(\(divideRadiusByImageScale))"
}
}
// MARK: -
/// Rounds the corners of an image into a circle.
public struct CircleFilter: ImageFilter {
/// Initializes the `CircleFilter` instance.
///
/// - returns: The new `CircleFilter` instance.
public init() {}
/// The filter closure used to create the modified representation of the given image.
public var filter: (Image) -> Image {
return { image in
return image.af_imageRoundedIntoCircle()
}
}
}
// MARK: -
#if os(iOS) || os(tvOS)
/// The `CoreImageFilter` protocol defines `parameters`, `filterName` properties used by CoreImage.
@available(iOS 9.0, *)
public protocol CoreImageFilter: ImageFilter {
/// The filter name of the CoreImage filter.
var filterName: String { get }
/// The image filter parameters passed to CoreImage.
var parameters: [String: Any] { get }
}
@available(iOS 9.0, *)
public extension ImageFilter where Self: CoreImageFilter {
/// The filter closure used to create the modified representation of the given image.
public var filter: (Image) -> Image {
return { image in
return image.af_imageFiltered(withCoreImageFilter: self.filterName, parameters: self.parameters) ?? image
}
}
/// The unique idenitifier for an `ImageFilter` conforming to the `CoreImageFilter` protocol.
public var identifier: String { return "\(type(of: self))-parameters:(\(self.parameters))" }
}
/// Blurs an image using a `CIGaussianBlur` filter with the specified blur radius.
@available(iOS 9.0, *)
public struct BlurFilter: ImageFilter, CoreImageFilter {
/// The filter name.
public let filterName = "CIGaussianBlur"
/// The image filter parameters passed to CoreImage.
public let parameters: [String: Any]
/// Initializes the `BlurFilter` instance with the given blur radius.
///
/// - parameter blurRadius: The blur radius.
///
/// - returns: The new `BlurFilter` instance.
public init(blurRadius: UInt = 10) {
self.parameters = ["inputRadius": blurRadius]
}
}
#endif
// MARK: - Composite Image Filters (iOS, tvOS and watchOS only) -
/// Scales an image to a specified size, then rounds the corners to the specified radius.
public struct ScaledToSizeWithRoundedCornersFilter: CompositeImageFilter {
/// Initializes the `ScaledToSizeWithRoundedCornersFilter` instance with the given size and radius.
///
/// - parameter size: The size.
/// - parameter radius: The radius.
/// - parameter divideRadiusByImageScale: Whether to divide the radius by the image scale. Set to `true` when the
/// image has the same resolution for all screen scales such as @1x, @2x and
/// @3x (i.e. single image from web server). Set to `false` for images loaded
/// from an asset catalog with varying resolutions for each screen scale.
/// `false` by default.
///
/// - returns: The new `ScaledToSizeWithRoundedCornersFilter` instance.
public init(size: CGSize, radius: CGFloat, divideRadiusByImageScale: Bool = false) {
self.filters = [
ScaledToSizeFilter(size: size),
RoundedCornersFilter(radius: radius, divideRadiusByImageScale: divideRadiusByImageScale)
]
}
/// The image filters to apply to the image in sequential order.
public let filters: [ImageFilter]
}
// MARK: -
/// Scales an image from the center while maintaining the aspect ratio to fit within a specified size, then rounds the
/// corners to the specified radius.
public struct AspectScaledToFillSizeWithRoundedCornersFilter: CompositeImageFilter {
/// Initializes the `AspectScaledToFillSizeWithRoundedCornersFilter` instance with the given size and radius.
///
/// - parameter size: The size.
/// - parameter radius: The radius.
/// - parameter divideRadiusByImageScale: Whether to divide the radius by the image scale. Set to `true` when the
/// image has the same resolution for all screen scales such as @1x, @2x and
/// @3x (i.e. single image from web server). Set to `false` for images loaded
/// from an asset catalog with varying resolutions for each screen scale.
/// `false` by default.
///
/// - returns: The new `AspectScaledToFillSizeWithRoundedCornersFilter` instance.
public init(size: CGSize, radius: CGFloat, divideRadiusByImageScale: Bool = false) {
self.filters = [
AspectScaledToFillSizeFilter(size: size),
RoundedCornersFilter(radius: radius, divideRadiusByImageScale: divideRadiusByImageScale)
]
}
/// The image filters to apply to the image in sequential order.
public let filters: [ImageFilter]
}
// MARK: -
/// Scales an image to a specified size, then rounds the corners into a circle.
public struct ScaledToSizeCircleFilter: CompositeImageFilter {
/// Initializes the `ScaledToSizeCircleFilter` instance with the given size.
///
/// - parameter size: The size.
///
/// - returns: The new `ScaledToSizeCircleFilter` instance.
public init(size: CGSize) {
self.filters = [ScaledToSizeFilter(size: size), CircleFilter()]
}
/// The image filters to apply to the image in sequential order.
public let filters: [ImageFilter]
}
// MARK: -
/// Scales an image from the center while maintaining the aspect ratio to fit within a specified size, then rounds the
/// corners into a circle.
public struct AspectScaledToFillSizeCircleFilter: CompositeImageFilter {
/// Initializes the `AspectScaledToFillSizeCircleFilter` instance with the given size.
///
/// - parameter size: The size.
///
/// - returns: The new `AspectScaledToFillSizeCircleFilter` instance.
public init(size: CGSize) {
self.filters = [AspectScaledToFillSizeFilter(size: size), CircleFilter()]
}
/// The image filters to apply to the image in sequential order.
public let filters: [ImageFilter]
}
#endif

View File

@@ -1,317 +0,0 @@
//
// Request+AlamofireImage.swift
//
// Copyright (c) 2015-2016 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Alamofire
import Foundation
#if os(iOS) || os(tvOS)
import UIKit
#elseif os(watchOS)
import UIKit
import WatchKit
#elseif os(macOS)
import Cocoa
#endif
extension DataRequest {
static var acceptableImageContentTypes: Set<String> = [
"image/tiff",
"image/jpeg",
"image/gif",
"image/png",
"image/ico",
"image/x-icon",
"image/bmp",
"image/x-bmp",
"image/x-xbitmap",
"image/x-ms-bmp",
"image/x-win-bitmap"
]
static let streamImageInitialBytePattern = Data(bytes: [255, 216]) // 0xffd8
/// Adds the content types specified to the list of acceptable images content types for validation.
///
/// - parameter contentTypes: The additional content types.
public class func addAcceptableImageContentTypes(_ contentTypes: Set<String>) {
DataRequest.acceptableImageContentTypes.formUnion(contentTypes)
}
// MARK: - iOS, tvOS and watchOS
#if os(iOS) || os(tvOS) || os(watchOS)
/// Creates a response serializer that returns an image initialized from the response data using the specified
/// image options.
///
/// - parameter imageScale: The scale factor used when interpreting the image data to construct
/// `responseImage`. Specifying a scale factor of 1.0 results in an image whose
/// size matches the pixel-based dimensions of the image. Applying a different
/// scale factor changes the size of the image as reported by the size property.
/// `Screen.scale` by default.
/// - parameter inflateResponseImage: Whether to automatically inflate response image data for compressed formats
/// (such as PNG or JPEG). Enabling this can significantly improve drawing
/// performance as it allows a bitmap representation to be constructed in the
/// background rather than on the main thread. `true` by default.
///
/// - returns: An image response serializer.
public class func imageResponseSerializer(
imageScale: CGFloat = DataRequest.imageScale,
inflateResponseImage: Bool = true)
-> DataResponseSerializer<Image>
{
return DataResponseSerializer { request, response, data, error in
let result = serializeResponseData(response: response, data: data, error: error)
guard case let .success(data) = result else { return .failure(result.error!) }
do {
try DataRequest.validateContentType(for: request, response: response)
let image = try DataRequest.image(from: data, withImageScale: imageScale)
if inflateResponseImage { image.af_inflate() }
return .success(image)
} catch {
return .failure(error)
}
}
}
/// Adds a response handler to be called once the request has finished.
///
/// - parameter imageScale: The scale factor used when interpreting the image data to construct
/// `responseImage`. Specifying a scale factor of 1.0 results in an image whose
/// size matches the pixel-based dimensions of the image. Applying a different
/// scale factor changes the size of the image as reported by the size property.
/// This is set to the value of scale of the main screen by default, which
/// automatically scales images for retina displays, for instance.
/// `Screen.scale` by default.
/// - parameter inflateResponseImage: Whether to automatically inflate response image data for compressed formats
/// (such as PNG or JPEG). Enabling this can significantly improve drawing
/// performance as it allows a bitmap representation to be constructed in the
/// background rather than on the main thread. `true` by default.
/// - parameter completionHandler: A closure to be executed once the request has finished. The closure takes 4
/// arguments: the URL request, the URL response, if one was received, the image,
/// if one could be created from the URL response and data, and any error produced
/// while creating the image.
///
/// - returns: The request.
@discardableResult
public func responseImage(
imageScale: CGFloat = DataRequest.imageScale,
inflateResponseImage: Bool = true,
completionHandler: @escaping (DataResponse<Image>) -> Void)
-> Self
{
return response(
responseSerializer: DataRequest.imageResponseSerializer(
imageScale: imageScale,
inflateResponseImage: inflateResponseImage
),
completionHandler: completionHandler
)
}
/// Sets a closure to be called periodically during the lifecycle of the request as data is read from the server
/// and converted into images.
///
/// - parameter imageScale: The scale factor used when interpreting the image data to construct
/// `responseImage`. Specifying a scale factor of 1.0 results in an image whose
/// size matches the pixel-based dimensions of the image. Applying a different
/// scale factor changes the size of the image as reported by the size property.
/// This is set to the value of scale of the main screen by default, which
/// automatically scales images for retina displays, for instance.
/// `Screen.scale` by default.
/// - parameter inflateResponseImage: Whether to automatically inflate response image data for compressed formats
/// (such as PNG or JPEG). Enabling this can significantly improve drawing
/// performance as it allows a bitmap representation to be constructed in the
/// background rather than on the main thread. `true` by default.
/// - parameter completionHandler: A closure to be executed when the request has new image. The closure takes 1
/// argument: the image, if one could be created from the data.
///
/// - returns: The request.
@discardableResult
public func streamImage(
imageScale: CGFloat = DataRequest.imageScale,
inflateResponseImage: Bool = true,
completionHandler: @escaping (Image) -> Void)
-> Self
{
var imageData = Data()
return stream { chunkData in
if chunkData.starts(with: DataRequest.streamImageInitialBytePattern) {
imageData = Data()
}
imageData.append(chunkData)
if let image = DataRequest.serializeImage(from: imageData) {
completionHandler(image)
}
}
}
private class func serializeImage(
from data: Data,
imageScale: CGFloat = DataRequest.imageScale,
inflateResponseImage: Bool = true)
-> UIImage?
{
guard data.count > 0 else { return nil }
do {
let image = try DataRequest.image(from: data, withImageScale: imageScale)
if inflateResponseImage { image.af_inflate() }
return image
} catch {
return nil
}
}
private class func image(from data: Data, withImageScale imageScale: CGFloat) throws -> UIImage {
if let image = UIImage.af_threadSafeImage(with: data, scale: imageScale) {
return image
}
throw AFIError.imageSerializationFailed
}
private class var imageScale: CGFloat {
#if os(iOS) || os(tvOS)
return UIScreen.main.scale
#elseif os(watchOS)
return WKInterfaceDevice.current().screenScale
#endif
}
#elseif os(macOS)
// MARK: - macOS
/// Creates a response serializer that returns an image initialized from the response data.
///
/// - returns: An image response serializer.
public class func imageResponseSerializer() -> DataResponseSerializer<Image> {
return DataResponseSerializer { request, response, data, error in
let result = serializeResponseData(response: response, data: data, error: error)
guard case let .success(data) = result else { return .failure(result.error!) }
do {
try DataRequest.validateContentType(for: request, response: response)
} catch {
return .failure(error)
}
guard let bitmapImage = NSBitmapImageRep(data: data) else {
return .failure(AFIError.imageSerializationFailed)
}
let image = NSImage(size: NSSize(width: bitmapImage.pixelsWide, height: bitmapImage.pixelsHigh))
image.addRepresentation(bitmapImage)
return .success(image)
}
}
/// Adds a response handler to be called once the request has finished.
///
/// - parameter completionHandler: A closure to be executed once the request has finished. The closure takes 4
/// arguments: the URL request, the URL response, if one was received, the image, if
/// one could be created from the URL response and data, and any error produced while
/// creating the image.
///
/// - returns: The request.
@discardableResult
public func responseImage(completionHandler: @escaping (DataResponse<Image>) -> Void) -> Self {
return response(
responseSerializer: DataRequest.imageResponseSerializer(),
completionHandler: completionHandler
)
}
/// Sets a closure to be called periodically during the lifecycle of the request as data is read from the server
/// and converted into images.
///
/// - parameter completionHandler: A closure to be executed when the request has new image. The closure takes 1
/// argument: the image, if one could be created from the data.
///
/// - returns: The request.
@discardableResult
public func streamImage(completionHandler: @escaping (Image) -> Void) -> Self {
var imageData = Data()
return stream { chunkData in
if chunkData.starts(with: DataRequest.streamImageInitialBytePattern) {
imageData = Data()
}
imageData.append(chunkData)
if let image = DataRequest.serializeImage(from: imageData) {
completionHandler(image)
}
}
}
private class func serializeImage(from data: Data) -> NSImage? {
guard data.count > 0 else { return nil }
guard let bitmapImage = NSBitmapImageRep(data: data) else { return nil }
let image = NSImage(size: NSSize(width: bitmapImage.pixelsWide, height: bitmapImage.pixelsHigh))
image.addRepresentation(bitmapImage)
return image
}
#endif
// MARK: - Content Type Validation
/// Returns whether the content type of the response matches one of the acceptable content types.
///
/// - parameter request: The request.
/// - parameter response: The server response.
///
/// - throws: An `AFError` response validation failure when an error is encountered.
public class func validateContentType(for request: URLRequest?, response: HTTPURLResponse?) throws {
if let url = request?.url, url.isFileURL { return }
guard let mimeType = response?.mimeType else {
let contentTypes = Array(DataRequest.acceptableImageContentTypes)
throw AFError.responseValidationFailed(reason: .missingContentType(acceptableContentTypes: contentTypes))
}
guard DataRequest.acceptableImageContentTypes.contains(mimeType) else {
let contentTypes = Array(DataRequest.acceptableImageContentTypes)
throw AFError.responseValidationFailed(
reason: .unacceptableContentType(acceptableContentTypes: contentTypes, responseContentType: mimeType)
)
}
}
}

View File

@@ -1,473 +0,0 @@
//
// UIButton+AlamofireImage.swift
//
// Copyright (c) 2015-2016 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Alamofire
import Foundation
#if os(iOS) || os(tvOS)
import UIKit
extension UIButton {
// MARK: - Private - AssociatedKeys
private struct AssociatedKey {
static var imageDownloader = "af_UIButton.ImageDownloader"
static var sharedImageDownloader = "af_UIButton.SharedImageDownloader"
static var imageReceipts = "af_UIButton.ImageReceipts"
static var backgroundImageReceipts = "af_UIButton.BackgroundImageReceipts"
}
// MARK: - Properties
/// The instance image downloader used to download all images. If this property is `nil`, the `UIButton` will
/// fallback on the `af_sharedImageDownloader` for all downloads. The most common use case for needing to use a
/// custom instance image downloader is when images are behind different basic auth credentials.
public var af_imageDownloader: ImageDownloader? {
get {
return objc_getAssociatedObject(self, &AssociatedKey.imageDownloader) as? ImageDownloader
}
set {
objc_setAssociatedObject(self, &AssociatedKey.imageDownloader, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
/// The shared image downloader used to download all images. By default, this is the default `ImageDownloader`
/// instance backed with an `AutoPurgingImageCache` which automatically evicts images from the cache when the memory
/// capacity is reached or memory warning notifications occur. The shared image downloader is only used if the
/// `af_imageDownloader` is `nil`.
public class var af_sharedImageDownloader: ImageDownloader {
get {
guard let
downloader = objc_getAssociatedObject(self, &AssociatedKey.sharedImageDownloader) as? ImageDownloader
else {
return ImageDownloader.default
}
return downloader
}
set {
objc_setAssociatedObject(self, &AssociatedKey.sharedImageDownloader, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
private var imageRequestReceipts: [UInt: RequestReceipt] {
get {
guard let
receipts = objc_getAssociatedObject(self, &AssociatedKey.imageReceipts) as? [UInt: RequestReceipt]
else {
return [:]
}
return receipts
}
set {
objc_setAssociatedObject(self, &AssociatedKey.imageReceipts, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
private var backgroundImageRequestReceipts: [UInt: RequestReceipt] {
get {
guard let
receipts = objc_getAssociatedObject(self, &AssociatedKey.backgroundImageReceipts) as? [UInt: RequestReceipt]
else {
return [:]
}
return receipts
}
set {
objc_setAssociatedObject(self, &AssociatedKey.backgroundImageReceipts, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
// MARK: - Image Downloads
/// Asynchronously downloads an image from the specified URL and sets it once the request is finished.
///
/// If the image is cached locally, the image is set immediately. Otherwise the specified placehoder image will be
/// set immediately, and then the remote image will be set once the image request is finished.
///
/// - parameter state: The control state of the button to set the image on.
/// - parameter url: The URL used for your image request.
/// - parameter placeholderImage: The image to be set initially until the image request finished. If `nil`, the
/// image will not change its image until the image request finishes. Defaults
/// to `nil`.
/// - parameter filter: The image filter applied to the image after the image request is finished.
/// Defaults to `nil`.
/// - parameter progress: The closure to be executed periodically during the lifecycle of the request.
/// Defaults to `nil`.
/// - parameter progressQueue: The dispatch queue to call the progress closure on. Defaults to the main queue.
/// - parameter completion: A closure to be executed when the image request finishes. The closure takes a
/// single response value containing either the image or the error that occurred. If
/// the image was returned from the image cache, the response will be `nil`. Defaults
/// to `nil`.
public func af_setImage(
for state: UIControlState,
url: URL,
placeholderImage: UIImage? = nil,
filter: ImageFilter? = nil,
progress: ImageDownloader.ProgressHandler? = nil,
progressQueue: DispatchQueue = DispatchQueue.main,
completion: ((DataResponse<UIImage>) -> Void)? = nil)
{
af_setImage(
for: state,
urlRequest: urlRequest(with: url),
placeholderImage: placeholderImage,
filter: filter,
progress: progress,
progressQueue: progressQueue,
completion: completion
)
}
/// Asynchronously downloads an image from the specified URL request and sets it once the request is finished.
///
/// If the image is cached locally, the image is set immediately. Otherwise the specified placehoder image will be
/// set immediately, and then the remote image will be set once the image request is finished.
///
/// - parameter state: The control state of the button to set the image on.
/// - parameter urlRequest: The URL request.
/// - parameter placeholderImage: The image to be set initially until the image request finished. If `nil`, the
/// image will not change its image until the image request finishes. Defaults
/// to `nil`.
/// - parameter filter: The image filter applied to the image after the image request is finished.
/// Defaults to `nil`.
/// - parameter progress: The closure to be executed periodically during the lifecycle of the request.
/// Defaults to `nil`.
/// - parameter progressQueue: The dispatch queue to call the progress closure on. Defaults to the main queue.
/// - parameter completion: A closure to be executed when the image request finishes. The closure takes a
/// single response value containing either the image or the error that occurred. If
/// the image was returned from the image cache, the response will be `nil`. Defaults
/// to `nil`.
public func af_setImage(
for state: UIControlState,
urlRequest: URLRequestConvertible,
placeholderImage: UIImage? = nil,
filter: ImageFilter? = nil,
progress: ImageDownloader.ProgressHandler? = nil,
progressQueue: DispatchQueue = DispatchQueue.main,
completion: ((DataResponse<UIImage>) -> Void)? = nil)
{
guard !isImageURLRequest(urlRequest, equalToActiveRequestURLForState: state) else {
let error = AFIError.requestCancelled
let response = DataResponse<UIImage>(request: nil, response: nil, data: nil, result: .failure(error))
completion?(response)
return
}
af_cancelImageRequest(for: state)
let imageDownloader = af_imageDownloader ?? UIButton.af_sharedImageDownloader
let imageCache = imageDownloader.imageCache
// Use the image from the image cache if it exists
if
let request = urlRequest.urlRequest,
let image = imageCache?.image(for: request, withIdentifier: filter?.identifier)
{
let response = DataResponse<UIImage>(
request: urlRequest.urlRequest,
response: nil,
data: nil,
result: .success(image)
)
setImage(image, for: state)
completion?(response)
return
}
// Set the placeholder since we're going to have to download
if let placeholderImage = placeholderImage { setImage(placeholderImage, for: state) }
// Generate a unique download id to check whether the active request has changed while downloading
let downloadID = UUID().uuidString
// Download the image, then set the image for the control state
let requestReceipt = imageDownloader.download(
urlRequest,
receiptID: downloadID,
filter: filter,
progress: progress,
progressQueue: progressQueue,
completion: { [weak self] response in
guard
let strongSelf = self,
strongSelf.isImageURLRequest(response.request, equalToActiveRequestURLForState: state) &&
strongSelf.imageRequestReceipt(for: state)?.receiptID == downloadID
else {
completion?(response)
return
}
if let image = response.result.value {
strongSelf.setImage(image, for: state)
}
strongSelf.setImageRequestReceipt(nil, for: state)
completion?(response)
}
)
setImageRequestReceipt(requestReceipt, for: state)
}
/// Cancels the active download request for the image, if one exists.
public func af_cancelImageRequest(for state: UIControlState) {
guard let receipt = imageRequestReceipt(for: state) else { return }
let imageDownloader = af_imageDownloader ?? UIButton.af_sharedImageDownloader
imageDownloader.cancelRequest(with: receipt)
setImageRequestReceipt(nil, for: state)
}
// MARK: - Background Image Downloads
/// Asynchronously downloads an image from the specified URL and sets it once the request is finished.
///
/// If the image is cached locally, the image is set immediately. Otherwise the specified placehoder image will be
/// set immediately, and then the remote image will be set once the image request is finished.
///
/// - parameter state: The control state of the button to set the image on.
/// - parameter url: The URL used for the image request.
/// - parameter placeholderImage: The image to be set initially until the image request finished. If `nil`, the
/// background image will not change its image until the image request finishes.
/// Defaults to `nil`.
/// - parameter filter: The image filter applied to the image after the image request is finished.
/// Defaults to `nil`.
/// - parameter progress: The closure to be executed periodically during the lifecycle of the request.
/// Defaults to `nil`.
/// - parameter progressQueue: The dispatch queue to call the progress closure on. Defaults to the main queue.
/// - parameter completion: A closure to be executed when the image request finishes. The closure takes a
/// single response value containing either the image or the error that occurred. If
/// the image was returned from the image cache, the response will be `nil`. Defaults
/// to `nil`.
public func af_setBackgroundImage(
for state: UIControlState,
url: URL,
placeholderImage: UIImage? = nil,
filter: ImageFilter? = nil,
progress: ImageDownloader.ProgressHandler? = nil,
progressQueue: DispatchQueue = DispatchQueue.main,
completion: ((DataResponse<UIImage>) -> Void)? = nil)
{
af_setBackgroundImage(
for: state,
urlRequest: urlRequest(with: url),
placeholderImage: placeholderImage,
filter: filter,
progress: progress,
progressQueue: progressQueue,
completion: completion
)
}
/// Asynchronously downloads an image from the specified URL request and sets it once the request is finished.
///
/// If the image is cached locally, the image is set immediately. Otherwise the specified placehoder image will be
/// set immediately, and then the remote image will be set once the image request is finished.
///
/// - parameter state: The control state of the button to set the image on.
/// - parameter urlRequest: The URL request.
/// - parameter placeholderImage: The image to be set initially until the image request finished. If `nil`, the
/// background image will not change its image until the image request finishes.
/// Defaults to `nil`.
/// - parameter filter: The image filter applied to the image after the image request is finished.
/// Defaults to `nil`.
/// - parameter progress: The closure to be executed periodically during the lifecycle of the request.
/// Defaults to `nil`.
/// - parameter progressQueue: The dispatch queue to call the progress closure on. Defaults to the main queue.
/// - parameter completion: A closure to be executed when the image request finishes. The closure takes a
/// single response value containing either the image or the error that occurred. If
/// the image was returned from the image cache, the response will be `nil`. Defaults
/// to `nil`.
public func af_setBackgroundImage(
for state: UIControlState,
urlRequest: URLRequestConvertible,
placeholderImage: UIImage? = nil,
filter: ImageFilter? = nil,
progress: ImageDownloader.ProgressHandler? = nil,
progressQueue: DispatchQueue = DispatchQueue.main,
completion: ((DataResponse<UIImage>) -> Void)? = nil)
{
guard !isImageURLRequest(urlRequest, equalToActiveRequestURLForState: state) else {
let error = AFIError.requestCancelled
let response = DataResponse<UIImage>(request: nil, response: nil, data: nil, result: .failure(error))
completion?(response)
return
}
af_cancelBackgroundImageRequest(for: state)
let imageDownloader = af_imageDownloader ?? UIButton.af_sharedImageDownloader
let imageCache = imageDownloader.imageCache
// Use the image from the image cache if it exists
if
let request = urlRequest.urlRequest,
let image = imageCache?.image(for: request, withIdentifier: filter?.identifier)
{
let response = DataResponse<UIImage>(
request: urlRequest.urlRequest,
response: nil,
data: nil,
result: .success(image)
)
setBackgroundImage(image, for: state)
completion?(response)
return
}
// Set the placeholder since we're going to have to download
if let placeholderImage = placeholderImage { self.setBackgroundImage(placeholderImage, for: state) }
// Generate a unique download id to check whether the active request has changed while downloading
let downloadID = UUID().uuidString
// Download the image, then set the image for the control state
let requestReceipt = imageDownloader.download(
urlRequest,
receiptID: downloadID,
filter: nil,
progress: progress,
progressQueue: progressQueue,
completion: { [weak self] response in
guard
let strongSelf = self,
strongSelf.isBackgroundImageURLRequest(response.request, equalToActiveRequestURLForState: state) &&
strongSelf.backgroundImageRequestReceipt(for: state)?.receiptID == downloadID
else {
completion?(response)
return
}
if let image = response.result.value {
strongSelf.setBackgroundImage(image, for: state)
}
strongSelf.setBackgroundImageRequestReceipt(nil, for: state)
completion?(response)
}
)
setBackgroundImageRequestReceipt(requestReceipt, for: state)
}
/// Cancels the active download request for the background image, if one exists.
public func af_cancelBackgroundImageRequest(for state: UIControlState) {
guard let receipt = backgroundImageRequestReceipt(for: state) else { return }
let imageDownloader = af_imageDownloader ?? UIButton.af_sharedImageDownloader
imageDownloader.cancelRequest(with: receipt)
setBackgroundImageRequestReceipt(nil, for: state)
}
// MARK: - Internal - Image Request Receipts
func imageRequestReceipt(for state: UIControlState) -> RequestReceipt? {
guard let receipt = imageRequestReceipts[state.rawValue] else { return nil }
return receipt
}
func setImageRequestReceipt(_ receipt: RequestReceipt?, for state: UIControlState) {
var receipts = imageRequestReceipts
receipts[state.rawValue] = receipt
imageRequestReceipts = receipts
}
// MARK: - Internal - Background Image Request Receipts
func backgroundImageRequestReceipt(for state: UIControlState) -> RequestReceipt? {
guard let receipt = backgroundImageRequestReceipts[state.rawValue] else { return nil }
return receipt
}
func setBackgroundImageRequestReceipt(_ receipt: RequestReceipt?, for state: UIControlState) {
var receipts = backgroundImageRequestReceipts
receipts[state.rawValue] = receipt
backgroundImageRequestReceipts = receipts
}
// MARK: - Private - URL Request Helpers
private func isImageURLRequest(
_ urlRequest: URLRequestConvertible?,
equalToActiveRequestURLForState state: UIControlState)
-> Bool
{
if
let currentURL = imageRequestReceipt(for: state)?.request.task?.originalRequest?.url,
let requestURL = urlRequest?.urlRequest?.url,
currentURL == requestURL
{
return true
}
return false
}
private func isBackgroundImageURLRequest(
_ urlRequest: URLRequestConvertible?,
equalToActiveRequestURLForState state: UIControlState)
-> Bool
{
if
let currentRequestURL = backgroundImageRequestReceipt(for: state)?.request.task?.originalRequest?.url,
let requestURL = urlRequest?.urlRequest?.url,
currentRequestURL == requestURL
{
return true
}
return false
}
private func urlRequest(with url: URL) -> URLRequest {
var urlRequest = URLRequest(url: url)
for mimeType in DataRequest.acceptableImageContentTypes {
urlRequest.addValue(mimeType, forHTTPHeaderField: "Accept")
}
return urlRequest
}
}
#endif

View File

@@ -1,309 +0,0 @@
//
// UIImage+AlamofireImage.swift
//
// Copyright (c) 2015-2016 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#if os(iOS) || os(tvOS) || os(watchOS)
import CoreGraphics
import Foundation
import UIKit
// MARK: Initialization
private let lock = NSLock()
extension UIImage {
/// Initializes and returns the image object with the specified data in a thread-safe manner.
///
/// It has been reported that there are thread-safety issues when initializing large amounts of images
/// simultaneously. In the event of these issues occurring, this method can be used in place of
/// the `init?(data:)` method.
///
/// - parameter data: The data object containing the image data.
///
/// - returns: An initialized `UIImage` object, or `nil` if the method failed.
public static func af_threadSafeImage(with data: Data) -> UIImage? {
lock.lock()
let image = UIImage(data: data)
lock.unlock()
return image
}
/// Initializes and returns the image object with the specified data and scale in a thread-safe manner.
///
/// It has been reported that there are thread-safety issues when initializing large amounts of images
/// simultaneously. In the event of these issues occurring, this method can be used in place of
/// the `init?(data:scale:)` method.
///
/// - parameter data: The data object containing the image data.
/// - parameter scale: The scale factor to assume when interpreting the image data. Applying a scale factor of 1.0
/// results in an image whose size matches the pixel-based dimensions of the image. Applying a
/// different scale factor changes the size of the image as reported by the size property.
///
/// - returns: An initialized `UIImage` object, or `nil` if the method failed.
public static func af_threadSafeImage(with data: Data, scale: CGFloat) -> UIImage? {
lock.lock()
let image = UIImage(data: data, scale: scale)
lock.unlock()
return image
}
}
// MARK: - Inflation
extension UIImage {
private struct AssociatedKey {
static var inflated = "af_UIImage.Inflated"
}
/// Returns whether the image is inflated.
public var af_inflated: Bool {
get {
if let inflated = objc_getAssociatedObject(self, &AssociatedKey.inflated) as? Bool {
return inflated
} else {
return false
}
}
set {
objc_setAssociatedObject(self, &AssociatedKey.inflated, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
/// Inflates the underlying compressed image data to be backed by an uncompressed bitmap representation.
///
/// Inflating compressed image formats (such as PNG or JPEG) can significantly improve drawing performance as it
/// allows a bitmap representation to be constructed in the background rather than on the main thread.
public func af_inflate() {
guard !af_inflated else { return }
af_inflated = true
_ = cgImage?.dataProvider?.data
}
}
// MARK: - Alpha
extension UIImage {
/// Returns whether the image contains an alpha component.
public var af_containsAlphaComponent: Bool {
let alphaInfo = cgImage?.alphaInfo
return (
alphaInfo == .first ||
alphaInfo == .last ||
alphaInfo == .premultipliedFirst ||
alphaInfo == .premultipliedLast
)
}
/// Returns whether the image is opaque.
public var af_isOpaque: Bool { return !af_containsAlphaComponent }
}
// MARK: - Scaling
extension UIImage {
/// Returns a new version of the image scaled to the specified size.
///
/// - parameter size: The size to use when scaling the new image.
///
/// - returns: A new image object.
public func af_imageScaled(to size: CGSize) -> UIImage {
UIGraphicsBeginImageContextWithOptions(size, af_isOpaque, 0.0)
draw(in: CGRect(origin: CGPoint.zero, size: size))
let scaledImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return scaledImage
}
/// Returns a new version of the image scaled from the center while maintaining the aspect ratio to fit within
/// a specified size.
///
/// The resulting image contains an alpha component used to pad the width or height with the necessary transparent
/// pixels to fit the specified size. In high performance critical situations, this may not be the optimal approach.
/// To maintain an opaque image, you could compute the `scaledSize` manually, then use the `af_imageScaledToSize`
/// method in conjunction with a `.Center` content mode to achieve the same visual result.
///
/// - parameter size: The size to use when scaling the new image.
///
/// - returns: A new image object.
public func af_imageAspectScaled(toFit size: CGSize) -> UIImage {
let imageAspectRatio = self.size.width / self.size.height
let canvasAspectRatio = size.width / size.height
var resizeFactor: CGFloat
if imageAspectRatio > canvasAspectRatio {
resizeFactor = size.width / self.size.width
} else {
resizeFactor = size.height / self.size.height
}
let scaledSize = CGSize(width: self.size.width * resizeFactor, height: self.size.height * resizeFactor)
let origin = CGPoint(x: (size.width - scaledSize.width) / 2.0, y: (size.height - scaledSize.height) / 2.0)
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
draw(in: CGRect(origin: origin, size: scaledSize))
let scaledImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return scaledImage
}
/// Returns a new version of the image scaled from the center while maintaining the aspect ratio to fill a
/// specified size. Any pixels that fall outside the specified size are clipped.
///
/// - parameter size: The size to use when scaling the new image.
///
/// - returns: A new image object.
public func af_imageAspectScaled(toFill size: CGSize) -> UIImage {
let imageAspectRatio = self.size.width / self.size.height
let canvasAspectRatio = size.width / size.height
var resizeFactor: CGFloat
if imageAspectRatio > canvasAspectRatio {
resizeFactor = size.height / self.size.height
} else {
resizeFactor = size.width / self.size.width
}
let scaledSize = CGSize(width: self.size.width * resizeFactor, height: self.size.height * resizeFactor)
let origin = CGPoint(x: (size.width - scaledSize.width) / 2.0, y: (size.height - scaledSize.height) / 2.0)
UIGraphicsBeginImageContextWithOptions(size, af_isOpaque, 0.0)
draw(in: CGRect(origin: origin, size: scaledSize))
let scaledImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return scaledImage
}
}
// MARK: - Rounded Corners
extension UIImage {
/// Returns a new version of the image with the corners rounded to the specified radius.
///
/// - parameter radius: The radius to use when rounding the new image.
/// - parameter divideRadiusByImageScale: Whether to divide the radius by the image scale. Set to `true` when the
/// image has the same resolution for all screen scales such as @1x, @2x and
/// @3x (i.e. single image from web server). Set to `false` for images loaded
/// from an asset catalog with varying resolutions for each screen scale.
/// `false` by default.
///
/// - returns: A new image object.
public func af_imageRounded(withCornerRadius radius: CGFloat, divideRadiusByImageScale: Bool = false) -> UIImage {
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
let scaledRadius = divideRadiusByImageScale ? radius / scale : radius
let clippingPath = UIBezierPath(roundedRect: CGRect(origin: CGPoint.zero, size: size), cornerRadius: scaledRadius)
clippingPath.addClip()
draw(in: CGRect(origin: CGPoint.zero, size: size))
let roundedImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return roundedImage
}
/// Returns a new version of the image rounded into a circle.
///
/// - returns: A new image object.
public func af_imageRoundedIntoCircle() -> UIImage {
let radius = min(size.width, size.height) / 2.0
var squareImage = self
if size.width != size.height {
let squareDimension = min(size.width, size.height)
let squareSize = CGSize(width: squareDimension, height: squareDimension)
squareImage = af_imageAspectScaled(toFill: squareSize)
}
UIGraphicsBeginImageContextWithOptions(squareImage.size, false, 0.0)
let clippingPath = UIBezierPath(
roundedRect: CGRect(origin: CGPoint.zero, size: squareImage.size),
cornerRadius: radius
)
clippingPath.addClip()
squareImage.draw(in: CGRect(origin: CGPoint.zero, size: squareImage.size))
let roundedImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return roundedImage
}
}
#endif
#if os(iOS) || os(tvOS)
import CoreImage
// MARK: - Core Image Filters
@available(iOS 9.0, *)
extension UIImage {
/// Returns a new version of the image using a CoreImage filter with the specified name and parameters.
///
/// - parameter name: The name of the CoreImage filter to use on the new image.
/// - parameter parameters: The parameters to apply to the CoreImage filter.
///
/// - returns: A new image object, or `nil` if the filter failed for any reason.
public func af_imageFiltered(withCoreImageFilter name: String, parameters: [String: Any]? = nil) -> UIImage? {
var image: CoreImage.CIImage? = ciImage
if image == nil, let CGImage = self.cgImage {
image = CoreImage.CIImage(cgImage: CGImage)
}
guard let coreImage = image else { return nil }
let context = CIContext(options: [kCIContextPriorityRequestLow: true])
var parameters: [String: Any] = parameters ?? [:]
parameters[kCIInputImageKey] = coreImage
guard let filter = CIFilter(name: name, withInputParameters: parameters) else { return nil }
guard let outputImage = filter.outputImage else { return nil }
let cgImageRef = context.createCGImage(outputImage, from: outputImage.extent)
return UIImage(cgImage: cgImageRef!, scale: scale, orientation: imageOrientation)
}
}
#endif

View File

@@ -1,392 +0,0 @@
//
// UIImageView+AlamofireImage.swift
//
// Copyright (c) 2015-2016 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Alamofire
import Foundation
#if os(iOS) || os(tvOS)
import UIKit
extension UIImageView {
// MARK: - ImageTransition
/// Used to wrap all `UIView` animation transition options alongside a duration.
public enum ImageTransition {
case noTransition
case crossDissolve(TimeInterval)
case curlDown(TimeInterval)
case curlUp(TimeInterval)
case flipFromBottom(TimeInterval)
case flipFromLeft(TimeInterval)
case flipFromRight(TimeInterval)
case flipFromTop(TimeInterval)
case custom(
duration: TimeInterval,
animationOptions: UIViewAnimationOptions,
animations: (UIImageView, Image) -> Void,
completion: ((Bool) -> Void)?
)
/// The duration of the image transition in seconds.
public var duration: TimeInterval {
switch self {
case .noTransition:
return 0.0
case .crossDissolve(let duration):
return duration
case .curlDown(let duration):
return duration
case .curlUp(let duration):
return duration
case .flipFromBottom(let duration):
return duration
case .flipFromLeft(let duration):
return duration
case .flipFromRight(let duration):
return duration
case .flipFromTop(let duration):
return duration
case .custom(let duration, _, _, _):
return duration
}
}
/// The animation options of the image transition.
public var animationOptions: UIViewAnimationOptions {
switch self {
case .noTransition:
return UIViewAnimationOptions()
case .crossDissolve:
return .transitionCrossDissolve
case .curlDown:
return .transitionCurlDown
case .curlUp:
return .transitionCurlUp
case .flipFromBottom:
return .transitionFlipFromBottom
case .flipFromLeft:
return .transitionFlipFromLeft
case .flipFromRight:
return .transitionFlipFromRight
case .flipFromTop:
return .transitionFlipFromTop
case .custom(_, let animationOptions, _, _):
return animationOptions
}
}
/// The animation options of the image transition.
public var animations: ((UIImageView, Image) -> Void) {
switch self {
case .custom(_, _, let animations, _):
return animations
default:
return { $0.image = $1 }
}
}
/// The completion closure associated with the image transition.
public var completion: ((Bool) -> Void)? {
switch self {
case .custom(_, _, _, let completion):
return completion
default:
return nil
}
}
}
// MARK: - Private - AssociatedKeys
private struct AssociatedKey {
static var imageDownloader = "af_UIImageView.ImageDownloader"
static var sharedImageDownloader = "af_UIImageView.SharedImageDownloader"
static var activeRequestReceipt = "af_UIImageView.ActiveRequestReceipt"
}
// MARK: - Associated Properties
/// The instance image downloader used to download all images. If this property is `nil`, the `UIImageView` will
/// fallback on the `af_sharedImageDownloader` for all downloads. The most common use case for needing to use a
/// custom instance image downloader is when images are behind different basic auth credentials.
public var af_imageDownloader: ImageDownloader? {
get {
return objc_getAssociatedObject(self, &AssociatedKey.imageDownloader) as? ImageDownloader
}
set(downloader) {
objc_setAssociatedObject(self, &AssociatedKey.imageDownloader, downloader, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
/// The shared image downloader used to download all images. By default, this is the default `ImageDownloader`
/// instance backed with an `AutoPurgingImageCache` which automatically evicts images from the cache when the memory
/// capacity is reached or memory warning notifications occur. The shared image downloader is only used if the
/// `af_imageDownloader` is `nil`.
public class var af_sharedImageDownloader: ImageDownloader {
get {
if let downloader = objc_getAssociatedObject(self, &AssociatedKey.sharedImageDownloader) as? ImageDownloader {
return downloader
} else {
return ImageDownloader.default
}
}
set {
objc_setAssociatedObject(self, &AssociatedKey.sharedImageDownloader, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
var af_activeRequestReceipt: RequestReceipt? {
get {
return objc_getAssociatedObject(self, &AssociatedKey.activeRequestReceipt) as? RequestReceipt
}
set {
objc_setAssociatedObject(self, &AssociatedKey.activeRequestReceipt, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
// MARK: - Image Download
/// Asynchronously downloads an image from the specified URL, applies the specified image filter to the downloaded
/// image and sets it once finished while executing the image transition.
///
/// If the image is cached locally, the image is set immediately. Otherwise the specified placehoder image will be
/// set immediately, and then the remote image will be set once the image request is finished.
///
/// The `completion` closure is called after the image download and filtering are complete, but before the start of
/// the image transition. Please note it is no longer the responsibility of the `completion` closure to set the
/// image. It will be set automatically. If you require a second notification after the image transition completes,
/// use a `.Custom` image transition with a `completion` closure. The `.Custom` `completion` closure is called when
/// the image transition is finished.
///
/// - parameter url: The URL used for the image request.
/// - parameter placeholderImage: The image to be set initially until the image request finished. If
/// `nil`, the image view will not change its image until the image
/// request finishes. Defaults to `nil`.
/// - parameter filter: The image filter applied to the image after the image request is
/// finished. Defaults to `nil`.
/// - parameter progress: The closure to be executed periodically during the lifecycle of the
/// request. Defaults to `nil`.
/// - parameter progressQueue: The dispatch queue to call the progress closure on. Defaults to the
/// main queue.
/// - parameter imageTransition: The image transition animation applied to the image when set.
/// Defaults to `.None`.
/// - parameter runImageTransitionIfCached: Whether to run the image transition if the image is cached. Defaults
/// to `false`.
/// - parameter completion: A closure to be executed when the image request finishes. The closure
/// has no return value and takes three arguments: the original request,
/// the response from the server and the result containing either the
/// image or the error that occurred. If the image was returned from the
/// image cache, the response will be `nil`. Defaults to `nil`.
public func af_setImage(
withURL url: URL,
placeholderImage: UIImage? = nil,
filter: ImageFilter? = nil,
progress: ImageDownloader.ProgressHandler? = nil,
progressQueue: DispatchQueue = DispatchQueue.main,
imageTransition: ImageTransition = .noTransition,
runImageTransitionIfCached: Bool = false,
completion: ((DataResponse<UIImage>) -> Void)? = nil)
{
af_setImage(
withURLRequest: urlRequest(with: url),
placeholderImage: placeholderImage,
filter: filter,
progress: progress,
progressQueue: progressQueue,
imageTransition: imageTransition,
runImageTransitionIfCached: runImageTransitionIfCached,
completion: completion
)
}
/// Asynchronously downloads an image from the specified URL Request, applies the specified image filter to the downloaded
/// image and sets it once finished while executing the image transition.
///
/// If the image is cached locally, the image is set immediately. Otherwise the specified placehoder image will be
/// set immediately, and then the remote image will be set once the image request is finished.
///
/// The `completion` closure is called after the image download and filtering are complete, but before the start of
/// the image transition. Please note it is no longer the responsibility of the `completion` closure to set the
/// image. It will be set automatically. If you require a second notification after the image transition completes,
/// use a `.Custom` image transition with a `completion` closure. The `.Custom` `completion` closure is called when
/// the image transition is finished.
///
/// - parameter urlRequest: The URL request.
/// - parameter placeholderImage: The image to be set initially until the image request finished. If
/// `nil`, the image view will not change its image until the image
/// request finishes. Defaults to `nil`.
/// - parameter filter: The image filter applied to the image after the image request is
/// finished. Defaults to `nil`.
/// - parameter progress: The closure to be executed periodically during the lifecycle of the
/// request. Defaults to `nil`.
/// - parameter progressQueue: The dispatch queue to call the progress closure on. Defaults to the
/// main queue.
/// - parameter imageTransition: The image transition animation applied to the image when set.
/// Defaults to `.None`.
/// - parameter runImageTransitionIfCached: Whether to run the image transition if the image is cached. Defaults
/// to `false`.
/// - parameter completion: A closure to be executed when the image request finishes. The closure
/// has no return value and takes three arguments: the original request,
/// the response from the server and the result containing either the
/// image or the error that occurred. If the image was returned from the
/// image cache, the response will be `nil`. Defaults to `nil`.
public func af_setImage(
withURLRequest urlRequest: URLRequestConvertible,
placeholderImage: UIImage? = nil,
filter: ImageFilter? = nil,
progress: ImageDownloader.ProgressHandler? = nil,
progressQueue: DispatchQueue = DispatchQueue.main,
imageTransition: ImageTransition = .noTransition,
runImageTransitionIfCached: Bool = false,
completion: ((DataResponse<UIImage>) -> Void)? = nil)
{
guard !isURLRequestURLEqualToActiveRequestURL(urlRequest) else {
let error = AFIError.requestCancelled
let response = DataResponse<UIImage>(request: nil, response: nil, data: nil, result: .failure(error))
completion?(response)
return
}
af_cancelImageRequest()
let imageDownloader = af_imageDownloader ?? UIImageView.af_sharedImageDownloader
let imageCache = imageDownloader.imageCache
// Use the image from the image cache if it exists
if
let request = urlRequest.urlRequest,
let image = imageCache?.image(for: request, withIdentifier: filter?.identifier)
{
let response = DataResponse<UIImage>(request: request, response: nil, data: nil, result: .success(image))
if runImageTransitionIfCached {
let tinyDelay = DispatchTime.now() + Double(Int64(0.001 * Float(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)
// Need to let the runloop cycle for the placeholder image to take affect
DispatchQueue.main.asyncAfter(deadline: tinyDelay) {
self.run(imageTransition, with: image)
completion?(response)
}
} else {
self.image = image
completion?(response)
}
return
}
// Set the placeholder since we're going to have to download
if let placeholderImage = placeholderImage { self.image = placeholderImage }
// Generate a unique download id to check whether the active request has changed while downloading
let downloadID = UUID().uuidString
// Download the image, then run the image transition or completion handler
let requestReceipt = imageDownloader.download(
urlRequest,
receiptID: downloadID,
filter: filter,
progress: progress,
progressQueue: progressQueue,
completion: { [weak self] response in
guard
let strongSelf = self,
strongSelf.isURLRequestURLEqualToActiveRequestURL(response.request) &&
strongSelf.af_activeRequestReceipt?.receiptID == downloadID
else {
completion?(response)
return
}
if let image = response.result.value {
strongSelf.run(imageTransition, with: image)
}
strongSelf.af_activeRequestReceipt = nil
completion?(response)
}
)
af_activeRequestReceipt = requestReceipt
}
// MARK: - Image Download Cancellation
/// Cancels the active download request, if one exists.
public func af_cancelImageRequest() {
guard let activeRequestReceipt = af_activeRequestReceipt else { return }
let imageDownloader = af_imageDownloader ?? UIImageView.af_sharedImageDownloader
imageDownloader.cancelRequest(with: activeRequestReceipt)
af_activeRequestReceipt = nil
}
// MARK: - Image Transition
/// Runs the image transition on the image view with the specified image.
///
/// - parameter imageTransition: The image transition to ran on the image view.
/// - parameter image: The image to use for the image transition.
public func run(_ imageTransition: ImageTransition, with image: Image) {
UIView.transition(
with: self,
duration: imageTransition.duration,
options: imageTransition.animationOptions,
animations: { imageTransition.animations(self, image) },
completion: imageTransition.completion
)
}
// MARK: - Private - URL Request Helper Methods
private func urlRequest(with url: URL) -> URLRequest {
var urlRequest = URLRequest(url: url)
for mimeType in DataRequest.acceptableImageContentTypes {
urlRequest.addValue(mimeType, forHTTPHeaderField: "Accept")
}
return urlRequest
}
private func isURLRequestURLEqualToActiveRequestURL(_ urlRequest: URLRequestConvertible?) -> Bool {
if
let currentRequestURL = af_activeRequestReceipt?.request.task?.originalRequest?.url,
let requestURL = urlRequest?.urlRequest?.url,
currentRequestURL == requestURL
{
return true
}
return false
}
}
#endif

11
Pods/Manifest.lock generated
View File

@@ -1,19 +1,20 @@
PODS:
- 1PasswordExtension (1.8.4)
- Alamofire (4.4.0)
- AlamofireImage (3.2.0):
- Alamofire (~> 4.1)
- IGListKit (2.1.0):
- IGListKit/Default (= 2.1.0)
- IGListKit/Default (2.1.0):
- IGListKit/Diffing
- IGListKit/Diffing (2.1.0)
- SDWebImage (4.0.0):
- SDWebImage/Core (= 4.0.0)
- SDWebImage/Core (4.0.0)
DEPENDENCIES:
- 1PasswordExtension (~> 1.8.4)
- Alamofire (~> 4.4.0)
- AlamofireImage (~> 3.2.0)
- IGListKit (from `https://github.com/Instagram/IGListKit.git`, branch `master`)
- SDWebImage (~> 4.0.0)
EXTERNAL SOURCES:
IGListKit:
@@ -28,9 +29,9 @@ CHECKOUT OPTIONS:
SPEC CHECKSUMS:
1PasswordExtension: e775a29310c65851e5a6cec1afc349ab4e334e47
Alamofire: dc44b1600b800eb63da6a19039a0083d62a6a62d
AlamofireImage: 157ed682cc81d3b9db4fb90c1f12180ac552d93b
IGListKit: b826c68ef7a4ae1626c09d4d3e1ea7a169e6c36e
SDWebImage: 76a6348bdc74eb5a55dd08a091ef298e56b55e41
PODFILE CHECKSUM: 456dae9d5a37ca50b70b098e5b2948e8b9fd029a
PODFILE CHECKSUM: 1c9f99d229a4ebba30490862f081749fbf54bc9a
COCOAPODS: 1.2.0

File diff suppressed because it is too large Load Diff

View File

@@ -1,15 +1,15 @@
Copyright (c) 2015-2016 Alamofire Software Foundation (http://alamofire.org/)
Copyright (c) 2016 Olivier Poitrey rs@dailymotion.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -17,3 +17,4 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

215
Pods/SDWebImage/README.md generated Normal file
View File

@@ -0,0 +1,215 @@
<p align="center" >
<img src="SDWebImage_logo.png" title="SDWebImage logo" float=left>
</p>
[![Build Status](http://img.shields.io/travis/rs/SDWebImage/master.svg?style=flat)](https://travis-ci.org/rs/SDWebImage)
[![Pod Version](http://img.shields.io/cocoapods/v/SDWebImage.svg?style=flat)](http://cocoadocs.org/docsets/SDWebImage/)
[![Pod Platform](http://img.shields.io/cocoapods/p/SDWebImage.svg?style=flat)](http://cocoadocs.org/docsets/SDWebImage/)
[![Pod License](http://img.shields.io/cocoapods/l/SDWebImage.svg?style=flat)](https://www.apache.org/licenses/LICENSE-2.0.html)
[![Dependency Status](https://www.versioneye.com/objective-c/sdwebimage/badge.svg?style=flat)](https://www.versioneye.com/objective-c/sdwebimage)
[![Reference Status](https://www.versioneye.com/objective-c/sdwebimage/reference_badge.svg?style=flat)](https://www.versioneye.com/objective-c/sdwebimage/references)
[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/rs/SDWebImage)
[![codecov](https://codecov.io/gh/rs/SDWebImage/branch/master/graph/badge.svg)](https://codecov.io/gh/rs/SDWebImage)
This library provides an async image downloader with cache support. For convenience, we added categories for UI elements like `UIImageView`, `UIButton`, `MKAnnotationView`.
## Features
- [x] Categories for `UIImageView`, `UIButton`, `MKAnnotationView` adding web image and cache management
- [x] An asynchronous image downloader
- [x] An asynchronous memory + disk image caching with automatic cache expiration handling
- [x] A background image decompression
- [x] A guarantee that the same URL won't be downloaded several times
- [x] A guarantee that bogus URLs won't be retried again and again
- [x] A guarantee that main thread will never be blocked
- [x] Performances!
- [x] Use GCD and ARC
## Supported Image Formats
- Image formats supported by UIImage (JPEG, PNG, ...), including GIF
- WebP format, including animated WebP (use the `WebP` subspec)
## Requirements
- iOS 7.0 or later
- tvOS 9.0 or later
- watchOS 2.0 or later
- OS X 10.8 or later
- Xcode 7.3 or later
#### Backwards compatibility
- For iOS 5 and 6, use [any 3.x version up to 3.7.6](https://github.com/rs/SDWebImage/tree/3.7.6)
- For iOS < 5.0, please use the last [2.0 version](https://github.com/rs/SDWebImage/tree/2.0-compat).
## Getting Started
- Read this Readme doc
- Read the [How to use section](https://github.com/rs/SDWebImage#how-to-use)
- Read the [documentation @ CocoaDocs](http://cocoadocs.org/docsets/SDWebImage/)
- Read [How is SDWebImage better than X?](https://github.com/rs/SDWebImage/wiki/How-is-SDWebImage-better-than-X%3F)
- Try the example by downloading the project from Github or even easier using CocoaPods try `pod try SDWebImage`
- Get to the [installation steps](https://github.com/rs/SDWebImage#installation)
- Read the [SDWebImage 4.0 Migration Guide](Docs/SDWebImage-4.0-Migration-guide.md) to get an idea of the changes from 3.x to 4.x
## Who Uses It
- Find out [who uses SDWebImage](https://github.com/rs/SDWebImage/wiki/Who-Uses-SDWebImage) and add your app to the list.
## Communication
- If you **need help**, use [Stack Overflow](http://stackoverflow.com/questions/tagged/sdwebimage). (Tag 'sdwebimage')
- If you'd like to **ask a general question**, use [Stack Overflow](http://stackoverflow.com/questions/tagged/sdwebimage).
- If you **found a bug**, open an issue.
- If you **have a feature request**, open an issue.
- If you **want to contribute**, submit a pull request.
## Installation
## How To Use
```objective-c
Objective-C:
#import <SDWebImage/UIImageView+WebCache.h>
...
[imageView sd_setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"]
placeholderImage:[UIImage imageNamed:@"placeholder.png"]];
```
```swift
Swift:
@import SDWebImage
imageView.sd_setImage(with: URL(string: "http://www.domain.com/path/to/image.jpg"), placeholderImage: UIImage(named: "placeholder.png"))
```
- For details about how to use the library and clear examples, see [The detailed How to use](Docs/HowToUse.md)
## Animated Images (GIF) support
- Starting with the 4.0 version, we rely on [FLAnimatedImage](https://github.com/Flipboard/FLAnimatedImage) to take care of our animated images.
- To use it, simply make sure you use `FLAnimatedImageView` instead of `UIImageView`.
- **Note**: there is a backwards compatible feature, so if you are still trying to load a GIF into a `UIImageView`, it will only show the 1st frame as a static image.
- **Important**: FLAnimatedImage only works on the iOS platform, so for all the other platforms (OS X, tvOS, watchOS) we will fallback to the backwards compatibility feature described above
Common Problems
---------------
### Using dynamic image size with UITableViewCell
UITableView determines the size of the image by the first image set for a cell. If your remote images
don't have the same size as your placeholder image, you may experience strange anamorphic scaling issue.
The following article gives a way to workaround this issue:
[http://www.wrichards.com/blog/2011/11/sdwebimage-fixed-width-cell-images/](http://www.wrichards.com/blog/2011/11/sdwebimage-fixed-width-cell-images/)
### Handle image refresh
SDWebImage does very aggressive caching by default. It ignores all kind of caching control header returned by the HTTP server and cache the returned images with no time restriction. It implies your images URLs are static URLs pointing to images that never change. If the pointed image happen to change, some parts of the URL should change accordingly.
If you don't control the image server you're using, you may not be able to change the URL when its content is updated. This is the case for Facebook avatar URLs for instance. In such case, you may use the `SDWebImageRefreshCached` flag. This will slightly degrade the performance but will respect the HTTP caching control headers:
``` objective-c
[imageView sd_setImageWithURL:[NSURL URLWithString:@"https://graph.facebook.com/olivier.poitrey/picture"]
placeholderImage:[UIImage imageNamed:@"avatar-placeholder.png"]
options:SDWebImageRefreshCached];
```
### Add a progress indicator
Add these before you call ```sd_setImageWithURL```
``` objective-c
[imageView setShowActivityIndicatorView:YES];
[imageView setIndicatorStyle:UIActivityIndicatorViewStyleGray];
```
``` swift
imageView.setShowActivityIndicatorView(true)
imageView.setIndicatorStyle(.Gray)
```
Installation
------------
There are three ways to use SDWebImage in your project:
- using CocoaPods
- using Carthage
- by cloning the project into your repository
### Installation with CocoaPods
[CocoaPods](http://cocoapods.org/) is a dependency manager for Objective-C, which automates and simplifies the process of using 3rd-party libraries in your projects. See the [Get Started](http://cocoapods.org/#get_started) section for more details.
#### Podfile
```
platform :ios, '7.0'
pod 'SDWebImage', '~>3.8'
```
If you are using Swift, be sure to add `use_frameworks!` and set your target to iOS 8+:
```
platform :ios, '8.0'
use_frameworks!
```
#### Subspecs
There are 3 subspecs available now: `Core`, `MapKit` and `WebP` (this means you can install only some of the SDWebImage modules. By default, you get just `Core`, so if you need `WebP`, you need to specify it).
Podfile example:
```
pod 'SDWebImage/WebP'
```
### Installation with Carthage (iOS 8+)
[Carthage](https://github.com/Carthage/Carthage) is a lightweight dependency manager for Swift and Objective-C. It leverages CocoaTouch modules and is less invasive than CocoaPods.
To install with carthage, follow the instruction on [Carthage](https://github.com/Carthage/Carthage)
#### Cartfile
```
github "rs/SDWebImage"
```
### Installation by cloning the repository
- see [Manual install](Docs/ManualInstallation.md)
### Import headers in your source files
In the source files where you need to use the library, import the header file:
```objective-c
#import <SDWebImage/UIImageView+WebCache.h>
```
### Build Project
At this point your workspace should build without error. If you are having problem, post to the Issue and the
community can help you solve it.
## Author
- [Olivier Poitrey](https://github.com/rs)
## Collaborators
- [Konstantinos K.](https://github.com/mythodeia)
- [Bogdan Poplauschi](https://github.com/bpoplauschi)
## Licenses
All source code is licensed under the [MIT License](https://raw.github.com/rs/SDWebImage/master/LICENSE).
## Architecture
<p align="center" >
<img src="Docs/SDWebImageClassDiagram.png" title="SDWebImage class diagram">
</p>
<p align="center" >
<img src="Docs/SDWebImageSequenceDiagram.png" title="SDWebImage sequence diagram">
</p>

View File

@@ -0,0 +1,33 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
* (c) Fabrice Aneche
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import <Foundation/Foundation.h>
#import "SDWebImageCompat.h"
typedef NS_ENUM(NSInteger, SDImageFormat) {
SDImageFormatUndefined = -1,
SDImageFormatJPEG = 0,
SDImageFormatPNG,
SDImageFormatGIF,
SDImageFormatTIFF,
SDImageFormatWebP
};
@interface NSData (ImageContentType)
/**
* Return image format
*
* @param data the input image data
*
* @return the image format as `SDImageFormat` (enum)
*/
+ (SDImageFormat)sd_imageFormatForImageData:(nullable NSData *)data;
@end

View File

@@ -0,0 +1,46 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
* (c) Fabrice Aneche
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "NSData+ImageContentType.h"
@implementation NSData (ImageContentType)
+ (SDImageFormat)sd_imageFormatForImageData:(nullable NSData *)data {
if (!data) {
return SDImageFormatUndefined;
}
uint8_t c;
[data getBytes:&c length:1];
switch (c) {
case 0xFF:
return SDImageFormatJPEG;
case 0x89:
return SDImageFormatPNG;
case 0x47:
return SDImageFormatGIF;
case 0x49:
case 0x4D:
return SDImageFormatTIFF;
case 0x52:
// R as RIFF for WEBP
if (data.length < 12) {
return SDImageFormatUndefined;
}
NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(0, 12)] encoding:NSASCIIStringEncoding];
if ([testString hasPrefix:@"RIFF"] && [testString hasSuffix:@"WEBP"]) {
return SDImageFormatWebP;
}
}
return SDImageFormatUndefined;
}
@end

View File

@@ -0,0 +1,23 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "SDWebImageCompat.h"
#if SD_MAC
#import <Cocoa/Cocoa.h>
@interface NSImage (WebCache)
- (CGImageRef)CGImage;
- (NSArray<NSImage *> *)images;
- (BOOL)isGIF;
@end
#endif

View File

@@ -0,0 +1,32 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "NSImage+WebCache.h"
#if SD_MAC
@implementation NSImage (WebCache)
- (CGImageRef)CGImage {
NSRect imageRect = NSMakeRect(0, 0, self.size.width, self.size.height);
CGImageRef cgImage = [self CGImageForProposedRect:&imageRect context:NULL hints:nil];
return cgImage;
}
- (NSArray<NSImage *> *)images {
return nil;
}
- (BOOL)isGIF {
return NO;
}
@end
#endif

267
Pods/SDWebImage/SDWebImage/SDImageCache.h generated Normal file
View File

@@ -0,0 +1,267 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import <Foundation/Foundation.h>
#import "SDWebImageCompat.h"
@class SDImageCacheConfig;
typedef NS_ENUM(NSInteger, SDImageCacheType) {
/**
* The image wasn't available the SDWebImage caches, but was downloaded from the web.
*/
SDImageCacheTypeNone,
/**
* The image was obtained from the disk cache.
*/
SDImageCacheTypeDisk,
/**
* The image was obtained from the memory cache.
*/
SDImageCacheTypeMemory
};
typedef void(^SDCacheQueryCompletedBlock)(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType);
typedef void(^SDWebImageCheckCacheCompletionBlock)(BOOL isInCache);
typedef void(^SDWebImageCalculateSizeBlock)(NSUInteger fileCount, NSUInteger totalSize);
/**
* SDImageCache maintains a memory cache and an optional disk cache. Disk cache write operations are performed
* asynchronous so it doesnt add unnecessary latency to the UI.
*/
@interface SDImageCache : NSObject
#pragma mark - Properties
/**
* Cache Config object - storing all kind of settings
*/
@property (nonatomic, nonnull, readonly) SDImageCacheConfig *config;
/**
* The maximum "total cost" of the in-memory image cache. The cost function is the number of pixels held in memory.
*/
@property (assign, nonatomic) NSUInteger maxMemoryCost;
/**
* The maximum number of objects the cache should hold.
*/
@property (assign, nonatomic) NSUInteger maxMemoryCountLimit;
#pragma mark - Singleton and initialization
/**
* Returns global shared cache instance
*
* @return SDImageCache global instance
*/
+ (nonnull instancetype)sharedImageCache;
/**
* Init a new cache store with a specific namespace
*
* @param ns The namespace to use for this cache store
*/
- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns;
/**
* Init a new cache store with a specific namespace and directory
*
* @param ns The namespace to use for this cache store
* @param directory Directory to cache disk images in
*/
- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns
diskCacheDirectory:(nonnull NSString *)directory NS_DESIGNATED_INITIALIZER;
#pragma mark - Cache paths
- (nullable NSString *)makeDiskCachePath:(nonnull NSString*)fullNamespace;
/**
* Add a read-only cache path to search for images pre-cached by SDImageCache
* Useful if you want to bundle pre-loaded images with your app
*
* @param path The path to use for this read-only cache path
*/
- (void)addReadOnlyCachePath:(nonnull NSString *)path;
#pragma mark - Store Ops
/**
* Asynchronously store an image into memory and disk cache at the given key.
*
* @param image The image to store
* @param key The unique image cache key, usually it's image absolute URL
* @param completionBlock A block executed after the operation is finished
*/
- (void)storeImage:(nullable UIImage *)image
forKey:(nullable NSString *)key
completion:(nullable SDWebImageNoParamsBlock)completionBlock;
/**
* Asynchronously store an image into memory and disk cache at the given key.
*
* @param image The image to store
* @param key The unique image cache key, usually it's image absolute URL
* @param toDisk Store the image to disk cache if YES
* @param completionBlock A block executed after the operation is finished
*/
- (void)storeImage:(nullable UIImage *)image
forKey:(nullable NSString *)key
toDisk:(BOOL)toDisk
completion:(nullable SDWebImageNoParamsBlock)completionBlock;
/**
* Asynchronously store an image into memory and disk cache at the given key.
*
* @param image The image to store
* @param imageData The image data as returned by the server, this representation will be used for disk storage
* instead of converting the given image object into a storable/compressed image format in order
* to save quality and CPU
* @param key The unique image cache key, usually it's image absolute URL
* @param toDisk Store the image to disk cache if YES
* @param completionBlock A block executed after the operation is finished
*/
- (void)storeImage:(nullable UIImage *)image
imageData:(nullable NSData *)imageData
forKey:(nullable NSString *)key
toDisk:(BOOL)toDisk
completion:(nullable SDWebImageNoParamsBlock)completionBlock;
/**
* Synchronously store image NSData into disk cache at the given key.
*
* @warning This method is synchronous, make sure to call it from the ioQueue
*
* @param imageData The image data to store
* @param key The unique image cache key, usually it's image absolute URL
*/
- (void)storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key;
#pragma mark - Query and Retrieve Ops
/**
* Async check if image exists in disk cache already (does not load the image)
*
* @param key the key describing the url
* @param completionBlock the block to be executed when the check is done.
* @note the completion block will be always executed on the main queue
*/
- (void)diskImageExistsWithKey:(nullable NSString *)key completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock;
/**
* Operation that queries the cache asynchronously and call the completion when done.
*
* @param key The unique key used to store the wanted image
* @param doneBlock The completion block. Will not get called if the operation is cancelled
*
* @return a NSOperation instance containing the cache op
*/
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock;
/**
* Query the memory cache synchronously.
*
* @param key The unique key used to store the image
*/
- (nullable UIImage *)imageFromMemoryCacheForKey:(nullable NSString *)key;
/**
* Query the disk cache synchronously.
*
* @param key The unique key used to store the image
*/
- (nullable UIImage *)imageFromDiskCacheForKey:(nullable NSString *)key;
/**
* Query the cache (memory and or disk) synchronously after checking the memory cache.
*
* @param key The unique key used to store the image
*/
- (nullable UIImage *)imageFromCacheForKey:(nullable NSString *)key;
#pragma mark - Remove Ops
/**
* Remove the image from memory and disk cache asynchronously
*
* @param key The unique image cache key
* @param completion A block that should be executed after the image has been removed (optional)
*/
- (void)removeImageForKey:(nullable NSString *)key withCompletion:(nullable SDWebImageNoParamsBlock)completion;
/**
* Remove the image from memory and optionally disk cache asynchronously
*
* @param key The unique image cache key
* @param fromDisk Also remove cache entry from disk if YES
* @param completion A block that should be executed after the image has been removed (optional)
*/
- (void)removeImageForKey:(nullable NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(nullable SDWebImageNoParamsBlock)completion;
#pragma mark - Cache clean Ops
/**
* Clear all memory cached images
*/
- (void)clearMemory;
/**
* Async clear all disk cached images. Non-blocking method - returns immediately.
* @param completion A block that should be executed after cache expiration completes (optional)
*/
- (void)clearDiskOnCompletion:(nullable SDWebImageNoParamsBlock)completion;
/**
* Async remove all expired cached image from disk. Non-blocking method - returns immediately.
* @param completionBlock A block that should be executed after cache expiration completes (optional)
*/
- (void)deleteOldFilesWithCompletionBlock:(nullable SDWebImageNoParamsBlock)completionBlock;
#pragma mark - Cache Info
/**
* Get the size used by the disk cache
*/
- (NSUInteger)getSize;
/**
* Get the number of images in the disk cache
*/
- (NSUInteger)getDiskCount;
/**
* Asynchronously calculate the disk cache's size.
*/
- (void)calculateSizeWithCompletionBlock:(nullable SDWebImageCalculateSizeBlock)completionBlock;
#pragma mark - Cache Paths
/**
* Get the cache path for a certain key (needs the cache path root folder)
*
* @param key the key (can be obtained from url using cacheKeyForURL)
* @param path the cache path root folder
*
* @return the cache path
*/
- (nullable NSString *)cachePathForKey:(nullable NSString *)key inPath:(nonnull NSString *)path;
/**
* Get the default cache path for a certain key
*
* @param key the key (can be obtained from url using cacheKeyForURL)
*
* @return the default cache path
*/
- (nullable NSString *)defaultCachePathForKey:(nullable NSString *)key;
@end

639
Pods/SDWebImage/SDWebImage/SDImageCache.m generated Normal file
View File

@@ -0,0 +1,639 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "SDImageCache.h"
#import "SDWebImageDecoder.h"
#import "UIImage+MultiFormat.h"
#import <CommonCrypto/CommonDigest.h>
#import "UIImage+GIF.h"
#import "NSData+ImageContentType.h"
#import "NSImage+WebCache.h"
#import "SDImageCacheConfig.h"
// See https://github.com/rs/SDWebImage/pull/1141 for discussion
@interface AutoPurgeCache : NSCache
@end
@implementation AutoPurgeCache
- (nonnull instancetype)init {
self = [super init];
if (self) {
#if SD_UIKIT
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(removeAllObjects) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
}
return self;
}
- (void)dealloc {
#if SD_UIKIT
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
}
@end
FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
#if SD_MAC
return image.size.height * image.size.width;
#elif SD_UIKIT || SD_WATCH
return image.size.height * image.size.width * image.scale * image.scale;
#endif
}
@interface SDImageCache ()
#pragma mark - Properties
@property (strong, nonatomic, nonnull) NSCache *memCache;
@property (strong, nonatomic, nonnull) NSString *diskCachePath;
@property (strong, nonatomic, nullable) NSMutableArray<NSString *> *customPaths;
@property (SDDispatchQueueSetterSementics, nonatomic, nullable) dispatch_queue_t ioQueue;
@end
@implementation SDImageCache {
NSFileManager *_fileManager;
}
#pragma mark - Singleton, init, dealloc
+ (nonnull instancetype)sharedImageCache {
static dispatch_once_t once;
static id instance;
dispatch_once(&once, ^{
instance = [self new];
});
return instance;
}
- (instancetype)init {
return [self initWithNamespace:@"default"];
}
- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns {
NSString *path = [self makeDiskCachePath:ns];
return [self initWithNamespace:ns diskCacheDirectory:path];
}
- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns
diskCacheDirectory:(nonnull NSString *)directory {
if ((self = [super init])) {
NSString *fullNamespace = [@"com.hackemist.SDWebImageCache." stringByAppendingString:ns];
// Create IO serial queue
_ioQueue = dispatch_queue_create("com.hackemist.SDWebImageCache", DISPATCH_QUEUE_SERIAL);
_config = [[SDImageCacheConfig alloc] init];
// Init the memory cache
_memCache = [[AutoPurgeCache alloc] init];
_memCache.name = fullNamespace;
// Init the disk cache
if (directory != nil) {
_diskCachePath = [directory stringByAppendingPathComponent:fullNamespace];
} else {
NSString *path = [self makeDiskCachePath:ns];
_diskCachePath = path;
}
dispatch_sync(_ioQueue, ^{
_fileManager = [NSFileManager new];
});
#if SD_UIKIT
// Subscribe to app events
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(clearMemory)
name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(deleteOldFiles)
name:UIApplicationWillTerminateNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(backgroundDeleteOldFiles)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
#endif
}
return self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
SDDispatchQueueRelease(_ioQueue);
}
- (void)checkIfQueueIsIOQueue {
const char *currentQueueLabel = dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL);
const char *ioQueueLabel = dispatch_queue_get_label(self.ioQueue);
if (strcmp(currentQueueLabel, ioQueueLabel) != 0) {
NSLog(@"This method should be called from the ioQueue");
}
}
#pragma mark - Cache paths
- (void)addReadOnlyCachePath:(nonnull NSString *)path {
if (!self.customPaths) {
self.customPaths = [NSMutableArray new];
}
if (![self.customPaths containsObject:path]) {
[self.customPaths addObject:path];
}
}
- (nullable NSString *)cachePathForKey:(nullable NSString *)key inPath:(nonnull NSString *)path {
NSString *filename = [self cachedFileNameForKey:key];
return [path stringByAppendingPathComponent:filename];
}
- (nullable NSString *)defaultCachePathForKey:(nullable NSString *)key {
return [self cachePathForKey:key inPath:self.diskCachePath];
}
- (nullable NSString *)cachedFileNameForKey:(nullable NSString *)key {
const char *str = key.UTF8String;
if (str == NULL) {
str = "";
}
unsigned char r[CC_MD5_DIGEST_LENGTH];
CC_MD5(str, (CC_LONG)strlen(str), r);
NSString *filename = [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%@",
r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10],
r[11], r[12], r[13], r[14], r[15], [key.pathExtension isEqualToString:@""] ? @"" : [NSString stringWithFormat:@".%@", key.pathExtension]];
return filename;
}
- (nullable NSString *)makeDiskCachePath:(nonnull NSString*)fullNamespace {
NSArray<NSString *> *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
return [paths[0] stringByAppendingPathComponent:fullNamespace];
}
#pragma mark - Store Ops
- (void)storeImage:(nullable UIImage *)image
forKey:(nullable NSString *)key
completion:(nullable SDWebImageNoParamsBlock)completionBlock {
[self storeImage:image imageData:nil forKey:key toDisk:YES completion:completionBlock];
}
- (void)storeImage:(nullable UIImage *)image
forKey:(nullable NSString *)key
toDisk:(BOOL)toDisk
completion:(nullable SDWebImageNoParamsBlock)completionBlock {
[self storeImage:image imageData:nil forKey:key toDisk:toDisk completion:completionBlock];
}
- (void)storeImage:(nullable UIImage *)image
imageData:(nullable NSData *)imageData
forKey:(nullable NSString *)key
toDisk:(BOOL)toDisk
completion:(nullable SDWebImageNoParamsBlock)completionBlock {
if (!image || !key) {
if (completionBlock) {
completionBlock();
}
return;
}
// if memory cache is enabled
if (self.config.shouldCacheImagesInMemory) {
NSUInteger cost = SDCacheCostForImage(image);
[self.memCache setObject:image forKey:key cost:cost];
}
if (toDisk) {
dispatch_async(self.ioQueue, ^{
NSData *data = imageData;
if (!data && image) {
SDImageFormat imageFormatFromData = [NSData sd_imageFormatForImageData:data];
data = [image sd_imageDataAsFormat:imageFormatFromData];
}
[self storeImageDataToDisk:data forKey:key];
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock();
});
}
});
} else {
if (completionBlock) {
completionBlock();
}
}
}
- (void)storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key {
if (!imageData || !key) {
return;
}
[self checkIfQueueIsIOQueue];
if (![_fileManager fileExistsAtPath:_diskCachePath]) {
[_fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];
}
// get cache Path for image key
NSString *cachePathForKey = [self defaultCachePathForKey:key];
// transform to NSUrl
NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey];
[_fileManager createFileAtPath:cachePathForKey contents:imageData attributes:nil];
// disable iCloud backup
if (self.config.shouldDisableiCloud) {
[fileURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil];
}
}
#pragma mark - Query and Retrieve Ops
- (void)diskImageExistsWithKey:(nullable NSString *)key completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock {
dispatch_async(_ioQueue, ^{
BOOL exists = [_fileManager fileExistsAtPath:[self defaultCachePathForKey:key]];
// fallback because of https://github.com/rs/SDWebImage/pull/976 that added the extension to the disk file name
// checking the key with and without the extension
if (!exists) {
exists = [_fileManager fileExistsAtPath:[self defaultCachePathForKey:key].stringByDeletingPathExtension];
}
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock(exists);
});
}
});
}
- (nullable UIImage *)imageFromMemoryCacheForKey:(nullable NSString *)key {
return [self.memCache objectForKey:key];
}
- (nullable UIImage *)imageFromDiskCacheForKey:(nullable NSString *)key {
UIImage *diskImage = [self diskImageForKey:key];
if (diskImage && self.config.shouldCacheImagesInMemory) {
NSUInteger cost = SDCacheCostForImage(diskImage);
[self.memCache setObject:diskImage forKey:key cost:cost];
}
return diskImage;
}
- (nullable UIImage *)imageFromCacheForKey:(nullable NSString *)key {
// First check the in-memory cache...
UIImage *image = [self imageFromMemoryCacheForKey:key];
if (image) {
return image;
}
// Second check the disk cache...
image = [self imageFromDiskCacheForKey:key];
return image;
}
- (nullable NSData *)diskImageDataBySearchingAllPathsForKey:(nullable NSString *)key {
NSString *defaultPath = [self defaultCachePathForKey:key];
NSData *data = [NSData dataWithContentsOfFile:defaultPath];
if (data) {
return data;
}
// fallback because of https://github.com/rs/SDWebImage/pull/976 that added the extension to the disk file name
// checking the key with and without the extension
data = [NSData dataWithContentsOfFile:defaultPath.stringByDeletingPathExtension];
if (data) {
return data;
}
NSArray<NSString *> *customPaths = [self.customPaths copy];
for (NSString *path in customPaths) {
NSString *filePath = [self cachePathForKey:key inPath:path];
NSData *imageData = [NSData dataWithContentsOfFile:filePath];
if (imageData) {
return imageData;
}
// fallback because of https://github.com/rs/SDWebImage/pull/976 that added the extension to the disk file name
// checking the key with and without the extension
imageData = [NSData dataWithContentsOfFile:filePath.stringByDeletingPathExtension];
if (imageData) {
return imageData;
}
}
return nil;
}
- (nullable UIImage *)diskImageForKey:(nullable NSString *)key {
NSData *data = [self diskImageDataBySearchingAllPathsForKey:key];
if (data) {
UIImage *image = [UIImage sd_imageWithData:data];
image = [self scaledImageForKey:key image:image];
if (self.config.shouldDecompressImages) {
image = [UIImage decodedImageWithImage:image];
}
return image;
}
else {
return nil;
}
}
- (nullable UIImage *)scaledImageForKey:(nullable NSString *)key image:(nullable UIImage *)image {
return SDScaledImageForKey(key, image);
}
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock {
if (!key) {
if (doneBlock) {
doneBlock(nil, nil, SDImageCacheTypeNone);
}
return nil;
}
// First check the in-memory cache...
UIImage *image = [self imageFromMemoryCacheForKey:key];
if (image) {
NSData *diskData = nil;
if ([image isGIF]) {
diskData = [self diskImageDataBySearchingAllPathsForKey:key];
}
if (doneBlock) {
doneBlock(image, diskData, SDImageCacheTypeMemory);
}
return nil;
}
NSOperation *operation = [NSOperation new];
dispatch_async(self.ioQueue, ^{
if (operation.isCancelled) {
// do not call the completion if cancelled
return;
}
@autoreleasepool {
NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
UIImage *diskImage = [self diskImageForKey:key];
if (diskImage && self.config.shouldCacheImagesInMemory) {
NSUInteger cost = SDCacheCostForImage(diskImage);
[self.memCache setObject:diskImage forKey:key cost:cost];
}
if (doneBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
});
}
}
});
return operation;
}
#pragma mark - Remove Ops
- (void)removeImageForKey:(nullable NSString *)key withCompletion:(nullable SDWebImageNoParamsBlock)completion {
[self removeImageForKey:key fromDisk:YES withCompletion:completion];
}
- (void)removeImageForKey:(nullable NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(nullable SDWebImageNoParamsBlock)completion {
if (key == nil) {
return;
}
if (self.config.shouldCacheImagesInMemory) {
[self.memCache removeObjectForKey:key];
}
if (fromDisk) {
dispatch_async(self.ioQueue, ^{
[_fileManager removeItemAtPath:[self defaultCachePathForKey:key] error:nil];
if (completion) {
dispatch_async(dispatch_get_main_queue(), ^{
completion();
});
}
});
} else if (completion){
completion();
}
}
# pragma mark - Mem Cache settings
- (void)setMaxMemoryCost:(NSUInteger)maxMemoryCost {
self.memCache.totalCostLimit = maxMemoryCost;
}
- (NSUInteger)maxMemoryCost {
return self.memCache.totalCostLimit;
}
- (NSUInteger)maxMemoryCountLimit {
return self.memCache.countLimit;
}
- (void)setMaxMemoryCountLimit:(NSUInteger)maxCountLimit {
self.memCache.countLimit = maxCountLimit;
}
#pragma mark - Cache clean Ops
- (void)clearMemory {
[self.memCache removeAllObjects];
}
- (void)clearDiskOnCompletion:(nullable SDWebImageNoParamsBlock)completion {
dispatch_async(self.ioQueue, ^{
[_fileManager removeItemAtPath:self.diskCachePath error:nil];
[_fileManager createDirectoryAtPath:self.diskCachePath
withIntermediateDirectories:YES
attributes:nil
error:NULL];
if (completion) {
dispatch_async(dispatch_get_main_queue(), ^{
completion();
});
}
});
}
- (void)deleteOldFiles {
[self deleteOldFilesWithCompletionBlock:nil];
}
- (void)deleteOldFilesWithCompletionBlock:(nullable SDWebImageNoParamsBlock)completionBlock {
dispatch_async(self.ioQueue, ^{
NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES];
NSArray<NSString *> *resourceKeys = @[NSURLIsDirectoryKey, NSURLContentModificationDateKey, NSURLTotalFileAllocatedSizeKey];
// This enumerator prefetches useful properties for our cache files.
NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURL
includingPropertiesForKeys:resourceKeys
options:NSDirectoryEnumerationSkipsHiddenFiles
errorHandler:NULL];
NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-self.config.maxCacheAge];
NSMutableDictionary<NSURL *, NSDictionary<NSString *, id> *> *cacheFiles = [NSMutableDictionary dictionary];
NSUInteger currentCacheSize = 0;
// Enumerate all of the files in the cache directory. This loop has two purposes:
//
// 1. Removing files that are older than the expiration date.
// 2. Storing file attributes for the size-based cleanup pass.
NSMutableArray<NSURL *> *urlsToDelete = [[NSMutableArray alloc] init];
for (NSURL *fileURL in fileEnumerator) {
NSError *error;
NSDictionary<NSString *, id> *resourceValues = [fileURL resourceValuesForKeys:resourceKeys error:&error];
// Skip directories and errors.
if (error || !resourceValues || [resourceValues[NSURLIsDirectoryKey] boolValue]) {
continue;
}
// Remove files that are older than the expiration date;
NSDate *modificationDate = resourceValues[NSURLContentModificationDateKey];
if ([[modificationDate laterDate:expirationDate] isEqualToDate:expirationDate]) {
[urlsToDelete addObject:fileURL];
continue;
}
// Store a reference to this file and account for its total size.
NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
currentCacheSize += totalAllocatedSize.unsignedIntegerValue;
cacheFiles[fileURL] = resourceValues;
}
for (NSURL *fileURL in urlsToDelete) {
[_fileManager removeItemAtURL:fileURL error:nil];
}
// If our remaining disk cache exceeds a configured maximum size, perform a second
// size-based cleanup pass. We delete the oldest files first.
if (self.config.maxCacheSize > 0 && currentCacheSize > self.config.maxCacheSize) {
// Target half of our maximum cache size for this cleanup pass.
const NSUInteger desiredCacheSize = self.config.maxCacheSize / 2;
// Sort the remaining cache files by their last modification time (oldest first).
NSArray<NSURL *> *sortedFiles = [cacheFiles keysSortedByValueWithOptions:NSSortConcurrent
usingComparator:^NSComparisonResult(id obj1, id obj2) {
return [obj1[NSURLContentModificationDateKey] compare:obj2[NSURLContentModificationDateKey]];
}];
// Delete files until we fall below our desired cache size.
for (NSURL *fileURL in sortedFiles) {
if ([_fileManager removeItemAtURL:fileURL error:nil]) {
NSDictionary<NSString *, id> *resourceValues = cacheFiles[fileURL];
NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
currentCacheSize -= totalAllocatedSize.unsignedIntegerValue;
if (currentCacheSize < desiredCacheSize) {
break;
}
}
}
}
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock();
});
}
});
}
#if SD_UIKIT
- (void)backgroundDeleteOldFiles {
Class UIApplicationClass = NSClassFromString(@"UIApplication");
if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) {
return;
}
UIApplication *application = [UIApplication performSelector:@selector(sharedApplication)];
__block UIBackgroundTaskIdentifier bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
// Clean up any unfinished task business by marking where you
// stopped or ending the task outright.
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
// Start the long-running task and return immediately.
[self deleteOldFilesWithCompletionBlock:^{
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
}
#endif
#pragma mark - Cache Info
- (NSUInteger)getSize {
__block NSUInteger size = 0;
dispatch_sync(self.ioQueue, ^{
NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtPath:self.diskCachePath];
for (NSString *fileName in fileEnumerator) {
NSString *filePath = [self.diskCachePath stringByAppendingPathComponent:fileName];
NSDictionary<NSString *, id> *attrs = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];
size += [attrs fileSize];
}
});
return size;
}
- (NSUInteger)getDiskCount {
__block NSUInteger count = 0;
dispatch_sync(self.ioQueue, ^{
NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtPath:self.diskCachePath];
count = fileEnumerator.allObjects.count;
});
return count;
}
- (void)calculateSizeWithCompletionBlock:(nullable SDWebImageCalculateSizeBlock)completionBlock {
NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES];
dispatch_async(self.ioQueue, ^{
NSUInteger fileCount = 0;
NSUInteger totalSize = 0;
NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURL
includingPropertiesForKeys:@[NSFileSize]
options:NSDirectoryEnumerationSkipsHiddenFiles
errorHandler:NULL];
for (NSURL *fileURL in fileEnumerator) {
NSNumber *fileSize;
[fileURL getResourceValue:&fileSize forKey:NSURLFileSizeKey error:NULL];
totalSize += fileSize.unsignedIntegerValue;
fileCount += 1;
}
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock(fileCount, totalSize);
});
}
});
}
@end

View File

@@ -0,0 +1,40 @@
//
// SDImageCacheConfig.h
// SDWebImage
//
// Created by Bogdan on 09/09/16.
// Copyright © 2016 Dailymotion. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "SDWebImageCompat.h"
@interface SDImageCacheConfig : NSObject
/**
* Decompressing images that are downloaded and cached can improve performance but can consume lot of memory.
* Defaults to YES. Set this to NO if you are experiencing a crash due to excessive memory consumption.
*/
@property (assign, nonatomic) BOOL shouldDecompressImages;
/**
* disable iCloud backup [defaults to YES]
*/
@property (assign, nonatomic) BOOL shouldDisableiCloud;
/**
* use memory cache [defaults to YES]
*/
@property (assign, nonatomic) BOOL shouldCacheImagesInMemory;
/**
* The maximum length of time to keep an image in the cache, in seconds
*/
@property (assign, nonatomic) NSInteger maxCacheAge;
/**
* The maximum size of the cache, in bytes.
*/
@property (assign, nonatomic) NSUInteger maxCacheSize;
@end

View File

@@ -0,0 +1,26 @@
//
// SDImageCacheConfig.m
// SDWebImage
//
// Created by Bogdan on 09/09/16.
// Copyright © 2016 Dailymotion. All rights reserved.
//
#import "SDImageCacheConfig.h"
static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 1 week
@implementation SDImageCacheConfig
- (instancetype)init {
if (self = [super init]) {
_shouldDecompressImages = YES;
_shouldDisableiCloud = YES;
_shouldCacheImagesInMemory = YES;
_maxCacheAge = kDefaultCacheMaxCacheAge;
_maxCacheSize = 0;
}
return self;
}
@end

View File

@@ -0,0 +1,111 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
* (c) Jamie Pinkham
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import <TargetConditionals.h>
#ifdef __OBJC_GC__
#error SDWebImage does not support Objective-C Garbage Collection
#endif
// Apple's defines from TargetConditionals.h are a bit weird.
// Seems like TARGET_OS_MAC is always defined (on all platforms).
// To determine if we are running on OSX, we can only relly on TARGET_OS_IPHONE=0 and all the other platforms
#if !TARGET_OS_IPHONE && !TARGET_OS_IOS && !TARGET_OS_TV && !TARGET_OS_WATCH
#define SD_MAC 1
#else
#define SD_MAC 0
#endif
// iOS and tvOS are very similar, UIKit exists on both platforms
// Note: watchOS also has UIKit, but it's very limited
#if TARGET_OS_IOS || TARGET_OS_TV
#define SD_UIKIT 1
#else
#define SD_UIKIT 0
#endif
#if TARGET_OS_IOS
#define SD_IOS 1
#else
#define SD_IOS 0
#endif
#if TARGET_OS_TV
#define SD_TV 1
#else
#define SD_TV 0
#endif
#if TARGET_OS_WATCH
#define SD_WATCH 1
#else
#define SD_WATCH 0
#endif
#if SD_MAC
#import <AppKit/AppKit.h>
#ifndef UIImage
#define UIImage NSImage
#endif
#ifndef UIImageView
#define UIImageView NSImageView
#endif
#ifndef UIView
#define UIView NSView
#endif
#else
#if __IPHONE_OS_VERSION_MIN_REQUIRED != 20000 && __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_5_0
#error SDWebImage doesn't support Deployment Target version < 5.0
#endif
#if SD_UIKIT
#import <UIKit/UIKit.h>
#endif
#if SD_WATCH
#import <WatchKit/WatchKit.h>
#endif
#endif
#ifndef NS_ENUM
#define NS_ENUM(_type, _name) enum _name : _type _name; enum _name : _type
#endif
#ifndef NS_OPTIONS
#define NS_OPTIONS(_type, _name) enum _name : _type _name; enum _name : _type
#endif
#if OS_OBJECT_USE_OBJC
#undef SDDispatchQueueRelease
#undef SDDispatchQueueSetterSementics
#define SDDispatchQueueRelease(q)
#define SDDispatchQueueSetterSementics strong
#else
#undef SDDispatchQueueRelease
#undef SDDispatchQueueSetterSementics
#define SDDispatchQueueRelease(q) (dispatch_release(q))
#define SDDispatchQueueSetterSementics assign
#endif
extern UIImage *SDScaledImageForKey(NSString *key, UIImage *image);
typedef void(^SDWebImageNoParamsBlock)();
extern NSString *const SDWebImageErrorDomain;
#ifndef dispatch_main_async_safe
#define dispatch_main_async_safe(block)\
if (strcmp(dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL), dispatch_queue_get_label(dispatch_get_main_queue())) == 0) {\
block();\
} else {\
dispatch_async(dispatch_get_main_queue(), block);\
}
#endif
static int64_t kAsyncTestTimeout = 5;

View File

@@ -0,0 +1,59 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "SDWebImageCompat.h"
#if !__has_feature(objc_arc)
#error SDWebImage is ARC only. Either turn on ARC for the project or use -fobjc-arc flag
#endif
inline UIImage *SDScaledImageForKey(NSString * _Nullable key, UIImage * _Nullable image) {
if (!image) {
return nil;
}
#if SD_MAC
return image;
#elif SD_UIKIT || SD_WATCH
if ((image.images).count > 0) {
NSMutableArray<UIImage *> *scaledImages = [NSMutableArray array];
for (UIImage *tempImage in image.images) {
[scaledImages addObject:SDScaledImageForKey(key, tempImage)];
}
return [UIImage animatedImageWithImages:scaledImages duration:image.duration];
}
else {
#if SD_WATCH
if ([[WKInterfaceDevice currentDevice] respondsToSelector:@selector(screenScale)]) {
#elif SD_UIKIT
if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)]) {
#endif
CGFloat scale = 1;
if (key.length >= 8) {
NSRange range = [key rangeOfString:@"@2x."];
if (range.location != NSNotFound) {
scale = 2.0;
}
range = [key rangeOfString:@"@3x."];
if (range.location != NSNotFound) {
scale = 3.0;
}
}
UIImage *scaledImage = [[UIImage alloc] initWithCGImage:image.CGImage scale:scale orientation:image.imageOrientation];
image = scaledImage;
}
return image;
}
#endif
}
NSString *const SDWebImageErrorDomain = @"SDWebImageErrorDomain";

View File

@@ -0,0 +1,19 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
* (c) james <https://github.com/mystcolor>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import <Foundation/Foundation.h>
#import "SDWebImageCompat.h"
@interface UIImage (ForceDecode)
+ (nullable UIImage *)decodedImageWithImage:(nullable UIImage *)image;
+ (nullable UIImage *)decodedAndScaledDownImageWithImage:(nullable UIImage *)image;
@end

View File

@@ -0,0 +1,279 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
* (c) james <https://github.com/mystcolor>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "SDWebImageDecoder.h"
@implementation UIImage (ForceDecode)
#if SD_UIKIT || SD_WATCH
static const size_t kBytesPerPixel = 4;
static const size_t kBitsPerComponent = 8;
+ (nullable UIImage *)decodedImageWithImage:(nullable UIImage *)image {
if (![UIImage shouldDecodeImage:image]) {
return image;
}
// autorelease the bitmap context and all vars to help system to free memory when there are memory warning.
// on iOS7, do not forget to call [[SDImageCache sharedImageCache] clearMemory];
@autoreleasepool{
CGImageRef imageRef = image.CGImage;
CGColorSpaceRef colorspaceRef = [UIImage colorSpaceForImageRef:imageRef];
size_t width = CGImageGetWidth(imageRef);
size_t height = CGImageGetHeight(imageRef);
size_t bytesPerRow = kBytesPerPixel * width;
// kCGImageAlphaNone is not supported in CGBitmapContextCreate.
// Since the original image here has no alpha info, use kCGImageAlphaNoneSkipLast
// to create bitmap graphics contexts without alpha info.
CGContextRef context = CGBitmapContextCreate(NULL,
width,
height,
kBitsPerComponent,
bytesPerRow,
colorspaceRef,
kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast);
if (context == NULL) {
return image;
}
// Draw the image into the context and retrieve the new bitmap image without alpha
CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
CGImageRef imageRefWithoutAlpha = CGBitmapContextCreateImage(context);
UIImage *imageWithoutAlpha = [UIImage imageWithCGImage:imageRefWithoutAlpha
scale:image.scale
orientation:image.imageOrientation];
CGContextRelease(context);
CGImageRelease(imageRefWithoutAlpha);
return imageWithoutAlpha;
}
}
/*
* Defines the maximum size in MB of the decoded image when the flag `SDWebImageScaleDownLargeImages` is set
* Suggested value for iPad1 and iPhone 3GS: 60.
* Suggested value for iPad2 and iPhone 4: 120.
* Suggested value for iPhone 3G and iPod 2 and earlier devices: 30.
*/
static const CGFloat kDestImageSizeMB = 60.0f;
/*
* Defines the maximum size in MB of a tile used to decode image when the flag `SDWebImageScaleDownLargeImages` is set
* Suggested value for iPad1 and iPhone 3GS: 20.
* Suggested value for iPad2 and iPhone 4: 40.
* Suggested value for iPhone 3G and iPod 2 and earlier devices: 10.
*/
static const CGFloat kSourceImageTileSizeMB = 20.0f;
static const CGFloat kBytesPerMB = 1024.0f * 1024.0f;
static const CGFloat kPixelsPerMB = kBytesPerMB / kBytesPerPixel;
static const CGFloat kDestTotalPixels = kDestImageSizeMB * kPixelsPerMB;
static const CGFloat kTileTotalPixels = kSourceImageTileSizeMB * kPixelsPerMB;
static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to overlap the seems where tiles meet.
+ (nullable UIImage *)decodedAndScaledDownImageWithImage:(nullable UIImage *)image {
if (![UIImage shouldDecodeImage:image]) {
return image;
}
if (![UIImage shouldScaleDownImage:image]) {
return [UIImage decodedImageWithImage:image];
}
CGContextRef destContext;
// autorelease the bitmap context and all vars to help system to free memory when there are memory warning.
// on iOS7, do not forget to call [[SDImageCache sharedImageCache] clearMemory];
@autoreleasepool {
CGImageRef sourceImageRef = image.CGImage;
CGSize sourceResolution = CGSizeZero;
sourceResolution.width = CGImageGetWidth(sourceImageRef);
sourceResolution.height = CGImageGetHeight(sourceImageRef);
float sourceTotalPixels = sourceResolution.width * sourceResolution.height;
// Determine the scale ratio to apply to the input image
// that results in an output image of the defined size.
// see kDestImageSizeMB, and how it relates to destTotalPixels.
float imageScale = kDestTotalPixels / sourceTotalPixels;
CGSize destResolution = CGSizeZero;
destResolution.width = (int)(sourceResolution.width*imageScale);
destResolution.height = (int)(sourceResolution.height*imageScale);
// current color space
CGColorSpaceRef colorspaceRef = [UIImage colorSpaceForImageRef:sourceImageRef];
size_t bytesPerRow = kBytesPerPixel * destResolution.width;
// Allocate enough pixel data to hold the output image.
void* destBitmapData = malloc( bytesPerRow * destResolution.height );
if (destBitmapData == NULL) {
return image;
}
// kCGImageAlphaNone is not supported in CGBitmapContextCreate.
// Since the original image here has no alpha info, use kCGImageAlphaNoneSkipLast
// to create bitmap graphics contexts without alpha info.
destContext = CGBitmapContextCreate(destBitmapData,
destResolution.width,
destResolution.height,
kBitsPerComponent,
bytesPerRow,
colorspaceRef,
kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast);
if (destContext == NULL) {
free(destBitmapData);
return image;
}
CGContextSetInterpolationQuality(destContext, kCGInterpolationHigh);
// Now define the size of the rectangle to be used for the
// incremental blits from the input image to the output image.
// we use a source tile width equal to the width of the source
// image due to the way that iOS retrieves image data from disk.
// iOS must decode an image from disk in full width 'bands', even
// if current graphics context is clipped to a subrect within that
// band. Therefore we fully utilize all of the pixel data that results
// from a decoding opertion by achnoring our tile size to the full
// width of the input image.
CGRect sourceTile = CGRectZero;
sourceTile.size.width = sourceResolution.width;
// The source tile height is dynamic. Since we specified the size
// of the source tile in MB, see how many rows of pixels high it
// can be given the input image width.
sourceTile.size.height = (int)(kTileTotalPixels / sourceTile.size.width );
sourceTile.origin.x = 0.0f;
// The output tile is the same proportions as the input tile, but
// scaled to image scale.
CGRect destTile;
destTile.size.width = destResolution.width;
destTile.size.height = sourceTile.size.height * imageScale;
destTile.origin.x = 0.0f;
// The source seem overlap is proportionate to the destination seem overlap.
// this is the amount of pixels to overlap each tile as we assemble the ouput image.
float sourceSeemOverlap = (int)((kDestSeemOverlap/destResolution.height)*sourceResolution.height);
CGImageRef sourceTileImageRef;
// calculate the number of read/write operations required to assemble the
// output image.
int iterations = (int)( sourceResolution.height / sourceTile.size.height );
// If tile height doesn't divide the image height evenly, add another iteration
// to account for the remaining pixels.
int remainder = (int)sourceResolution.height % (int)sourceTile.size.height;
if(remainder) {
iterations++;
}
// Add seem overlaps to the tiles, but save the original tile height for y coordinate calculations.
float sourceTileHeightMinusOverlap = sourceTile.size.height;
sourceTile.size.height += sourceSeemOverlap;
destTile.size.height += kDestSeemOverlap;
for( int y = 0; y < iterations; ++y ) {
@autoreleasepool {
sourceTile.origin.y = y * sourceTileHeightMinusOverlap + sourceSeemOverlap;
destTile.origin.y = destResolution.height - (( y + 1 ) * sourceTileHeightMinusOverlap * imageScale + kDestSeemOverlap);
sourceTileImageRef = CGImageCreateWithImageInRect( sourceImageRef, sourceTile );
if( y == iterations - 1 && remainder ) {
float dify = destTile.size.height;
destTile.size.height = CGImageGetHeight( sourceTileImageRef ) * imageScale;
dify -= destTile.size.height;
destTile.origin.y += dify;
}
CGContextDrawImage( destContext, destTile, sourceTileImageRef );
CGImageRelease( sourceTileImageRef );
}
}
CGImageRef destImageRef = CGBitmapContextCreateImage(destContext);
CGContextRelease(destContext);
if (destImageRef == NULL) {
return image;
}
UIImage *destImage = [UIImage imageWithCGImage:destImageRef scale:image.scale orientation:image.imageOrientation];
CGImageRelease(destImageRef);
if (destImage == nil) {
return image;
}
return destImage;
}
}
+ (BOOL)shouldDecodeImage:(nullable UIImage *)image {
// Prevent "CGBitmapContextCreateImage: invalid context 0x0" error
if (image == nil) {
return NO;
}
// do not decode animated images
if (image.images != nil) {
return NO;
}
CGImageRef imageRef = image.CGImage;
CGImageAlphaInfo alpha = CGImageGetAlphaInfo(imageRef);
BOOL anyAlpha = (alpha == kCGImageAlphaFirst ||
alpha == kCGImageAlphaLast ||
alpha == kCGImageAlphaPremultipliedFirst ||
alpha == kCGImageAlphaPremultipliedLast);
// do not decode images with alpha
if (anyAlpha) {
return NO;
}
return YES;
}
+ (BOOL)shouldScaleDownImage:(nonnull UIImage *)image {
BOOL shouldScaleDown = YES;
CGImageRef sourceImageRef = image.CGImage;
CGSize sourceResolution = CGSizeZero;
sourceResolution.width = CGImageGetWidth(sourceImageRef);
sourceResolution.height = CGImageGetHeight(sourceImageRef);
float sourceTotalPixels = sourceResolution.width * sourceResolution.height;
float imageScale = kDestTotalPixels / sourceTotalPixels;
if (imageScale < 1) {
shouldScaleDown = YES;
} else {
shouldScaleDown = NO;
}
return shouldScaleDown;
}
+ (CGColorSpaceRef)colorSpaceForImageRef:(CGImageRef)imageRef {
// current
CGColorSpaceModel imageColorSpaceModel = CGColorSpaceGetModel(CGImageGetColorSpace(imageRef));
CGColorSpaceRef colorspaceRef = CGImageGetColorSpace(imageRef);
BOOL unsupportedColorSpace = (imageColorSpaceModel == kCGColorSpaceModelUnknown ||
imageColorSpaceModel == kCGColorSpaceModelMonochrome ||
imageColorSpaceModel == kCGColorSpaceModelCMYK ||
imageColorSpaceModel == kCGColorSpaceModelIndexed);
if (unsupportedColorSpace) {
colorspaceRef = CGColorSpaceCreateDeviceRGB();
CFAutorelease(colorspaceRef);
}
return colorspaceRef;
}
#elif SD_MAC
+ (nullable UIImage *)decodedImageWithImage:(nullable UIImage *)image {
return image;
}
+ (nullable UIImage *)decodedAndScaledDownImageWithImage:(nullable UIImage *)image {
return image;
}
#endif
@end

View File

@@ -0,0 +1,233 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import <Foundation/Foundation.h>
#import "SDWebImageCompat.h"
#import "SDWebImageOperation.h"
typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) {
SDWebImageDownloaderLowPriority = 1 << 0,
SDWebImageDownloaderProgressiveDownload = 1 << 1,
/**
* By default, request prevent the use of NSURLCache. With this flag, NSURLCache
* is used with default policies.
*/
SDWebImageDownloaderUseNSURLCache = 1 << 2,
/**
* Call completion block with nil image/imageData if the image was read from NSURLCache
* (to be combined with `SDWebImageDownloaderUseNSURLCache`).
*/
SDWebImageDownloaderIgnoreCachedResponse = 1 << 3,
/**
* In iOS 4+, continue the download of the image if the app goes to background. This is achieved by asking the system for
* extra time in background to let the request finish. If the background task expires the operation will be cancelled.
*/
SDWebImageDownloaderContinueInBackground = 1 << 4,
/**
* Handles cookies stored in NSHTTPCookieStore by setting
* NSMutableURLRequest.HTTPShouldHandleCookies = YES;
*/
SDWebImageDownloaderHandleCookies = 1 << 5,
/**
* Enable to allow untrusted SSL certificates.
* Useful for testing purposes. Use with caution in production.
*/
SDWebImageDownloaderAllowInvalidSSLCertificates = 1 << 6,
/**
* Put the image in the high priority queue.
*/
SDWebImageDownloaderHighPriority = 1 << 7,
/**
* Scale down the image
*/
SDWebImageDownloaderScaleDownLargeImages = 1 << 8,
};
typedef NS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder) {
/**
* Default value. All download operations will execute in queue style (first-in-first-out).
*/
SDWebImageDownloaderFIFOExecutionOrder,
/**
* All download operations will execute in stack style (last-in-first-out).
*/
SDWebImageDownloaderLIFOExecutionOrder
};
extern NSString * _Nonnull const SDWebImageDownloadStartNotification;
extern NSString * _Nonnull const SDWebImageDownloadStopNotification;
typedef void(^SDWebImageDownloaderProgressBlock)(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL);
typedef void(^SDWebImageDownloaderCompletedBlock)(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, BOOL finished);
typedef NSDictionary<NSString *, NSString *> SDHTTPHeadersDictionary;
typedef NSMutableDictionary<NSString *, NSString *> SDHTTPHeadersMutableDictionary;
typedef SDHTTPHeadersDictionary * _Nullable (^SDWebImageDownloaderHeadersFilterBlock)(NSURL * _Nullable url, SDHTTPHeadersDictionary * _Nullable headers);
/**
* A token associated with each download. Can be used to cancel a download
*/
@interface SDWebImageDownloadToken : NSObject
@property (nonatomic, strong, nullable) NSURL *url;
@property (nonatomic, strong, nullable) id downloadOperationCancelToken;
@end
/**
* Asynchronous downloader dedicated and optimized for image loading.
*/
@interface SDWebImageDownloader : NSObject
/**
* Decompressing images that are downloaded and cached can improve performance but can consume lot of memory.
* Defaults to YES. Set this to NO if you are experiencing a crash due to excessive memory consumption.
*/
@property (assign, nonatomic) BOOL shouldDecompressImages;
/**
* The maximum number of concurrent downloads
*/
@property (assign, nonatomic) NSInteger maxConcurrentDownloads;
/**
* Shows the current amount of downloads that still need to be downloaded
*/
@property (readonly, nonatomic) NSUInteger currentDownloadCount;
/**
* The timeout value (in seconds) for the download operation. Default: 15.0.
*/
@property (assign, nonatomic) NSTimeInterval downloadTimeout;
/**
* Changes download operations execution order. Default value is `SDWebImageDownloaderFIFOExecutionOrder`.
*/
@property (assign, nonatomic) SDWebImageDownloaderExecutionOrder executionOrder;
/**
* Singleton method, returns the shared instance
*
* @return global shared instance of downloader class
*/
+ (nonnull instancetype)sharedDownloader;
/**
* Set the default URL credential to be set for request operations.
*/
@property (strong, nonatomic, nullable) NSURLCredential *urlCredential;
/**
* Set username
*/
@property (strong, nonatomic, nullable) NSString *username;
/**
* Set password
*/
@property (strong, nonatomic, nullable) NSString *password;
/**
* Set filter to pick headers for downloading image HTTP request.
*
* This block will be invoked for each downloading image request, returned
* NSDictionary will be used as headers in corresponding HTTP request.
*/
@property (nonatomic, copy, nullable) SDWebImageDownloaderHeadersFilterBlock headersFilter;
/**
* Creates an instance of a downloader with specified session configuration.
* *Note*: `timeoutIntervalForRequest` is going to be overwritten.
* @return new instance of downloader class
*/
- (nonnull instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)sessionConfiguration NS_DESIGNATED_INITIALIZER;
/**
* Set a value for a HTTP header to be appended to each download HTTP request.
*
* @param value The value for the header field. Use `nil` value to remove the header.
* @param field The name of the header field to set.
*/
- (void)setValue:(nullable NSString *)value forHTTPHeaderField:(nullable NSString *)field;
/**
* Returns the value of the specified HTTP header field.
*
* @return The value associated with the header field field, or `nil` if there is no corresponding header field.
*/
- (nullable NSString *)valueForHTTPHeaderField:(nullable NSString *)field;
/**
* Sets a subclass of `SDWebImageDownloaderOperation` as the default
* `NSOperation` to be used each time SDWebImage constructs a request
* operation to download an image.
*
* @param operationClass The subclass of `SDWebImageDownloaderOperation` to set
* as default. Passing `nil` will revert to `SDWebImageDownloaderOperation`.
*/
- (void)setOperationClass:(nullable Class)operationClass;
/**
* Creates a SDWebImageDownloader async downloader instance with a given URL
*
* The delegate will be informed when the image is finish downloaded or an error has happen.
*
* @see SDWebImageDownloaderDelegate
*
* @param url The URL to the image to download
* @param options The options to be used for this download
* @param progressBlock A block called repeatedly while the image is downloading
* @note the progress block is executed on a background queue
* @param completedBlock A block called once the download is completed.
* If the download succeeded, the image parameter is set, in case of error,
* error parameter is set with the error. The last parameter is always YES
* if SDWebImageDownloaderProgressiveDownload isn't use. With the
* SDWebImageDownloaderProgressiveDownload option, this block is called
* repeatedly with the partial image object and the finished argument set to NO
* before to be called a last time with the full image and finished argument
* set to YES. In case of error, the finished argument is always YES.
*
* @return A token (SDWebImageDownloadToken) that can be passed to -cancel: to cancel this operation
*/
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock;
/**
* Cancels a download that was previously queued using -downloadImageWithURL:options:progress:completed:
*
* @param token The token received from -downloadImageWithURL:options:progress:completed: that should be canceled.
*/
- (void)cancel:(nullable SDWebImageDownloadToken *)token;
/**
* Sets the download queue suspension state
*/
- (void)setSuspended:(BOOL)suspended;
/**
* Cancels all download operations in the queue
*/
- (void)cancelAllDownloads;
@end

View File

@@ -0,0 +1,315 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "SDWebImageDownloader.h"
#import "SDWebImageDownloaderOperation.h"
#import <ImageIO/ImageIO.h>
@implementation SDWebImageDownloadToken
@end
@interface SDWebImageDownloader () <NSURLSessionTaskDelegate, NSURLSessionDataDelegate>
@property (strong, nonatomic, nonnull) NSOperationQueue *downloadQueue;
@property (weak, nonatomic, nullable) NSOperation *lastAddedOperation;
@property (assign, nonatomic, nullable) Class operationClass;
@property (strong, nonatomic, nonnull) NSMutableDictionary<NSURL *, SDWebImageDownloaderOperation *> *URLOperations;
@property (strong, nonatomic, nullable) SDHTTPHeadersMutableDictionary *HTTPHeaders;
// This queue is used to serialize the handling of the network responses of all the download operation in a single queue
@property (SDDispatchQueueSetterSementics, nonatomic, nullable) dispatch_queue_t barrierQueue;
// The session in which data tasks will run
@property (strong, nonatomic) NSURLSession *session;
@end
@implementation SDWebImageDownloader
+ (void)initialize {
// Bind SDNetworkActivityIndicator if available (download it here: http://github.com/rs/SDNetworkActivityIndicator )
// To use it, just add #import "SDNetworkActivityIndicator.h" in addition to the SDWebImage import
if (NSClassFromString(@"SDNetworkActivityIndicator")) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
id activityIndicator = [NSClassFromString(@"SDNetworkActivityIndicator") performSelector:NSSelectorFromString(@"sharedActivityIndicator")];
#pragma clang diagnostic pop
// Remove observer in case it was previously added.
[[NSNotificationCenter defaultCenter] removeObserver:activityIndicator name:SDWebImageDownloadStartNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:activityIndicator name:SDWebImageDownloadStopNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:activityIndicator
selector:NSSelectorFromString(@"startActivity")
name:SDWebImageDownloadStartNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:activityIndicator
selector:NSSelectorFromString(@"stopActivity")
name:SDWebImageDownloadStopNotification object:nil];
}
}
+ (nonnull instancetype)sharedDownloader {
static dispatch_once_t once;
static id instance;
dispatch_once(&once, ^{
instance = [self new];
});
return instance;
}
- (nonnull instancetype)init {
return [self initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
}
- (nonnull instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)sessionConfiguration {
if ((self = [super init])) {
_operationClass = [SDWebImageDownloaderOperation class];
_shouldDecompressImages = YES;
_executionOrder = SDWebImageDownloaderFIFOExecutionOrder;
_downloadQueue = [NSOperationQueue new];
_downloadQueue.maxConcurrentOperationCount = 6;
_downloadQueue.name = @"com.hackemist.SDWebImageDownloader";
_URLOperations = [NSMutableDictionary new];
#ifdef SD_WEBP
_HTTPHeaders = [@{@"Accept": @"image/webp,image/*;q=0.8"} mutableCopy];
#else
_HTTPHeaders = [@{@"Accept": @"image/*;q=0.8"} mutableCopy];
#endif
_barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderBarrierQueue", DISPATCH_QUEUE_CONCURRENT);
_downloadTimeout = 15.0;
sessionConfiguration.timeoutIntervalForRequest = _downloadTimeout;
/**
* Create the session for this task
* We send nil as delegate queue so that the session creates a serial operation queue for performing all delegate
* method calls and completion handler calls.
*/
self.session = [NSURLSession sessionWithConfiguration:sessionConfiguration
delegate:self
delegateQueue:nil];
}
return self;
}
- (void)dealloc {
[self.session invalidateAndCancel];
self.session = nil;
[self.downloadQueue cancelAllOperations];
SDDispatchQueueRelease(_barrierQueue);
}
- (void)setValue:(nullable NSString *)value forHTTPHeaderField:(nullable NSString *)field {
if (value) {
self.HTTPHeaders[field] = value;
}
else {
[self.HTTPHeaders removeObjectForKey:field];
}
}
- (nullable NSString *)valueForHTTPHeaderField:(nullable NSString *)field {
return self.HTTPHeaders[field];
}
- (void)setMaxConcurrentDownloads:(NSInteger)maxConcurrentDownloads {
_downloadQueue.maxConcurrentOperationCount = maxConcurrentDownloads;
}
- (NSUInteger)currentDownloadCount {
return _downloadQueue.operationCount;
}
- (NSInteger)maxConcurrentDownloads {
return _downloadQueue.maxConcurrentOperationCount;
}
- (void)setOperationClass:(nullable Class)operationClass {
if (operationClass && [operationClass isSubclassOfClass:[NSOperation class]] && [operationClass conformsToProtocol:@protocol(SDWebImageDownloaderOperationInterface)]) {
_operationClass = operationClass;
} else {
_operationClass = [SDWebImageDownloaderOperation class];
}
}
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
__weak SDWebImageDownloader *wself = self;
return [self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:^SDWebImageDownloaderOperation *{
__strong __typeof (wself) sself = wself;
NSTimeInterval timeoutInterval = sself.downloadTimeout;
if (timeoutInterval == 0.0) {
timeoutInterval = 15.0;
}
// In order to prevent from potential duplicate caching (NSURLCache + SDImageCache) we disable the cache for image requests if told otherwise
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:timeoutInterval];
request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
request.HTTPShouldUsePipelining = YES;
if (sself.headersFilter) {
request.allHTTPHeaderFields = sself.headersFilter(url, [sself.HTTPHeaders copy]);
}
else {
request.allHTTPHeaderFields = sself.HTTPHeaders;
}
SDWebImageDownloaderOperation *operation = [[sself.operationClass alloc] initWithRequest:request inSession:sself.session options:options];
operation.shouldDecompressImages = sself.shouldDecompressImages;
if (sself.urlCredential) {
operation.credential = sself.urlCredential;
} else if (sself.username && sself.password) {
operation.credential = [NSURLCredential credentialWithUser:sself.username password:sself.password persistence:NSURLCredentialPersistenceForSession];
}
if (options & SDWebImageDownloaderHighPriority) {
operation.queuePriority = NSOperationQueuePriorityHigh;
} else if (options & SDWebImageDownloaderLowPriority) {
operation.queuePriority = NSOperationQueuePriorityLow;
}
[sself.downloadQueue addOperation:operation];
if (sself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
// Emulate LIFO execution order by systematically adding new operations as last operation's dependency
[sself.lastAddedOperation addDependency:operation];
sself.lastAddedOperation = operation;
}
return operation;
}];
}
- (void)cancel:(nullable SDWebImageDownloadToken *)token {
dispatch_barrier_async(self.barrierQueue, ^{
SDWebImageDownloaderOperation *operation = self.URLOperations[token.url];
BOOL canceled = [operation cancel:token.downloadOperationCancelToken];
if (canceled) {
[self.URLOperations removeObjectForKey:token.url];
}
});
}
- (nullable SDWebImageDownloadToken *)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock
completedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock
forURL:(nullable NSURL *)url
createCallback:(SDWebImageDownloaderOperation *(^)())createCallback {
// The URL will be used as the key to the callbacks dictionary so it cannot be nil. If it is nil immediately call the completed block with no image or data.
if (url == nil) {
if (completedBlock != nil) {
completedBlock(nil, nil, nil, NO);
}
return nil;
}
__block SDWebImageDownloadToken *token = nil;
dispatch_barrier_sync(self.barrierQueue, ^{
SDWebImageDownloaderOperation *operation = self.URLOperations[url];
if (!operation) {
operation = createCallback();
self.URLOperations[url] = operation;
__weak SDWebImageDownloaderOperation *woperation = operation;
operation.completionBlock = ^{
SDWebImageDownloaderOperation *soperation = woperation;
if (!soperation) return;
if (self.URLOperations[url] == soperation) {
[self.URLOperations removeObjectForKey:url];
};
};
}
id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
token = [SDWebImageDownloadToken new];
token.url = url;
token.downloadOperationCancelToken = downloadOperationCancelToken;
});
return token;
}
- (void)setSuspended:(BOOL)suspended {
(self.downloadQueue).suspended = suspended;
}
- (void)cancelAllDownloads {
[self.downloadQueue cancelAllOperations];
}
#pragma mark Helper methods
- (SDWebImageDownloaderOperation *)operationWithTask:(NSURLSessionTask *)task {
SDWebImageDownloaderOperation *returnOperation = nil;
for (SDWebImageDownloaderOperation *operation in self.downloadQueue.operations) {
if (operation.dataTask.taskIdentifier == task.taskIdentifier) {
returnOperation = operation;
break;
}
}
return returnOperation;
}
#pragma mark NSURLSessionDataDelegate
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
// Identify the operation that runs this task and pass it the delegate method
SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:dataTask];
[dataOperation URLSession:session dataTask:dataTask didReceiveResponse:response completionHandler:completionHandler];
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
// Identify the operation that runs this task and pass it the delegate method
SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:dataTask];
[dataOperation URLSession:session dataTask:dataTask didReceiveData:data];
}
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
willCacheResponse:(NSCachedURLResponse *)proposedResponse
completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler {
// Identify the operation that runs this task and pass it the delegate method
SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:dataTask];
[dataOperation URLSession:session dataTask:dataTask willCacheResponse:proposedResponse completionHandler:completionHandler];
}
#pragma mark NSURLSessionTaskDelegate
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
// Identify the operation that runs this task and pass it the delegate method
SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:task];
[dataOperation URLSession:session task:task didCompleteWithError:error];
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler {
completionHandler(request);
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
// Identify the operation that runs this task and pass it the delegate method
SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:task];
[dataOperation URLSession:session task:task didReceiveChallenge:challenge completionHandler:completionHandler];
}
@end

View File

@@ -0,0 +1,122 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import <Foundation/Foundation.h>
#import "SDWebImageDownloader.h"
#import "SDWebImageOperation.h"
extern NSString * _Nonnull const SDWebImageDownloadStartNotification;
extern NSString * _Nonnull const SDWebImageDownloadReceiveResponseNotification;
extern NSString * _Nonnull const SDWebImageDownloadStopNotification;
extern NSString * _Nonnull const SDWebImageDownloadFinishNotification;
/**
Describes a downloader operation. If one wants to use a custom downloader op, it needs to inherit from `NSOperation` and conform to this protocol
*/
@protocol SDWebImageDownloaderOperationInterface<NSObject>
- (nonnull instancetype)initWithRequest:(nullable NSURLRequest *)request
inSession:(nullable NSURLSession *)session
options:(SDWebImageDownloaderOptions)options;
- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock;
- (BOOL)shouldDecompressImages;
- (void)setShouldDecompressImages:(BOOL)value;
- (nullable NSURLCredential *)credential;
- (void)setCredential:(nullable NSURLCredential *)value;
@end
@interface SDWebImageDownloaderOperation : NSOperation <SDWebImageDownloaderOperationInterface, SDWebImageOperation, NSURLSessionTaskDelegate, NSURLSessionDataDelegate>
/**
* The request used by the operation's task.
*/
@property (strong, nonatomic, readonly, nullable) NSURLRequest *request;
/**
* The operation's task
*/
@property (strong, nonatomic, readonly, nullable) NSURLSessionTask *dataTask;
@property (assign, nonatomic) BOOL shouldDecompressImages;
/**
* Was used to determine whether the URL connection should consult the credential storage for authenticating the connection.
* @deprecated Not used for a couple of versions
*/
@property (nonatomic, assign) BOOL shouldUseCredentialStorage __deprecated_msg("Property deprecated. Does nothing. Kept only for backwards compatibility");
/**
* The credential used for authentication challenges in `-connection:didReceiveAuthenticationChallenge:`.
*
* This will be overridden by any shared credentials that exist for the username or password of the request URL, if present.
*/
@property (nonatomic, strong, nullable) NSURLCredential *credential;
/**
* The SDWebImageDownloaderOptions for the receiver.
*/
@property (assign, nonatomic, readonly) SDWebImageDownloaderOptions options;
/**
* The expected size of data.
*/
@property (assign, nonatomic) NSInteger expectedSize;
/**
* The response returned by the operation's connection.
*/
@property (strong, nonatomic, nullable) NSURLResponse *response;
/**
* Initializes a `SDWebImageDownloaderOperation` object
*
* @see SDWebImageDownloaderOperation
*
* @param request the URL request
* @param session the URL session in which this operation will run
* @param options downloader options
*
* @return the initialized instance
*/
- (nonnull instancetype)initWithRequest:(nullable NSURLRequest *)request
inSession:(nullable NSURLSession *)session
options:(SDWebImageDownloaderOptions)options NS_DESIGNATED_INITIALIZER;
/**
* Adds handlers for progress and completion. Returns a tokent that can be passed to -cancel: to cancel this set of
* callbacks.
*
* @param progressBlock the block executed when a new chunk of data arrives.
* @note the progress block is executed on a background queue
* @param completedBlock the block executed when the download is done.
* @note the completed block is executed on the main queue for success. If errors are found, there is a chance the block will be executed on a background queue
*
* @return the token to use to cancel this set of handlers
*/
- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock;
/**
* Cancels a set of callbacks. Once all callbacks are canceled, the operation is cancelled.
*
* @param token the token representing a set of callbacks to cancel
*
* @return YES if the operation was stopped because this was the last token to be canceled. NO otherwise.
*/
- (BOOL)cancel:(nullable id)token;
@end

View File

@@ -0,0 +1,540 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "SDWebImageDownloaderOperation.h"
#import "SDWebImageDecoder.h"
#import "UIImage+MultiFormat.h"
#import <ImageIO/ImageIO.h>
#import "SDWebImageManager.h"
#import "NSImage+WebCache.h"
NSString *const SDWebImageDownloadStartNotification = @"SDWebImageDownloadStartNotification";
NSString *const SDWebImageDownloadReceiveResponseNotification = @"SDWebImageDownloadReceiveResponseNotification";
NSString *const SDWebImageDownloadStopNotification = @"SDWebImageDownloadStopNotification";
NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinishNotification";
static NSString *const kProgressCallbackKey = @"progress";
static NSString *const kCompletedCallbackKey = @"completed";
typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary;
@interface SDWebImageDownloaderOperation ()
@property (strong, nonatomic, nonnull) NSMutableArray<SDCallbacksDictionary *> *callbackBlocks;
@property (assign, nonatomic, getter = isExecuting) BOOL executing;
@property (assign, nonatomic, getter = isFinished) BOOL finished;
@property (strong, nonatomic, nullable) NSMutableData *imageData;
// This is weak because it is injected by whoever manages this session. If this gets nil-ed out, we won't be able to run
// the task associated with this operation
@property (weak, nonatomic, nullable) NSURLSession *unownedSession;
// This is set if we're using not using an injected NSURLSession. We're responsible of invalidating this one
@property (strong, nonatomic, nullable) NSURLSession *ownedSession;
@property (strong, nonatomic, readwrite, nullable) NSURLSessionTask *dataTask;
@property (SDDispatchQueueSetterSementics, nonatomic, nullable) dispatch_queue_t barrierQueue;
#if SD_UIKIT
@property (assign, nonatomic) UIBackgroundTaskIdentifier backgroundTaskId;
#endif
@end
@implementation SDWebImageDownloaderOperation {
size_t width, height;
#if SD_UIKIT || SD_WATCH
UIImageOrientation orientation;
#endif
BOOL responseFromCached;
}
@synthesize executing = _executing;
@synthesize finished = _finished;
- (nonnull instancetype)init {
return [self initWithRequest:nil inSession:nil options:0];
}
- (nonnull instancetype)initWithRequest:(nullable NSURLRequest *)request
inSession:(nullable NSURLSession *)session
options:(SDWebImageDownloaderOptions)options {
if ((self = [super init])) {
_request = [request copy];
_shouldDecompressImages = YES;
_options = options;
_callbackBlocks = [NSMutableArray new];
_executing = NO;
_finished = NO;
_expectedSize = 0;
_unownedSession = session;
responseFromCached = YES; // Initially wrong until `- URLSession:dataTask:willCacheResponse:completionHandler: is called or not called
_barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderOperationBarrierQueue", DISPATCH_QUEUE_CONCURRENT);
}
return self;
}
- (void)dealloc {
SDDispatchQueueRelease(_barrierQueue);
}
- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
SDCallbacksDictionary *callbacks = [NSMutableDictionary new];
if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
dispatch_barrier_async(self.barrierQueue, ^{
[self.callbackBlocks addObject:callbacks];
});
return callbacks;
}
- (nullable NSArray<id> *)callbacksForKey:(NSString *)key {
__block NSMutableArray<id> *callbacks = nil;
dispatch_sync(self.barrierQueue, ^{
// We need to remove [NSNull null] because there might not always be a progress block for each callback
callbacks = [[self.callbackBlocks valueForKey:key] mutableCopy];
[callbacks removeObjectIdenticalTo:[NSNull null]];
});
return [callbacks copy]; // strip mutability here
}
- (BOOL)cancel:(nullable id)token {
__block BOOL shouldCancel = NO;
dispatch_barrier_sync(self.barrierQueue, ^{
[self.callbackBlocks removeObjectIdenticalTo:token];
if (self.callbackBlocks.count == 0) {
shouldCancel = YES;
}
});
if (shouldCancel) {
[self cancel];
}
return shouldCancel;
}
- (void)start {
@synchronized (self) {
if (self.isCancelled) {
self.finished = YES;
[self reset];
return;
}
#if SD_UIKIT
Class UIApplicationClass = NSClassFromString(@"UIApplication");
BOOL hasApplication = UIApplicationClass && [UIApplicationClass respondsToSelector:@selector(sharedApplication)];
if (hasApplication && [self shouldContinueWhenAppEntersBackground]) {
__weak __typeof__ (self) wself = self;
UIApplication * app = [UIApplicationClass performSelector:@selector(sharedApplication)];
self.backgroundTaskId = [app beginBackgroundTaskWithExpirationHandler:^{
__strong __typeof (wself) sself = wself;
if (sself) {
[sself cancel];
[app endBackgroundTask:sself.backgroundTaskId];
sself.backgroundTaskId = UIBackgroundTaskInvalid;
}
}];
}
#endif
NSURLSession *session = self.unownedSession;
if (!self.unownedSession) {
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfig.timeoutIntervalForRequest = 15;
/**
* Create the session for this task
* We send nil as delegate queue so that the session creates a serial operation queue for performing all delegate
* method calls and completion handler calls.
*/
self.ownedSession = [NSURLSession sessionWithConfiguration:sessionConfig
delegate:self
delegateQueue:nil];
session = self.ownedSession;
}
self.dataTask = [session dataTaskWithRequest:self.request];
self.executing = YES;
}
[self.dataTask resume];
if (self.dataTask) {
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
progressBlock(0, NSURLResponseUnknownLength, self.request.URL);
}
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:self];
});
} else {
[self callCompletionBlocksWithError:[NSError errorWithDomain:NSURLErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Connection can't be initialized"}]];
}
#if SD_UIKIT
Class UIApplicationClass = NSClassFromString(@"UIApplication");
if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) {
return;
}
if (self.backgroundTaskId != UIBackgroundTaskInvalid) {
UIApplication * app = [UIApplication performSelector:@selector(sharedApplication)];
[app endBackgroundTask:self.backgroundTaskId];
self.backgroundTaskId = UIBackgroundTaskInvalid;
}
#endif
}
- (void)cancel {
@synchronized (self) {
[self cancelInternal];
}
}
- (void)cancelInternal {
if (self.isFinished) return;
[super cancel];
if (self.dataTask) {
[self.dataTask cancel];
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
});
// As we cancelled the connection, its callback won't be called and thus won't
// maintain the isFinished and isExecuting flags.
if (self.isExecuting) self.executing = NO;
if (!self.isFinished) self.finished = YES;
}
[self reset];
}
- (void)done {
self.finished = YES;
self.executing = NO;
[self reset];
}
- (void)reset {
dispatch_barrier_async(self.barrierQueue, ^{
[self.callbackBlocks removeAllObjects];
});
self.dataTask = nil;
self.imageData = nil;
if (self.ownedSession) {
[self.ownedSession invalidateAndCancel];
self.ownedSession = nil;
}
}
- (void)setFinished:(BOOL)finished {
[self willChangeValueForKey:@"isFinished"];
_finished = finished;
[self didChangeValueForKey:@"isFinished"];
}
- (void)setExecuting:(BOOL)executing {
[self willChangeValueForKey:@"isExecuting"];
_executing = executing;
[self didChangeValueForKey:@"isExecuting"];
}
- (BOOL)isConcurrent {
return YES;
}
#pragma mark NSURLSessionDataDelegate
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
//'304 Not Modified' is an exceptional one
if (![response respondsToSelector:@selector(statusCode)] || (((NSHTTPURLResponse *)response).statusCode < 400 && ((NSHTTPURLResponse *)response).statusCode != 304)) {
NSInteger expected = response.expectedContentLength > 0 ? (NSInteger)response.expectedContentLength : 0;
self.expectedSize = expected;
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
progressBlock(0, expected, self.request.URL);
}
self.imageData = [[NSMutableData alloc] initWithCapacity:expected];
self.response = response;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadReceiveResponseNotification object:self];
});
}
else {
NSUInteger code = ((NSHTTPURLResponse *)response).statusCode;
//This is the case when server returns '304 Not Modified'. It means that remote image is not changed.
//In case of 304 we need just cancel the operation and return cached image from the cache.
if (code == 304) {
[self cancelInternal];
} else {
[self.dataTask cancel];
}
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
});
[self callCompletionBlocksWithError:[NSError errorWithDomain:NSURLErrorDomain code:((NSHTTPURLResponse *)response).statusCode userInfo:nil]];
[self done];
}
if (completionHandler) {
completionHandler(NSURLSessionResponseAllow);
}
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
[self.imageData appendData:data];
if ((self.options & SDWebImageDownloaderProgressiveDownload) && self.expectedSize > 0) {
// The following code is from http://www.cocoaintheshell.com/2011/05/progressive-images-download-imageio/
// Thanks to the author @Nyx0uf
// Get the total bytes downloaded
const NSInteger totalSize = self.imageData.length;
// Update the data source, we must pass ALL the data, not just the new bytes
CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)self.imageData, NULL);
if (width + height == 0) {
CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, NULL);
if (properties) {
NSInteger orientationValue = -1;
CFTypeRef val = CFDictionaryGetValue(properties, kCGImagePropertyPixelHeight);
if (val) CFNumberGetValue(val, kCFNumberLongType, &height);
val = CFDictionaryGetValue(properties, kCGImagePropertyPixelWidth);
if (val) CFNumberGetValue(val, kCFNumberLongType, &width);
val = CFDictionaryGetValue(properties, kCGImagePropertyOrientation);
if (val) CFNumberGetValue(val, kCFNumberNSIntegerType, &orientationValue);
CFRelease(properties);
// When we draw to Core Graphics, we lose orientation information,
// which means the image below born of initWithCGIImage will be
// oriented incorrectly sometimes. (Unlike the image born of initWithData
// in didCompleteWithError.) So save it here and pass it on later.
#if SD_UIKIT || SD_WATCH
orientation = [[self class] orientationFromPropertyValue:(orientationValue == -1 ? 1 : orientationValue)];
#endif
}
}
if (width + height > 0 && totalSize < self.expectedSize) {
// Create the image
CGImageRef partialImageRef = CGImageSourceCreateImageAtIndex(imageSource, 0, NULL);
#if SD_UIKIT || SD_WATCH
// Workaround for iOS anamorphic image
if (partialImageRef) {
const size_t partialHeight = CGImageGetHeight(partialImageRef);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef bmContext = CGBitmapContextCreate(NULL, width, height, 8, width * 4, colorSpace, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);
CGColorSpaceRelease(colorSpace);
if (bmContext) {
CGContextDrawImage(bmContext, (CGRect){.origin.x = 0.0f, .origin.y = 0.0f, .size.width = width, .size.height = partialHeight}, partialImageRef);
CGImageRelease(partialImageRef);
partialImageRef = CGBitmapContextCreateImage(bmContext);
CGContextRelease(bmContext);
}
else {
CGImageRelease(partialImageRef);
partialImageRef = nil;
}
}
#endif
if (partialImageRef) {
#if SD_UIKIT || SD_WATCH
UIImage *image = [UIImage imageWithCGImage:partialImageRef scale:1 orientation:orientation];
#elif SD_MAC
UIImage *image = [[UIImage alloc] initWithCGImage:partialImageRef size:NSZeroSize];
#endif
NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
UIImage *scaledImage = [self scaledImageForKey:key image:image];
if (self.shouldDecompressImages) {
image = [UIImage decodedImageWithImage:scaledImage];
}
else {
image = scaledImage;
}
CGImageRelease(partialImageRef);
[self callCompletionBlocksWithImage:image imageData:nil error:nil finished:NO];
}
}
CFRelease(imageSource);
}
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
progressBlock(self.imageData.length, self.expectedSize, self.request.URL);
}
}
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
willCacheResponse:(NSCachedURLResponse *)proposedResponse
completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler {
responseFromCached = NO; // If this method is called, it means the response wasn't read from cache
NSCachedURLResponse *cachedResponse = proposedResponse;
if (self.request.cachePolicy == NSURLRequestReloadIgnoringLocalCacheData) {
// Prevents caching of responses
cachedResponse = nil;
}
if (completionHandler) {
completionHandler(cachedResponse);
}
}
#pragma mark NSURLSessionTaskDelegate
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
@synchronized(self) {
self.dataTask = nil;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
if (!error) {
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadFinishNotification object:self];
}
});
}
if (error) {
[self callCompletionBlocksWithError:error];
} else {
if ([self callbacksForKey:kCompletedCallbackKey].count > 0) {
/**
* See #1608 and #1623 - apparently, there is a race condition on `NSURLCache` that causes a crash
* Limited the calls to `cachedResponseForRequest:` only for cases where we should ignore the cached response
* and images for which responseFromCached is YES (only the ones that cannot be cached).
* Note: responseFromCached is set to NO inside `willCacheResponse:`. This method doesn't get called for large images or images behind authentication
*/
if (self.options & SDWebImageDownloaderIgnoreCachedResponse && responseFromCached && [[NSURLCache sharedURLCache] cachedResponseForRequest:self.request]) {
// hack
[self callCompletionBlocksWithImage:nil imageData:nil error:nil finished:YES];
} else if (self.imageData) {
UIImage *image = [UIImage sd_imageWithData:self.imageData];
NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
image = [self scaledImageForKey:key image:image];
// Do not force decoding animated GIFs
if (!image.images) {
if (self.shouldDecompressImages) {
if (self.options & SDWebImageDownloaderScaleDownLargeImages) {
#if SD_UIKIT || SD_WATCH
image = [UIImage decodedAndScaledDownImageWithImage:image];
[self.imageData setData:UIImagePNGRepresentation(image)];
#endif
} else {
image = [UIImage decodedImageWithImage:image];
}
}
}
if (CGSizeEqualToSize(image.size, CGSizeZero)) {
[self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image has 0 pixels"}]];
} else {
[self callCompletionBlocksWithImage:image imageData:self.imageData error:nil finished:YES];
}
} else {
[self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Image data is nil"}]];
}
}
}
[self done];
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
__block NSURLCredential *credential = nil;
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
if (!(self.options & SDWebImageDownloaderAllowInvalidSSLCertificates)) {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
} else {
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
disposition = NSURLSessionAuthChallengeUseCredential;
}
} else {
if (challenge.previousFailureCount == 0) {
if (self.credential) {
credential = self.credential;
disposition = NSURLSessionAuthChallengeUseCredential;
} else {
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
} else {
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
}
if (completionHandler) {
completionHandler(disposition, credential);
}
}
#pragma mark Helper methods
#if SD_UIKIT || SD_WATCH
+ (UIImageOrientation)orientationFromPropertyValue:(NSInteger)value {
switch (value) {
case 1:
return UIImageOrientationUp;
case 3:
return UIImageOrientationDown;
case 8:
return UIImageOrientationLeft;
case 6:
return UIImageOrientationRight;
case 2:
return UIImageOrientationUpMirrored;
case 4:
return UIImageOrientationDownMirrored;
case 5:
return UIImageOrientationLeftMirrored;
case 7:
return UIImageOrientationRightMirrored;
default:
return UIImageOrientationUp;
}
}
#endif
- (nullable UIImage *)scaledImageForKey:(nullable NSString *)key image:(nullable UIImage *)image {
return SDScaledImageForKey(key, image);
}
- (BOOL)shouldContinueWhenAppEntersBackground {
return self.options & SDWebImageDownloaderContinueInBackground;
}
- (void)callCompletionBlocksWithError:(nullable NSError *)error {
[self callCompletionBlocksWithImage:nil imageData:nil error:error finished:YES];
}
- (void)callCompletionBlocksWithImage:(nullable UIImage *)image
imageData:(nullable NSData *)imageData
error:(nullable NSError *)error
finished:(BOOL)finished {
NSArray<id> *completionBlocks = [self callbacksForKey:kCompletedCallbackKey];
dispatch_main_async_safe(^{
for (SDWebImageDownloaderCompletedBlock completedBlock in completionBlocks) {
completedBlock(image, imageData, error, finished);
}
});
}
@end

View File

@@ -0,0 +1,275 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "SDWebImageCompat.h"
#import "SDWebImageOperation.h"
#import "SDWebImageDownloader.h"
#import "SDImageCache.h"
typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
/**
* By default, when a URL fail to be downloaded, the URL is blacklisted so the library won't keep trying.
* This flag disable this blacklisting.
*/
SDWebImageRetryFailed = 1 << 0,
/**
* By default, image downloads are started during UI interactions, this flags disable this feature,
* leading to delayed download on UIScrollView deceleration for instance.
*/
SDWebImageLowPriority = 1 << 1,
/**
* This flag disables on-disk caching
*/
SDWebImageCacheMemoryOnly = 1 << 2,
/**
* This flag enables progressive download, the image is displayed progressively during download as a browser would do.
* By default, the image is only displayed once completely downloaded.
*/
SDWebImageProgressiveDownload = 1 << 3,
/**
* Even if the image is cached, respect the HTTP response cache control, and refresh the image from remote location if needed.
* The disk caching will be handled by NSURLCache instead of SDWebImage leading to slight performance degradation.
* This option helps deal with images changing behind the same request URL, e.g. Facebook graph api profile pics.
* If a cached image is refreshed, the completion block is called once with the cached image and again with the final image.
*
* Use this flag only if you can't make your URLs static with embedded cache busting parameter.
*/
SDWebImageRefreshCached = 1 << 4,
/**
* In iOS 4+, continue the download of the image if the app goes to background. This is achieved by asking the system for
* extra time in background to let the request finish. If the background task expires the operation will be cancelled.
*/
SDWebImageContinueInBackground = 1 << 5,
/**
* Handles cookies stored in NSHTTPCookieStore by setting
* NSMutableURLRequest.HTTPShouldHandleCookies = YES;
*/
SDWebImageHandleCookies = 1 << 6,
/**
* Enable to allow untrusted SSL certificates.
* Useful for testing purposes. Use with caution in production.
*/
SDWebImageAllowInvalidSSLCertificates = 1 << 7,
/**
* By default, images are loaded in the order in which they were queued. This flag moves them to
* the front of the queue.
*/
SDWebImageHighPriority = 1 << 8,
/**
* By default, placeholder images are loaded while the image is loading. This flag will delay the loading
* of the placeholder image until after the image has finished loading.
*/
SDWebImageDelayPlaceholder = 1 << 9,
/**
* We usually don't call transformDownloadedImage delegate method on animated images,
* as most transformation code would mangle it.
* Use this flag to transform them anyway.
*/
SDWebImageTransformAnimatedImage = 1 << 10,
/**
* By default, image is added to the imageView after download. But in some cases, we want to
* have the hand before setting the image (apply a filter or add it with cross-fade animation for instance)
* Use this flag if you want to manually set the image in the completion when success
*/
SDWebImageAvoidAutoSetImage = 1 << 11,
/**
* By default, images are decoded respecting their original size. On iOS, this flag will scale down the
* images to a size compatible with the constrained memory of devices.
* If `SDWebImageProgressiveDownload` flag is set the scale down is deactivated.
*/
SDWebImageScaleDownLargeImages = 1 << 12
};
typedef void(^SDExternalCompletionBlock)(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL);
typedef void(^SDInternalCompletionBlock)(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL);
typedef NSString * _Nullable (^SDWebImageCacheKeyFilterBlock)(NSURL * _Nullable url);
@class SDWebImageManager;
@protocol SDWebImageManagerDelegate <NSObject>
@optional
/**
* Controls which image should be downloaded when the image is not found in the cache.
*
* @param imageManager The current `SDWebImageManager`
* @param imageURL The url of the image to be downloaded
*
* @return Return NO to prevent the downloading of the image on cache misses. If not implemented, YES is implied.
*/
- (BOOL)imageManager:(nonnull SDWebImageManager *)imageManager shouldDownloadImageForURL:(nullable NSURL *)imageURL;
/**
* Allows to transform the image immediately after it has been downloaded and just before to cache it on disk and memory.
* NOTE: This method is called from a global queue in order to not to block the main thread.
*
* @param imageManager The current `SDWebImageManager`
* @param image The image to transform
* @param imageURL The url of the image to transform
*
* @return The transformed image object.
*/
- (nullable UIImage *)imageManager:(nonnull SDWebImageManager *)imageManager transformDownloadedImage:(nullable UIImage *)image withURL:(nullable NSURL *)imageURL;
@end
/**
* The SDWebImageManager is the class behind the UIImageView+WebCache category and likes.
* It ties the asynchronous downloader (SDWebImageDownloader) with the image cache store (SDImageCache).
* You can use this class directly to benefit from web image downloading with caching in another context than
* a UIView.
*
* Here is a simple example of how to use SDWebImageManager:
*
* @code
SDWebImageManager *manager = [SDWebImageManager sharedManager];
[manager loadImageWithURL:imageURL
options:0
progress:nil
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
if (image) {
// do something with image
}
}];
* @endcode
*/
@interface SDWebImageManager : NSObject
@property (weak, nonatomic, nullable) id <SDWebImageManagerDelegate> delegate;
@property (strong, nonatomic, readonly, nullable) SDImageCache *imageCache;
@property (strong, nonatomic, readonly, nullable) SDWebImageDownloader *imageDownloader;
/**
* The cache filter is a block used each time SDWebImageManager need to convert an URL into a cache key. This can
* be used to remove dynamic part of an image URL.
*
* The following example sets a filter in the application delegate that will remove any query-string from the
* URL before to use it as a cache key:
*
* @code
[[SDWebImageManager sharedManager] setCacheKeyFilter:^(NSURL *url) {
url = [[NSURL alloc] initWithScheme:url.scheme host:url.host path:url.path];
return [url absoluteString];
}];
* @endcode
*/
@property (nonatomic, copy, nullable) SDWebImageCacheKeyFilterBlock cacheKeyFilter;
/**
* Returns global SDWebImageManager instance.
*
* @return SDWebImageManager shared instance
*/
+ (nonnull instancetype)sharedManager;
/**
* Allows to specify instance of cache and image downloader used with image manager.
* @return new instance of `SDWebImageManager` with specified cache and downloader.
*/
- (nonnull instancetype)initWithCache:(nonnull SDImageCache *)cache downloader:(nonnull SDWebImageDownloader *)downloader NS_DESIGNATED_INITIALIZER;
/**
* Downloads the image at the given URL if not present in cache or return the cached version otherwise.
*
* @param url The URL to the image
* @param options A mask to specify options to use for this request
* @param progressBlock A block called while image is downloading
* @note the progress block is executed on a background queue
* @param completedBlock A block called when operation has been completed.
*
* This parameter is required.
*
* This block has no return value and takes the requested UIImage as first parameter and the NSData representation as second parameter.
* In case of error the image parameter is nil and the third parameter may contain an NSError.
*
* The forth parameter is an `SDImageCacheType` enum indicating if the image was retrieved from the local cache
* or from the memory cache or from the network.
*
* The fith parameter is set to NO when the SDWebImageProgressiveDownload option is used and the image is
* downloading. This block is thus called repeatedly with a partial image. When image is fully downloaded, the
* block is called a last time with the full image and the last parameter set to YES.
*
* The last parameter is the original image URL
*
* @return Returns an NSObject conforming to SDWebImageOperation. Should be an instance of SDWebImageDownloaderOperation
*/
- (nullable id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock;
/**
* Saves image to cache for given URL
*
* @param image The image to cache
* @param url The URL to the image
*
*/
- (void)saveImageToCache:(nullable UIImage *)image forURL:(nullable NSURL *)url;
/**
* Cancel all current operations
*/
- (void)cancelAll;
/**
* Check one or more operations running
*/
- (BOOL)isRunning;
/**
* Async check if image has already been cached
*
* @param url image url
* @param completionBlock the block to be executed when the check is finished
*
* @note the completion block is always executed on the main queue
*/
- (void)cachedImageExistsForURL:(nullable NSURL *)url
completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock;
/**
* Async check if image has already been cached on disk only
*
* @param url image url
* @param completionBlock the block to be executed when the check is finished
*
* @note the completion block is always executed on the main queue
*/
- (void)diskImageExistsForURL:(nullable NSURL *)url
completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock;
/**
*Return the cache key for a given URL
*/
- (nullable NSString *)cacheKeyForURL:(nullable NSURL *)url;
@end

View File

@@ -0,0 +1,336 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "SDWebImageManager.h"
#import <objc/message.h>
#import "NSImage+WebCache.h"
@interface SDWebImageCombinedOperation : NSObject <SDWebImageOperation>
@property (assign, nonatomic, getter = isCancelled) BOOL cancelled;
@property (copy, nonatomic, nullable) SDWebImageNoParamsBlock cancelBlock;
@property (strong, nonatomic, nullable) NSOperation *cacheOperation;
@end
@interface SDWebImageManager ()
@property (strong, nonatomic, readwrite, nonnull) SDImageCache *imageCache;
@property (strong, nonatomic, readwrite, nonnull) SDWebImageDownloader *imageDownloader;
@property (strong, nonatomic, nonnull) NSMutableSet<NSURL *> *failedURLs;
@property (strong, nonatomic, nonnull) NSMutableArray<SDWebImageCombinedOperation *> *runningOperations;
@end
@implementation SDWebImageManager
+ (nonnull instancetype)sharedManager {
static dispatch_once_t once;
static id instance;
dispatch_once(&once, ^{
instance = [self new];
});
return instance;
}
- (nonnull instancetype)init {
SDImageCache *cache = [SDImageCache sharedImageCache];
SDWebImageDownloader *downloader = [SDWebImageDownloader sharedDownloader];
return [self initWithCache:cache downloader:downloader];
}
- (nonnull instancetype)initWithCache:(nonnull SDImageCache *)cache downloader:(nonnull SDWebImageDownloader *)downloader {
if ((self = [super init])) {
_imageCache = cache;
_imageDownloader = downloader;
_failedURLs = [NSMutableSet new];
_runningOperations = [NSMutableArray new];
}
return self;
}
- (nullable NSString *)cacheKeyForURL:(nullable NSURL *)url {
if (!url) {
return @"";
}
if (self.cacheKeyFilter) {
return self.cacheKeyFilter(url);
} else {
return url.absoluteString;
}
}
- (void)cachedImageExistsForURL:(nullable NSURL *)url
completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock {
NSString *key = [self cacheKeyForURL:url];
BOOL isInMemoryCache = ([self.imageCache imageFromMemoryCacheForKey:key] != nil);
if (isInMemoryCache) {
// making sure we call the completion block on the main queue
dispatch_async(dispatch_get_main_queue(), ^{
if (completionBlock) {
completionBlock(YES);
}
});
return;
}
[self.imageCache diskImageExistsWithKey:key completion:^(BOOL isInDiskCache) {
// the completion block of checkDiskCacheForImageWithKey:completion: is always called on the main queue, no need to further dispatch
if (completionBlock) {
completionBlock(isInDiskCache);
}
}];
}
- (void)diskImageExistsForURL:(nullable NSURL *)url
completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock {
NSString *key = [self cacheKeyForURL:url];
[self.imageCache diskImageExistsWithKey:key completion:^(BOOL isInDiskCache) {
// the completion block of checkDiskCacheForImageWithKey:completion: is always called on the main queue, no need to further dispatch
if (completionBlock) {
completionBlock(isInDiskCache);
}
}];
}
- (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock {
// Invoking this method without a completedBlock is pointless
NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");
// Very common mistake is to send the URL using NSString object instead of NSURL. For some strange reason, Xcode won't
// throw any warning for this type mismatch. Here we failsafe this error by allowing URLs to be passed as NSString.
if ([url isKindOfClass:NSString.class]) {
url = [NSURL URLWithString:(NSString *)url];
}
// Prevents app crashing on argument type error like sending NSNull instead of NSURL
if (![url isKindOfClass:NSURL.class]) {
url = nil;
}
__block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
__weak SDWebImageCombinedOperation *weakOperation = operation;
BOOL isFailedUrl = NO;
if (url) {
@synchronized (self.failedURLs) {
isFailedUrl = [self.failedURLs containsObject:url];
}
}
if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
[self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil] url:url];
return operation;
}
@synchronized (self.runningOperations) {
[self.runningOperations addObject:operation];
}
NSString *key = [self cacheKeyForURL:url];
operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
if (operation.isCancelled) {
[self safelyRemoveOperationFromRunning:operation];
return;
}
if ((!cachedImage || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
if (cachedImage && options & SDWebImageRefreshCached) {
// If image was found in the cache but SDWebImageRefreshCached is provided, notify about the cached image
// AND try to re-download it in order to let a chance to NSURLCache to refresh it from server.
[self callCompletionBlockForOperation:weakOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
}
// download if no image or requested to refresh anyway, and download allowed by delegate
SDWebImageDownloaderOptions downloaderOptions = 0;
if (options & SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority;
if (options & SDWebImageProgressiveDownload) downloaderOptions |= SDWebImageDownloaderProgressiveDownload;
if (options & SDWebImageRefreshCached) downloaderOptions |= SDWebImageDownloaderUseNSURLCache;
if (options & SDWebImageContinueInBackground) downloaderOptions |= SDWebImageDownloaderContinueInBackground;
if (options & SDWebImageHandleCookies) downloaderOptions |= SDWebImageDownloaderHandleCookies;
if (options & SDWebImageAllowInvalidSSLCertificates) downloaderOptions |= SDWebImageDownloaderAllowInvalidSSLCertificates;
if (options & SDWebImageHighPriority) downloaderOptions |= SDWebImageDownloaderHighPriority;
if (options & SDWebImageScaleDownLargeImages) downloaderOptions |= SDWebImageDownloaderScaleDownLargeImages;
if (cachedImage && options & SDWebImageRefreshCached) {
// force progressive off if image already cached but forced refreshing
downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload;
// ignore image read from NSURLCache if image if cached but force refreshing
downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
}
SDWebImageDownloadToken *subOperationToken = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (!strongOperation || strongOperation.isCancelled) {
// Do nothing if the operation was cancelled
// See #699 for more details
// if we would call the completedBlock, there could be a race condition between this block and another completedBlock for the same object, so if this one is called second, we will overwrite the new data
} else if (error) {
[self callCompletionBlockForOperation:strongOperation completion:completedBlock error:error url:url];
if ( error.code != NSURLErrorNotConnectedToInternet
&& error.code != NSURLErrorCancelled
&& error.code != NSURLErrorTimedOut
&& error.code != NSURLErrorInternationalRoamingOff
&& error.code != NSURLErrorDataNotAllowed
&& error.code != NSURLErrorCannotFindHost
&& error.code != NSURLErrorCannotConnectToHost) {
@synchronized (self.failedURLs) {
[self.failedURLs addObject:url];
}
}
}
else {
if ((options & SDWebImageRetryFailed)) {
@synchronized (self.failedURLs) {
[self.failedURLs removeObject:url];
}
}
BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);
if (options & SDWebImageRefreshCached && cachedImage && !downloadedImage) {
// Image refresh hit the NSURLCache cache, do not call the completion block
} else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];
if (transformedImage && finished) {
BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
// pass nil if the image was transformed, so we can recalculate the data from the image
[self.imageCache storeImage:transformedImage imageData:(imageWasTransformed ? nil : downloadedData) forKey:key toDisk:cacheOnDisk completion:nil];
}
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:transformedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
});
} else {
if (downloadedImage && finished) {
[self.imageCache storeImage:downloadedImage imageData:downloadedData forKey:key toDisk:cacheOnDisk completion:nil];
}
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:downloadedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
}
}
if (finished) {
[self safelyRemoveOperationFromRunning:strongOperation];
}
}];
operation.cancelBlock = ^{
[self.imageDownloader cancel:subOperationToken];
__strong __typeof(weakOperation) strongOperation = weakOperation;
[self safelyRemoveOperationFromRunning:strongOperation];
};
} else if (cachedImage) {
__strong __typeof(weakOperation) strongOperation = weakOperation;
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
[self safelyRemoveOperationFromRunning:operation];
} else {
// Image not in cache and download disallowed by delegate
__strong __typeof(weakOperation) strongOperation = weakOperation;
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url];
[self safelyRemoveOperationFromRunning:operation];
}
}];
return operation;
}
- (void)saveImageToCache:(nullable UIImage *)image forURL:(nullable NSURL *)url {
if (image && url) {
NSString *key = [self cacheKeyForURL:url];
[self.imageCache storeImage:image forKey:key toDisk:YES completion:nil];
}
}
- (void)cancelAll {
@synchronized (self.runningOperations) {
NSArray<SDWebImageCombinedOperation *> *copiedOperations = [self.runningOperations copy];
[copiedOperations makeObjectsPerformSelector:@selector(cancel)];
[self.runningOperations removeObjectsInArray:copiedOperations];
}
}
- (BOOL)isRunning {
BOOL isRunning = NO;
@synchronized (self.runningOperations) {
isRunning = (self.runningOperations.count > 0);
}
return isRunning;
}
- (void)safelyRemoveOperationFromRunning:(nullable SDWebImageCombinedOperation*)operation {
@synchronized (self.runningOperations) {
if (operation) {
[self.runningOperations removeObject:operation];
}
}
}
- (void)callCompletionBlockForOperation:(nullable SDWebImageCombinedOperation*)operation
completion:(nullable SDInternalCompletionBlock)completionBlock
error:(nullable NSError *)error
url:(nullable NSURL *)url {
[self callCompletionBlockForOperation:operation completion:completionBlock image:nil data:nil error:error cacheType:SDImageCacheTypeNone finished:YES url:url];
}
- (void)callCompletionBlockForOperation:(nullable SDWebImageCombinedOperation*)operation
completion:(nullable SDInternalCompletionBlock)completionBlock
image:(nullable UIImage *)image
data:(nullable NSData *)data
error:(nullable NSError *)error
cacheType:(SDImageCacheType)cacheType
finished:(BOOL)finished
url:(nullable NSURL *)url {
dispatch_main_async_safe(^{
if (operation && !operation.isCancelled && completionBlock) {
completionBlock(image, data, error, cacheType, finished, url);
}
});
}
@end
@implementation SDWebImageCombinedOperation
- (void)setCancelBlock:(nullable SDWebImageNoParamsBlock)cancelBlock {
// check if the operation is already cancelled, then we just call the cancelBlock
if (self.isCancelled) {
if (cancelBlock) {
cancelBlock();
}
_cancelBlock = nil; // don't forget to nil the cancelBlock, otherwise we will get crashes
} else {
_cancelBlock = [cancelBlock copy];
}
}
- (void)cancel {
self.cancelled = YES;
if (self.cacheOperation) {
[self.cacheOperation cancel];
self.cacheOperation = nil;
}
if (self.cancelBlock) {
self.cancelBlock();
// TODO: this is a temporary fix to #809.
// Until we can figure the exact cause of the crash, going with the ivar instead of the setter
// self.cancelBlock = nil;
_cancelBlock = nil;
}
}
@end

View File

@@ -0,0 +1,15 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import <Foundation/Foundation.h>
@protocol SDWebImageOperation <NSObject>
- (void)cancel;
@end

View File

@@ -0,0 +1,112 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import <Foundation/Foundation.h>
#import "SDWebImageManager.h"
@class SDWebImagePrefetcher;
@protocol SDWebImagePrefetcherDelegate <NSObject>
@optional
/**
* Called when an image was prefetched.
*
* @param imagePrefetcher The current image prefetcher
* @param imageURL The image url that was prefetched
* @param finishedCount The total number of images that were prefetched (successful or not)
* @param totalCount The total number of images that were to be prefetched
*/
- (void)imagePrefetcher:(nonnull SDWebImagePrefetcher *)imagePrefetcher didPrefetchURL:(nullable NSURL *)imageURL finishedCount:(NSUInteger)finishedCount totalCount:(NSUInteger)totalCount;
/**
* Called when all images are prefetched.
* @param imagePrefetcher The current image prefetcher
* @param totalCount The total number of images that were prefetched (whether successful or not)
* @param skippedCount The total number of images that were skipped
*/
- (void)imagePrefetcher:(nonnull SDWebImagePrefetcher *)imagePrefetcher didFinishWithTotalCount:(NSUInteger)totalCount skippedCount:(NSUInteger)skippedCount;
@end
typedef void(^SDWebImagePrefetcherProgressBlock)(NSUInteger noOfFinishedUrls, NSUInteger noOfTotalUrls);
typedef void(^SDWebImagePrefetcherCompletionBlock)(NSUInteger noOfFinishedUrls, NSUInteger noOfSkippedUrls);
/**
* Prefetch some URLs in the cache for future use. Images are downloaded in low priority.
*/
@interface SDWebImagePrefetcher : NSObject
/**
* The web image manager
*/
@property (strong, nonatomic, readonly, nonnull) SDWebImageManager *manager;
/**
* Maximum number of URLs to prefetch at the same time. Defaults to 3.
*/
@property (nonatomic, assign) NSUInteger maxConcurrentDownloads;
/**
* SDWebImageOptions for prefetcher. Defaults to SDWebImageLowPriority.
*/
@property (nonatomic, assign) SDWebImageOptions options;
/**
* Queue options for Prefetcher. Defaults to Main Queue.
*/
@property (nonatomic, assign, nonnull) dispatch_queue_t prefetcherQueue;
@property (weak, nonatomic, nullable) id <SDWebImagePrefetcherDelegate> delegate;
/**
* Return the global image prefetcher instance.
*/
+ (nonnull instancetype)sharedImagePrefetcher;
/**
* Allows you to instantiate a prefetcher with any arbitrary image manager.
*/
- (nonnull instancetype)initWithImageManager:(nonnull SDWebImageManager *)manager NS_DESIGNATED_INITIALIZER;
/**
* Assign list of URLs to let SDWebImagePrefetcher to queue the prefetching,
* currently one image is downloaded at a time,
* and skips images for failed downloads and proceed to the next image in the list.
* Any previously-running prefetch operations are canceled.
*
* @param urls list of URLs to prefetch
*/
- (void)prefetchURLs:(nullable NSArray<NSURL *> *)urls;
/**
* Assign list of URLs to let SDWebImagePrefetcher to queue the prefetching,
* currently one image is downloaded at a time,
* and skips images for failed downloads and proceed to the next image in the list.
* Any previously-running prefetch operations are canceled.
*
* @param urls list of URLs to prefetch
* @param progressBlock block to be called when progress updates;
* first parameter is the number of completed (successful or not) requests,
* second parameter is the total number of images originally requested to be prefetched
* @param completionBlock block to be called when prefetching is completed
* first param is the number of completed (successful or not) requests,
* second parameter is the number of skipped requests
*/
- (void)prefetchURLs:(nullable NSArray<NSURL *> *)urls
progress:(nullable SDWebImagePrefetcherProgressBlock)progressBlock
completed:(nullable SDWebImagePrefetcherCompletionBlock)completionBlock;
/**
* Remove and cancel queued list
*/
- (void)cancelPrefetching;
@end

View File

@@ -0,0 +1,142 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "SDWebImagePrefetcher.h"
@interface SDWebImagePrefetcher ()
@property (strong, nonatomic, nonnull) SDWebImageManager *manager;
@property (strong, nonatomic, nullable) NSArray<NSURL *> *prefetchURLs;
@property (assign, nonatomic) NSUInteger requestedCount;
@property (assign, nonatomic) NSUInteger skippedCount;
@property (assign, nonatomic) NSUInteger finishedCount;
@property (assign, nonatomic) NSTimeInterval startedTime;
@property (copy, nonatomic, nullable) SDWebImagePrefetcherCompletionBlock completionBlock;
@property (copy, nonatomic, nullable) SDWebImagePrefetcherProgressBlock progressBlock;
@end
@implementation SDWebImagePrefetcher
+ (nonnull instancetype)sharedImagePrefetcher {
static dispatch_once_t once;
static id instance;
dispatch_once(&once, ^{
instance = [self new];
});
return instance;
}
- (nonnull instancetype)init {
return [self initWithImageManager:[SDWebImageManager new]];
}
- (nonnull instancetype)initWithImageManager:(SDWebImageManager *)manager {
if ((self = [super init])) {
_manager = manager;
_options = SDWebImageLowPriority;
_prefetcherQueue = dispatch_get_main_queue();
self.maxConcurrentDownloads = 3;
}
return self;
}
- (void)setMaxConcurrentDownloads:(NSUInteger)maxConcurrentDownloads {
self.manager.imageDownloader.maxConcurrentDownloads = maxConcurrentDownloads;
}
- (NSUInteger)maxConcurrentDownloads {
return self.manager.imageDownloader.maxConcurrentDownloads;
}
- (void)startPrefetchingAtIndex:(NSUInteger)index {
if (index >= self.prefetchURLs.count) return;
self.requestedCount++;
[self.manager loadImageWithURL:self.prefetchURLs[index] options:self.options progress:nil completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
if (!finished) return;
self.finishedCount++;
if (image) {
if (self.progressBlock) {
self.progressBlock(self.finishedCount,(self.prefetchURLs).count);
}
}
else {
if (self.progressBlock) {
self.progressBlock(self.finishedCount,(self.prefetchURLs).count);
}
// Add last failed
self.skippedCount++;
}
if ([self.delegate respondsToSelector:@selector(imagePrefetcher:didPrefetchURL:finishedCount:totalCount:)]) {
[self.delegate imagePrefetcher:self
didPrefetchURL:self.prefetchURLs[index]
finishedCount:self.finishedCount
totalCount:self.prefetchURLs.count
];
}
if (self.prefetchURLs.count > self.requestedCount) {
dispatch_async(self.prefetcherQueue, ^{
[self startPrefetchingAtIndex:self.requestedCount];
});
} else if (self.finishedCount == self.requestedCount) {
[self reportStatus];
if (self.completionBlock) {
self.completionBlock(self.finishedCount, self.skippedCount);
self.completionBlock = nil;
}
self.progressBlock = nil;
}
}];
}
- (void)reportStatus {
NSUInteger total = (self.prefetchURLs).count;
if ([self.delegate respondsToSelector:@selector(imagePrefetcher:didFinishWithTotalCount:skippedCount:)]) {
[self.delegate imagePrefetcher:self
didFinishWithTotalCount:(total - self.skippedCount)
skippedCount:self.skippedCount
];
}
}
- (void)prefetchURLs:(nullable NSArray<NSURL *> *)urls {
[self prefetchURLs:urls progress:nil completed:nil];
}
- (void)prefetchURLs:(nullable NSArray<NSURL *> *)urls
progress:(nullable SDWebImagePrefetcherProgressBlock)progressBlock
completed:(nullable SDWebImagePrefetcherCompletionBlock)completionBlock {
[self cancelPrefetching]; // Prevent duplicate prefetch request
self.startedTime = CFAbsoluteTimeGetCurrent();
self.prefetchURLs = urls;
self.completionBlock = completionBlock;
self.progressBlock = progressBlock;
if (urls.count == 0) {
if (completionBlock) {
completionBlock(0,0);
}
} else {
// Starts prefetching from the very first image on the list with the max allowed concurrency
NSUInteger listCount = self.prefetchURLs.count;
for (NSUInteger i = 0; i < self.maxConcurrentDownloads && self.requestedCount < listCount; i++) {
[self startPrefetchingAtIndex:i];
}
}
}
- (void)cancelPrefetching {
self.prefetchURLs = nil;
self.skippedCount = 0;
self.requestedCount = 0;
self.finishedCount = 0;
[self.manager cancelAll];
}
@end

View File

@@ -0,0 +1,243 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "SDWebImageCompat.h"
#if SD_UIKIT
#import "SDWebImageManager.h"
/**
* Integrates SDWebImage async downloading and caching of remote images with UIButtonView.
*/
@interface UIButton (WebCache)
/**
* Get the current image URL.
*/
- (nullable NSURL *)sd_currentImageURL;
#pragma mark - Image
/**
* Get the image URL for a control state.
*
* @param state Which state you want to know the URL for. The values are described in UIControlState.
*/
- (nullable NSURL *)sd_imageURLForState:(UIControlState)state;
/**
* Set the imageView `image` with an `url`.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param state The state that uses the specified title. The values are described in UIControlState.
*/
- (void)sd_setImageWithURL:(nullable NSURL *)url
forState:(UIControlState)state;
/**
* Set the imageView `image` with an `url` and a placeholder.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param state The state that uses the specified title. The values are described in UIControlState.
* @param placeholder The image to be set initially, until the image request finishes.
* @see sd_setImageWithURL:placeholderImage:options:
*/
- (void)sd_setImageWithURL:(nullable NSURL *)url
forState:(UIControlState)state
placeholderImage:(nullable UIImage *)placeholder;
/**
* Set the imageView `image` with an `url`, placeholder and custom options.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param state The state that uses the specified title. The values are described in UIControlState.
* @param placeholder The image to be set initially, until the image request finishes.
* @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values.
*/
- (void)sd_setImageWithURL:(nullable NSURL *)url
forState:(UIControlState)state
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options;
/**
* Set the imageView `image` with an `url`.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param state The state that uses the specified title. The values are described in UIControlState.
* @param completedBlock A block called when operation has been completed. This block has no return value
* and takes the requested UIImage as first parameter. In case of error the image parameter
* is nil and the second parameter may contain an NSError. The third parameter is a Boolean
* indicating if the image was retrieved from the local cache or from the network.
* The fourth parameter is the original image url.
*/
- (void)sd_setImageWithURL:(nullable NSURL *)url
forState:(UIControlState)state
completed:(nullable SDExternalCompletionBlock)completedBlock;
/**
* Set the imageView `image` with an `url`, placeholder.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param state The state that uses the specified title. The values are described in UIControlState.
* @param placeholder The image to be set initially, until the image request finishes.
* @param completedBlock A block called when operation has been completed. This block has no return value
* and takes the requested UIImage as first parameter. In case of error the image parameter
* is nil and the second parameter may contain an NSError. The third parameter is a Boolean
* indicating if the image was retrieved from the local cache or from the network.
* The fourth parameter is the original image url.
*/
- (void)sd_setImageWithURL:(nullable NSURL *)url
forState:(UIControlState)state
placeholderImage:(nullable UIImage *)placeholder
completed:(nullable SDExternalCompletionBlock)completedBlock;
/**
* Set the imageView `image` with an `url`, placeholder and custom options.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param state The state that uses the specified title. The values are described in UIControlState.
* @param placeholder The image to be set initially, until the image request finishes.
* @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values.
* @param completedBlock A block called when operation has been completed. This block has no return value
* and takes the requested UIImage as first parameter. In case of error the image parameter
* is nil and the second parameter may contain an NSError. The third parameter is a Boolean
* indicating if the image was retrieved from the local cache or from the network.
* The fourth parameter is the original image url.
*/
- (void)sd_setImageWithURL:(nullable NSURL *)url
forState:(UIControlState)state
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
completed:(nullable SDExternalCompletionBlock)completedBlock;
#pragma mark - Background image
/**
* Set the backgroundImageView `image` with an `url`.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param state The state that uses the specified title. The values are described in UIControlState.
*/
- (void)sd_setBackgroundImageWithURL:(nullable NSURL *)url
forState:(UIControlState)state;
/**
* Set the backgroundImageView `image` with an `url` and a placeholder.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param state The state that uses the specified title. The values are described in UIControlState.
* @param placeholder The image to be set initially, until the image request finishes.
* @see sd_setImageWithURL:placeholderImage:options:
*/
- (void)sd_setBackgroundImageWithURL:(nullable NSURL *)url
forState:(UIControlState)state
placeholderImage:(nullable UIImage *)placeholder;
/**
* Set the backgroundImageView `image` with an `url`, placeholder and custom options.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param state The state that uses the specified title. The values are described in UIControlState.
* @param placeholder The image to be set initially, until the image request finishes.
* @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values.
*/
- (void)sd_setBackgroundImageWithURL:(nullable NSURL *)url
forState:(UIControlState)state
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options;
/**
* Set the backgroundImageView `image` with an `url`.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param state The state that uses the specified title. The values are described in UIControlState.
* @param completedBlock A block called when operation has been completed. This block has no return value
* and takes the requested UIImage as first parameter. In case of error the image parameter
* is nil and the second parameter may contain an NSError. The third parameter is a Boolean
* indicating if the image was retrieved from the local cache or from the network.
* The fourth parameter is the original image url.
*/
- (void)sd_setBackgroundImageWithURL:(nullable NSURL *)url
forState:(UIControlState)state
completed:(nullable SDExternalCompletionBlock)completedBlock;
/**
* Set the backgroundImageView `image` with an `url`, placeholder.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param state The state that uses the specified title. The values are described in UIControlState.
* @param placeholder The image to be set initially, until the image request finishes.
* @param completedBlock A block called when operation has been completed. This block has no return value
* and takes the requested UIImage as first parameter. In case of error the image parameter
* is nil and the second parameter may contain an NSError. The third parameter is a Boolean
* indicating if the image was retrieved from the local cache or from the network.
* The fourth parameter is the original image url.
*/
- (void)sd_setBackgroundImageWithURL:(nullable NSURL *)url
forState:(UIControlState)state
placeholderImage:(nullable UIImage *)placeholder
completed:(nullable SDExternalCompletionBlock)completedBlock;
/**
* Set the backgroundImageView `image` with an `url`, placeholder and custom options.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param placeholder The image to be set initially, until the image request finishes.
* @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values.
* @param completedBlock A block called when operation has been completed. This block has no return value
* and takes the requested UIImage as first parameter. In case of error the image parameter
* is nil and the second parameter may contain an NSError. The third parameter is a Boolean
* indicating if the image was retrieved from the local cache or from the network.
* The fourth parameter is the original image url.
*/
- (void)sd_setBackgroundImageWithURL:(nullable NSURL *)url
forState:(UIControlState)state
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
completed:(nullable SDExternalCompletionBlock)completedBlock;
#pragma mark - Cancel
/**
* Cancel the current image download
*/
- (void)sd_cancelImageLoadForState:(UIControlState)state;
/**
* Cancel the current backgroundImage download
*/
- (void)sd_cancelBackgroundImageLoadForState:(UIControlState)state;
@end
#endif

View File

@@ -0,0 +1,157 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "UIButton+WebCache.h"
#if SD_UIKIT
#import "objc/runtime.h"
#import "UIView+WebCacheOperation.h"
#import "UIView+WebCache.h"
static char imageURLStorageKey;
typedef NSMutableDictionary<NSNumber *, NSURL *> SDStateImageURLDictionary;
@implementation UIButton (WebCache)
- (nullable NSURL *)sd_currentImageURL {
NSURL *url = self.imageURLStorage[@(self.state)];
if (!url) {
url = self.imageURLStorage[@(UIControlStateNormal)];
}
return url;
}
- (nullable NSURL *)sd_imageURLForState:(UIControlState)state {
return self.imageURLStorage[@(state)];
}
#pragma mark - Image
- (void)sd_setImageWithURL:(nullable NSURL *)url forState:(UIControlState)state {
[self sd_setImageWithURL:url forState:state placeholderImage:nil options:0 completed:nil];
}
- (void)sd_setImageWithURL:(nullable NSURL *)url forState:(UIControlState)state placeholderImage:(nullable UIImage *)placeholder {
[self sd_setImageWithURL:url forState:state placeholderImage:placeholder options:0 completed:nil];
}
- (void)sd_setImageWithURL:(nullable NSURL *)url forState:(UIControlState)state placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options {
[self sd_setImageWithURL:url forState:state placeholderImage:placeholder options:options completed:nil];
}
- (void)sd_setImageWithURL:(nullable NSURL *)url forState:(UIControlState)state completed:(nullable SDExternalCompletionBlock)completedBlock {
[self sd_setImageWithURL:url forState:state placeholderImage:nil options:0 completed:completedBlock];
}
- (void)sd_setImageWithURL:(nullable NSURL *)url forState:(UIControlState)state placeholderImage:(nullable UIImage *)placeholder completed:(nullable SDExternalCompletionBlock)completedBlock {
[self sd_setImageWithURL:url forState:state placeholderImage:placeholder options:0 completed:completedBlock];
}
- (void)sd_setImageWithURL:(nullable NSURL *)url
forState:(UIControlState)state
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
completed:(nullable SDExternalCompletionBlock)completedBlock {
if (!url) {
[self.imageURLStorage removeObjectForKey:@(state)];
return;
}
self.imageURLStorage[@(state)] = url;
__weak typeof(self)weakSelf = self;
[self sd_internalSetImageWithURL:url
placeholderImage:placeholder
options:options
operationKey:[NSString stringWithFormat:@"UIButtonImageOperation%@", @(state)]
setImageBlock:^(UIImage *image, NSData *imageData) {
[weakSelf setImage:image forState:state];
}
progress:nil
completed:completedBlock];
}
#pragma mark - Background image
- (void)sd_setBackgroundImageWithURL:(nullable NSURL *)url forState:(UIControlState)state {
[self sd_setBackgroundImageWithURL:url forState:state placeholderImage:nil options:0 completed:nil];
}
- (void)sd_setBackgroundImageWithURL:(nullable NSURL *)url forState:(UIControlState)state placeholderImage:(nullable UIImage *)placeholder {
[self sd_setBackgroundImageWithURL:url forState:state placeholderImage:placeholder options:0 completed:nil];
}
- (void)sd_setBackgroundImageWithURL:(nullable NSURL *)url forState:(UIControlState)state placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options {
[self sd_setBackgroundImageWithURL:url forState:state placeholderImage:placeholder options:options completed:nil];
}
- (void)sd_setBackgroundImageWithURL:(nullable NSURL *)url forState:(UIControlState)state completed:(nullable SDExternalCompletionBlock)completedBlock {
[self sd_setBackgroundImageWithURL:url forState:state placeholderImage:nil options:0 completed:completedBlock];
}
- (void)sd_setBackgroundImageWithURL:(nullable NSURL *)url forState:(UIControlState)state placeholderImage:(nullable UIImage *)placeholder completed:(nullable SDExternalCompletionBlock)completedBlock {
[self sd_setBackgroundImageWithURL:url forState:state placeholderImage:placeholder options:0 completed:completedBlock];
}
- (void)sd_setBackgroundImageWithURL:(nullable NSURL *)url
forState:(UIControlState)state
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
completed:(nullable SDExternalCompletionBlock)completedBlock {
if (!url) {
[self.imageURLStorage removeObjectForKey:@(state)];
return;
}
self.imageURLStorage[@(state)] = url;
__weak typeof(self)weakSelf = self;
[self sd_internalSetImageWithURL:url
placeholderImage:placeholder
options:options
operationKey:[NSString stringWithFormat:@"UIButtonBackgroundImageOperation%@", @(state)]
setImageBlock:^(UIImage *image, NSData *imageData) {
[weakSelf setBackgroundImage:image forState:state];
}
progress:nil
completed:completedBlock];
}
- (void)sd_setImageLoadOperation:(id<SDWebImageOperation>)operation forState:(UIControlState)state {
[self sd_setImageLoadOperation:operation forKey:[NSString stringWithFormat:@"UIButtonImageOperation%@", @(state)]];
}
- (void)sd_cancelImageLoadForState:(UIControlState)state {
[self sd_cancelImageLoadOperationWithKey:[NSString stringWithFormat:@"UIButtonImageOperation%@", @(state)]];
}
- (void)sd_setBackgroundImageLoadOperation:(id<SDWebImageOperation>)operation forState:(UIControlState)state {
[self sd_setImageLoadOperation:operation forKey:[NSString stringWithFormat:@"UIButtonBackgroundImageOperation%@", @(state)]];
}
- (void)sd_cancelBackgroundImageLoadForState:(UIControlState)state {
[self sd_cancelImageLoadOperationWithKey:[NSString stringWithFormat:@"UIButtonBackgroundImageOperation%@", @(state)]];
}
- (SDStateImageURLDictionary *)imageURLStorage {
SDStateImageURLDictionary *storage = objc_getAssociatedObject(self, &imageURLStorageKey);
if (!storage) {
storage = [NSMutableDictionary dictionary];
objc_setAssociatedObject(self, &imageURLStorageKey, storage, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return storage;
}
@end
#endif

24
Pods/SDWebImage/SDWebImage/UIImage+GIF.h generated Executable file
View File

@@ -0,0 +1,24 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
* (c) Laurin Brandner
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "SDWebImageCompat.h"
@interface UIImage (GIF)
/**
* Compatibility method - creates an animated UIImage from an NSData, it will only contain the 1st frame image
*/
+ (UIImage *)sd_animatedGIFWithData:(NSData *)data;
/**
* Checks if an UIImage instance is a GIF. Will use the `images` array
*/
- (BOOL)isGIF;
@end

60
Pods/SDWebImage/SDWebImage/UIImage+GIF.m generated Executable file
View File

@@ -0,0 +1,60 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
* (c) Laurin Brandner
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "UIImage+GIF.h"
#import <ImageIO/ImageIO.h>
#import "objc/runtime.h"
#import "NSImage+WebCache.h"
@implementation UIImage (GIF)
+ (UIImage *)sd_animatedGIFWithData:(NSData *)data {
if (!data) {
return nil;
}
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
size_t count = CGImageSourceGetCount(source);
UIImage *staticImage;
if (count <= 1) {
staticImage = [[UIImage alloc] initWithData:data];
} else {
// we will only retrieve the 1st frame. the full GIF support is available via the FLAnimatedImageView category.
// this here is only code to allow drawing animated images as static ones
#if SD_WATCH
CGFloat scale = 1;
scale = [WKInterfaceDevice currentDevice].screenScale;
#elif SD_UIKIT
CGFloat scale = 1;
scale = [UIScreen mainScreen].scale;
#endif
CGImageRef CGImage = CGImageSourceCreateImageAtIndex(source, 0, NULL);
#if SD_UIKIT || SD_WATCH
UIImage *frameImage = [UIImage imageWithCGImage:CGImage scale:scale orientation:UIImageOrientationUp];
staticImage = [UIImage animatedImageWithImages:@[frameImage] duration:0.0f];
#elif SD_MAC
staticImage = [[UIImage alloc] initWithCGImage:CGImage size:NSZeroSize];
#endif
CGImageRelease(CGImage);
}
CFRelease(source);
return staticImage;
}
- (BOOL)isGIF {
return (self.images != nil);
}
@end

View File

@@ -0,0 +1,18 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "SDWebImageCompat.h"
#import "NSData+ImageContentType.h"
@interface UIImage (MultiFormat)
+ (nullable UIImage *)sd_imageWithData:(nullable NSData *)data;
- (nullable NSData *)sd_imageData;
- (nullable NSData *)sd_imageDataAsFormat:(SDImageFormat)imageFormat;
@end

View File

@@ -0,0 +1,161 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "UIImage+MultiFormat.h"
#import "UIImage+GIF.h"
#import "NSData+ImageContentType.h"
#import <ImageIO/ImageIO.h>
#ifdef SD_WEBP
#import "UIImage+WebP.h"
#endif
@implementation UIImage (MultiFormat)
+ (nullable UIImage *)sd_imageWithData:(nullable NSData *)data {
if (!data) {
return nil;
}
UIImage *image;
SDImageFormat imageFormat = [NSData sd_imageFormatForImageData:data];
if (imageFormat == SDImageFormatGIF) {
image = [UIImage sd_animatedGIFWithData:data];
}
#ifdef SD_WEBP
else if (imageFormat == SDImageFormatWebP)
{
image = [UIImage sd_imageWithWebPData:data];
}
#endif
else {
image = [[UIImage alloc] initWithData:data];
#if SD_UIKIT || SD_WATCH
UIImageOrientation orientation = [self sd_imageOrientationFromImageData:data];
if (orientation != UIImageOrientationUp) {
image = [UIImage imageWithCGImage:image.CGImage
scale:image.scale
orientation:orientation];
}
#endif
}
return image;
}
#if SD_UIKIT || SD_WATCH
+(UIImageOrientation)sd_imageOrientationFromImageData:(nonnull NSData *)imageData {
UIImageOrientation result = UIImageOrientationUp;
CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)imageData, NULL);
if (imageSource) {
CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, NULL);
if (properties) {
CFTypeRef val;
int exifOrientation;
val = CFDictionaryGetValue(properties, kCGImagePropertyOrientation);
if (val) {
CFNumberGetValue(val, kCFNumberIntType, &exifOrientation);
result = [self sd_exifOrientationToiOSOrientation:exifOrientation];
} // else - if it's not set it remains at up
CFRelease((CFTypeRef) properties);
} else {
//NSLog(@"NO PROPERTIES, FAIL");
}
CFRelease(imageSource);
}
return result;
}
#pragma mark EXIF orientation tag converter
// Convert an EXIF image orientation to an iOS one.
// reference see here: http://sylvana.net/jpegcrop/exif_orientation.html
+ (UIImageOrientation) sd_exifOrientationToiOSOrientation:(int)exifOrientation {
UIImageOrientation orientation = UIImageOrientationUp;
switch (exifOrientation) {
case 1:
orientation = UIImageOrientationUp;
break;
case 3:
orientation = UIImageOrientationDown;
break;
case 8:
orientation = UIImageOrientationLeft;
break;
case 6:
orientation = UIImageOrientationRight;
break;
case 2:
orientation = UIImageOrientationUpMirrored;
break;
case 4:
orientation = UIImageOrientationDownMirrored;
break;
case 5:
orientation = UIImageOrientationLeftMirrored;
break;
case 7:
orientation = UIImageOrientationRightMirrored;
break;
default:
break;
}
return orientation;
}
#endif
- (nullable NSData *)sd_imageData {
return [self sd_imageDataAsFormat:SDImageFormatUndefined];
}
- (nullable NSData *)sd_imageDataAsFormat:(SDImageFormat)imageFormat {
NSData *imageData = nil;
if (self) {
#if SD_UIKIT || SD_WATCH
int alphaInfo = CGImageGetAlphaInfo(self.CGImage);
BOOL hasAlpha = !(alphaInfo == kCGImageAlphaNone ||
alphaInfo == kCGImageAlphaNoneSkipFirst ||
alphaInfo == kCGImageAlphaNoneSkipLast);
BOOL usePNG = hasAlpha;
// the imageFormat param has priority here. But if the format is undefined, we relly on the alpha channel
if (imageFormat != SDImageFormatUndefined) {
usePNG = (imageFormat == SDImageFormatPNG);
}
if (usePNG) {
imageData = UIImagePNGRepresentation(self);
} else {
imageData = UIImageJPEGRepresentation(self, (CGFloat)1.0);
}
#else
NSBitmapImageFileType imageFileType = NSJPEGFileType;
if (imageFormat == SDImageFormatGIF) {
imageFileType = NSGIFFileType;
} else if (imageFormat == SDImageFormatPNG) {
imageFileType = NSPNGFileType;
}
imageData = [NSBitmapImageRep representationOfImageRepsInArray:self.representations
usingType:imageFileType
properties:@{}];
#endif
}
return imageData;
}
@end

View File

@@ -0,0 +1,94 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "SDWebImageCompat.h"
#if SD_UIKIT
#import "SDWebImageManager.h"
/**
* Integrates SDWebImage async downloading and caching of remote images with UIImageView for highlighted state.
*/
@interface UIImageView (HighlightedWebCache)
/**
* Set the imageView `highlightedImage` with an `url`.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
*/
- (void)sd_setHighlightedImageWithURL:(nullable NSURL *)url;
/**
* Set the imageView `highlightedImage` with an `url` and custom options.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values.
*/
- (void)sd_setHighlightedImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options;
/**
* Set the imageView `highlightedImage` with an `url`.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param completedBlock A block called when operation has been completed. This block has no return value
* and takes the requested UIImage as first parameter. In case of error the image parameter
* is nil and the second parameter may contain an NSError. The third parameter is a Boolean
* indicating if the image was retrieved from the local cache or from the network.
* The fourth parameter is the original image url.
*/
- (void)sd_setHighlightedImageWithURL:(nullable NSURL *)url
completed:(nullable SDExternalCompletionBlock)completedBlock;
/**
* Set the imageView `highlightedImage` with an `url` and custom options.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values.
* @param completedBlock A block called when operation has been completed. This block has no return value
* and takes the requested UIImage as first parameter. In case of error the image parameter
* is nil and the second parameter may contain an NSError. The third parameter is a Boolean
* indicating if the image was retrieved from the local cache or from the network.
* The fourth parameter is the original image url.
*/
- (void)sd_setHighlightedImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
completed:(nullable SDExternalCompletionBlock)completedBlock;
/**
* Set the imageView `highlightedImage` with an `url` and custom options.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values.
* @param progressBlock A block called while image is downloading
* @note the progress block is executed on a background queue
* @param completedBlock A block called when operation has been completed. This block has no return value
* and takes the requested UIImage as first parameter. In case of error the image parameter
* is nil and the second parameter may contain an NSError. The third parameter is a Boolean
* indicating if the image was retrieved from the local cache or from the network.
* The fourth parameter is the original image url.
*/
- (void)sd_setHighlightedImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock;
@end
#endif

View File

@@ -0,0 +1,52 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "UIImageView+HighlightedWebCache.h"
#if SD_UIKIT
#import "UIView+WebCacheOperation.h"
#import "UIView+WebCache.h"
@implementation UIImageView (HighlightedWebCache)
- (void)sd_setHighlightedImageWithURL:(nullable NSURL *)url {
[self sd_setHighlightedImageWithURL:url options:0 progress:nil completed:nil];
}
- (void)sd_setHighlightedImageWithURL:(nullable NSURL *)url options:(SDWebImageOptions)options {
[self sd_setHighlightedImageWithURL:url options:options progress:nil completed:nil];
}
- (void)sd_setHighlightedImageWithURL:(nullable NSURL *)url completed:(nullable SDExternalCompletionBlock)completedBlock {
[self sd_setHighlightedImageWithURL:url options:0 progress:nil completed:completedBlock];
}
- (void)sd_setHighlightedImageWithURL:(nullable NSURL *)url options:(SDWebImageOptions)options completed:(nullable SDExternalCompletionBlock)completedBlock {
[self sd_setHighlightedImageWithURL:url options:options progress:nil completed:completedBlock];
}
- (void)sd_setHighlightedImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock {
__weak typeof(self)weakSelf = self;
[self sd_internalSetImageWithURL:url
placeholderImage:nil
options:options
operationKey:@"UIImageViewImageOperationHighlighted"
setImageBlock:^(UIImage *image, NSData *imageData) {
weakSelf.highlightedImage = image;
}
progress:progressBlock
completed:completedBlock];
}
@end
#endif

View File

@@ -0,0 +1,196 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "SDWebImageCompat.h"
#if SD_UIKIT || SD_MAC
#import "SDWebImageManager.h"
/**
* Integrates SDWebImage async downloading and caching of remote images with UIImageView.
*
* Usage with a UITableViewCell sub-class:
*
* @code
#import <SDWebImage/UIImageView+WebCache.h>
...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *MyIdentifier = @"MyIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyIdentifier]
autorelease];
}
// Here we use the provided sd_setImageWithURL: method to load the web image
// Ensure you use a placeholder image otherwise cells will be initialized with no image
[cell.imageView sd_setImageWithURL:[NSURL URLWithString:@"http://example.com/image.jpg"]
placeholderImage:[UIImage imageNamed:@"placeholder"]];
cell.textLabel.text = @"My Text";
return cell;
}
* @endcode
*/
@interface UIImageView (WebCache)
/**
* Set the imageView `image` with an `url`.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
*/
- (void)sd_setImageWithURL:(nullable NSURL *)url;
/**
* Set the imageView `image` with an `url` and a placeholder.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param placeholder The image to be set initially, until the image request finishes.
* @see sd_setImageWithURL:placeholderImage:options:
*/
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder;
/**
* Set the imageView `image` with an `url`, placeholder and custom options.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param placeholder The image to be set initially, until the image request finishes.
* @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values.
*/
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options;
/**
* Set the imageView `image` with an `url`.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param completedBlock A block called when operation has been completed. This block has no return value
* and takes the requested UIImage as first parameter. In case of error the image parameter
* is nil and the second parameter may contain an NSError. The third parameter is a Boolean
* indicating if the image was retrieved from the local cache or from the network.
* The fourth parameter is the original image url.
*/
- (void)sd_setImageWithURL:(nullable NSURL *)url
completed:(nullable SDExternalCompletionBlock)completedBlock;
/**
* Set the imageView `image` with an `url`, placeholder.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param placeholder The image to be set initially, until the image request finishes.
* @param completedBlock A block called when operation has been completed. This block has no return value
* and takes the requested UIImage as first parameter. In case of error the image parameter
* is nil and the second parameter may contain an NSError. The third parameter is a Boolean
* indicating if the image was retrieved from the local cache or from the network.
* The fourth parameter is the original image url.
*/
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
completed:(nullable SDExternalCompletionBlock)completedBlock;
/**
* Set the imageView `image` with an `url`, placeholder and custom options.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param placeholder The image to be set initially, until the image request finishes.
* @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values.
* @param completedBlock A block called when operation has been completed. This block has no return value
* and takes the requested UIImage as first parameter. In case of error the image parameter
* is nil and the second parameter may contain an NSError. The third parameter is a Boolean
* indicating if the image was retrieved from the local cache or from the network.
* The fourth parameter is the original image url.
*/
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
completed:(nullable SDExternalCompletionBlock)completedBlock;
/**
* Set the imageView `image` with an `url`, placeholder and custom options.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param placeholder The image to be set initially, until the image request finishes.
* @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values.
* @param progressBlock A block called while image is downloading
* @note the progress block is executed on a background queue
* @param completedBlock A block called when operation has been completed. This block has no return value
* and takes the requested UIImage as first parameter. In case of error the image parameter
* is nil and the second parameter may contain an NSError. The third parameter is a Boolean
* indicating if the image was retrieved from the local cache or from the network.
* The fourth parameter is the original image url.
*/
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock;
/**
* Set the imageView `image` with an `url` and optionally a placeholder image.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param placeholder The image to be set initially, until the image request finishes.
* @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values.
* @param progressBlock A block called while image is downloading
* @note the progress block is executed on a background queue
* @param completedBlock A block called when operation has been completed. This block has no return value
* and takes the requested UIImage as first parameter. In case of error the image parameter
* is nil and the second parameter may contain an NSError. The third parameter is a Boolean
* indicating if the image was retrieved from the local cache or from the network.
* The fourth parameter is the original image url.
*/
- (void)sd_setImageWithPreviousCachedImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock;
#if SD_UIKIT
#pragma mark - Animation of multiple images
/**
* Download an array of images and starts them in an animation loop
*
* @param arrayOfURLs An array of NSURL
*/
- (void)sd_setAnimationImagesWithURLs:(nonnull NSArray<NSURL *> *)arrayOfURLs;
- (void)sd_cancelCurrentAnimationImagesLoad;
#endif
@end
#endif

View File

@@ -0,0 +1,110 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "UIImageView+WebCache.h"
#if SD_UIKIT || SD_MAC
#import "objc/runtime.h"
#import "UIView+WebCacheOperation.h"
#import "UIView+WebCache.h"
@implementation UIImageView (WebCache)
- (void)sd_setImageWithURL:(nullable NSURL *)url {
[self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:nil];
}
- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder {
[self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:nil];
}
- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options {
[self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:nil];
}
- (void)sd_setImageWithURL:(nullable NSURL *)url completed:(nullable SDExternalCompletionBlock)completedBlock {
[self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:completedBlock];
}
- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder completed:(nullable SDExternalCompletionBlock)completedBlock {
[self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:completedBlock];
}
- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options completed:(nullable SDExternalCompletionBlock)completedBlock {
[self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:completedBlock];
}
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock {
[self sd_internalSetImageWithURL:url
placeholderImage:placeholder
options:options
operationKey:nil
setImageBlock:nil
progress:progressBlock
completed:completedBlock];
}
- (void)sd_setImageWithPreviousCachedImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock {
NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:url];
UIImage *lastPreviousCachedImage = [[SDImageCache sharedImageCache] imageFromCacheForKey:key];
[self sd_setImageWithURL:url placeholderImage:lastPreviousCachedImage ?: placeholder options:options progress:progressBlock completed:completedBlock];
}
#if SD_UIKIT
#pragma mark - Animation of multiple images
- (void)sd_setAnimationImagesWithURLs:(nonnull NSArray<NSURL *> *)arrayOfURLs {
[self sd_cancelCurrentAnimationImagesLoad];
__weak __typeof(self)wself = self;
NSMutableArray<id<SDWebImageOperation>> *operationsArray = [[NSMutableArray alloc] init];
for (NSURL *logoImageURL in arrayOfURLs) {
id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager loadImageWithURL:logoImageURL options:0 progress:nil completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
if (!wself) return;
dispatch_main_async_safe(^{
__strong UIImageView *sself = wself;
[sself stopAnimating];
if (sself && image) {
NSMutableArray<UIImage *> *currentImages = [[sself animationImages] mutableCopy];
if (!currentImages) {
currentImages = [[NSMutableArray alloc] init];
}
[currentImages addObject:image];
sself.animationImages = currentImages;
[sself setNeedsLayout];
}
[sself startAnimating];
});
}];
[operationsArray addObject:operation];
}
[self sd_setImageLoadOperation:[operationsArray copy] forKey:@"UIImageViewAnimationImages"];
}
- (void)sd_cancelCurrentAnimationImagesLoad {
[self sd_cancelImageLoadOperationWithKey:@"UIImageViewAnimationImages"];
}
#endif
@end
#endif

View File

@@ -0,0 +1,82 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "SDWebImageCompat.h"
#if SD_UIKIT || SD_MAC
#import "SDWebImageManager.h"
typedef void(^SDSetImageBlock)(UIImage * _Nullable image, NSData * _Nullable imageData);
@interface UIView (WebCache)
/**
* Get the current image URL.
*
* Note that because of the limitations of categories this property can get out of sync
* if you use setImage: directly.
*/
- (nullable NSURL *)sd_imageURL;
/**
* Set the imageView `image` with an `url` and optionally a placeholder image.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param placeholder The image to be set initially, until the image request finishes.
* @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values.
* @param operationKey A string to be used as the operation key. If nil, will use the class name
* @param setImageBlock Block used for custom set image code
* @param progressBlock A block called while image is downloading
* @note the progress block is executed on a background queue
* @param completedBlock A block called when operation has been completed. This block has no return value
* and takes the requested UIImage as first parameter. In case of error the image parameter
* is nil and the second parameter may contain an NSError. The third parameter is a Boolean
* indicating if the image was retrieved from the local cache or from the network.
* The fourth parameter is the original image url.
*/
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
operationKey:(nullable NSString *)operationKey
setImageBlock:(nullable SDSetImageBlock)setImageBlock
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock;
/**
* Cancel the current download
*/
- (void)sd_cancelCurrentImageLoad;
#if SD_UIKIT
#pragma mark - Activity indicator
/**
* Show activity UIActivityIndicatorView
*/
- (void)sd_setShowActivityIndicatorView:(BOOL)show;
/**
* set desired UIActivityIndicatorViewStyle
*
* @param style The style of the UIActivityIndicatorView
*/
- (void)sd_setIndicatorStyle:(UIActivityIndicatorViewStyle)style;
- (BOOL)sd_showActivityIndicatorView;
- (void)sd_addActivityIndicator;
- (void)sd_removeActivityIndicator;
#endif
@end
#endif

View File

@@ -0,0 +1,200 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "UIView+WebCache.h"
#if SD_UIKIT || SD_MAC
#import "objc/runtime.h"
#import "UIView+WebCacheOperation.h"
static char imageURLKey;
#if SD_UIKIT
static char TAG_ACTIVITY_INDICATOR;
static char TAG_ACTIVITY_STYLE;
#endif
static char TAG_ACTIVITY_SHOW;
@implementation UIView (WebCache)
- (nullable NSURL *)sd_imageURL {
return objc_getAssociatedObject(self, &imageURLKey);
}
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
operationKey:(nullable NSString *)operationKey
setImageBlock:(nullable SDSetImageBlock)setImageBlock
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock {
NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
if (!(options & SDWebImageDelayPlaceholder)) {
dispatch_main_async_safe(^{
[self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
});
}
if (url) {
// check if activityView is enabled or not
if ([self sd_showActivityIndicatorView]) {
[self sd_addActivityIndicator];
}
__weak __typeof(self)wself = self;
id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager loadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
__strong __typeof (wself) sself = wself;
[sself sd_removeActivityIndicator];
if (!sself) {
return;
}
dispatch_main_async_safe(^{
if (!sself) {
return;
}
if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock) {
completedBlock(image, error, cacheType, url);
return;
} else if (image) {
[sself sd_setImage:image imageData:data basedOnClassOrViaCustomSetImageBlock:setImageBlock];
[sself sd_setNeedsLayout];
} else {
if ((options & SDWebImageDelayPlaceholder)) {
[sself sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
[sself sd_setNeedsLayout];
}
}
if (completedBlock && finished) {
completedBlock(image, error, cacheType, url);
}
});
}];
[self sd_setImageLoadOperation:operation forKey:validOperationKey];
} else {
dispatch_main_async_safe(^{
[self sd_removeActivityIndicator];
if (completedBlock) {
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
completedBlock(nil, error, SDImageCacheTypeNone, url);
}
});
}
}
- (void)sd_cancelCurrentImageLoad {
[self sd_cancelImageLoadOperationWithKey:NSStringFromClass([self class])];
}
- (void)sd_setImage:(UIImage *)image imageData:(NSData *)imageData basedOnClassOrViaCustomSetImageBlock:(SDSetImageBlock)setImageBlock {
if (setImageBlock) {
setImageBlock(image, imageData);
return;
}
#if SD_UIKIT || SD_MAC
if ([self isKindOfClass:[UIImageView class]]) {
UIImageView *imageView = (UIImageView *)self;
imageView.image = image;
}
#endif
#if SD_UIKIT
if ([self isKindOfClass:[UIButton class]]) {
UIButton *button = (UIButton *)self;
[button setImage:image forState:UIControlStateNormal];
}
#endif
}
- (void)sd_setNeedsLayout {
#if SD_UIKIT
[self setNeedsLayout];
#elif SD_MAC
[self setNeedsLayout:YES];
#endif
}
#pragma mark - Activity indicator
#pragma mark -
#if SD_UIKIT
- (UIActivityIndicatorView *)activityIndicator {
return (UIActivityIndicatorView *)objc_getAssociatedObject(self, &TAG_ACTIVITY_INDICATOR);
}
- (void)setActivityIndicator:(UIActivityIndicatorView *)activityIndicator {
objc_setAssociatedObject(self, &TAG_ACTIVITY_INDICATOR, activityIndicator, OBJC_ASSOCIATION_RETAIN);
}
#endif
- (void)sd_setShowActivityIndicatorView:(BOOL)show {
objc_setAssociatedObject(self, &TAG_ACTIVITY_SHOW, @(show), OBJC_ASSOCIATION_RETAIN);
}
- (BOOL)sd_showActivityIndicatorView {
return [objc_getAssociatedObject(self, &TAG_ACTIVITY_SHOW) boolValue];
}
#if SD_UIKIT
- (void)sd_setIndicatorStyle:(UIActivityIndicatorViewStyle)style{
objc_setAssociatedObject(self, &TAG_ACTIVITY_STYLE, [NSNumber numberWithInt:style], OBJC_ASSOCIATION_RETAIN);
}
- (int)sd_getIndicatorStyle{
return [objc_getAssociatedObject(self, &TAG_ACTIVITY_STYLE) intValue];
}
#endif
- (void)sd_addActivityIndicator {
#if SD_UIKIT
if (!self.activityIndicator) {
self.activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:[self sd_getIndicatorStyle]];
self.activityIndicator.translatesAutoresizingMaskIntoConstraints = NO;
dispatch_main_async_safe(^{
[self addSubview:self.activityIndicator];
[self addConstraint:[NSLayoutConstraint constraintWithItem:self.activityIndicator
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0.0]];
[self addConstraint:[NSLayoutConstraint constraintWithItem:self.activityIndicator
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self
attribute:NSLayoutAttributeCenterY
multiplier:1.0
constant:0.0]];
});
}
dispatch_main_async_safe(^{
[self.activityIndicator startAnimating];
});
#endif
}
- (void)sd_removeActivityIndicator {
#if SD_UIKIT
if (self.activityIndicator) {
[self.activityIndicator removeFromSuperview];
self.activityIndicator = nil;
}
#endif
}
@end
#endif

View File

@@ -0,0 +1,41 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "SDWebImageCompat.h"
#if SD_UIKIT || SD_MAC
#import "SDWebImageManager.h"
@interface UIView (WebCacheOperation)
/**
* Set the image load operation (storage in a UIView based dictionary)
*
* @param operation the operation
* @param key key for storing the operation
*/
- (void)sd_setImageLoadOperation:(nullable id)operation forKey:(nullable NSString *)key;
/**
* Cancel all operations for the current UIView and key
*
* @param key key for identifying the operations
*/
- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key;
/**
* Just remove the operations corresponding to the current UIView and key without cancelling them
*
* @param key key for identifying the operations
*/
- (void)sd_removeImageLoadOperationWithKey:(nullable NSString *)key;
@end
#endif

View File

@@ -0,0 +1,68 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "UIView+WebCacheOperation.h"
#if SD_UIKIT || SD_MAC
#import "objc/runtime.h"
static char loadOperationKey;
typedef NSMutableDictionary<NSString *, id> SDOperationsDictionary;
@implementation UIView (WebCacheOperation)
- (SDOperationsDictionary *)operationDictionary {
SDOperationsDictionary *operations = objc_getAssociatedObject(self, &loadOperationKey);
if (operations) {
return operations;
}
operations = [NSMutableDictionary dictionary];
objc_setAssociatedObject(self, &loadOperationKey, operations, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return operations;
}
- (void)sd_setImageLoadOperation:(nullable id)operation forKey:(nullable NSString *)key {
if (key) {
[self sd_cancelImageLoadOperationWithKey:key];
if (operation) {
SDOperationsDictionary *operationDictionary = [self operationDictionary];
operationDictionary[key] = operation;
}
}
}
- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key {
// Cancel in progress downloader from queue
SDOperationsDictionary *operationDictionary = [self operationDictionary];
id operations = operationDictionary[key];
if (operations) {
if ([operations isKindOfClass:[NSArray class]]) {
for (id <SDWebImageOperation> operation in operations) {
if (operation) {
[operation cancel];
}
}
} else if ([operations conformsToProtocol:@protocol(SDWebImageOperation)]){
[(id<SDWebImageOperation>) operations cancel];
}
[operationDictionary removeObjectForKey:key];
}
}
- (void)sd_removeImageLoadOperationWithKey:(nullable NSString *)key {
if (key) {
SDOperationsDictionary *operationDictionary = [self operationDictionary];
[operationDictionary removeObjectForKey:key];
}
}
@end
#endif

View File

@@ -1,5 +0,0 @@
#import <Foundation/Foundation.h>
@interface PodsDummy_AlamofireImage : NSObject
@end
@implementation PodsDummy_AlamofireImage
@end

View File

@@ -1,16 +0,0 @@
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#else
#ifndef FOUNDATION_EXPORT
#if defined(__cplusplus)
#define FOUNDATION_EXPORT extern "C"
#else
#define FOUNDATION_EXPORT extern
#endif
#endif
#endif
FOUNDATION_EXPORT double AlamofireImageVersionNumber;
FOUNDATION_EXPORT const unsigned char AlamofireImageVersionString[];

View File

@@ -1,6 +0,0 @@
framework module AlamofireImage {
umbrella header "AlamofireImage-umbrella.h"
export *
module * { export * }
}

View File

@@ -46,29 +46,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
## AlamofireImage
Copyright (c) 2015-2016 Alamofire Software Foundation (http://alamofire.org/)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
## IGListKit
BSD License
@@ -102,4 +79,28 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
## SDWebImage
Copyright (c) 2016 Olivier Poitrey rs@dailymotion.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Generated by CocoaPods - https://cocoapods.org

View File

@@ -69,35 +69,6 @@ THE SOFTWARE.
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
<dict>
<key>FooterText</key>
<string>Copyright (c) 2015-2016 Alamofire Software Foundation (http://alamofire.org/)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
</string>
<key>License</key>
<string>MIT</string>
<key>Title</key>
<string>AlamofireImage</string>
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
<dict>
<key>FooterText</key>
<string>BSD License
@@ -138,6 +109,36 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
<dict>
<key>FooterText</key>
<string>Copyright (c) 2016 Olivier Poitrey rs@dailymotion.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
</string>
<key>License</key>
<string>MIT</string>
<key>Title</key>
<string>SDWebImage</string>
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
<dict>
<key>FooterText</key>
<string>Generated by CocoaPods - https://cocoapods.org</string>

View File

@@ -91,14 +91,14 @@ strip_invalid_archs() {
if [[ "$CONFIGURATION" == "Debug" ]]; then
install_framework "$BUILT_PRODUCTS_DIR/1PasswordExtension/OnePasswordExtension.framework"
install_framework "$BUILT_PRODUCTS_DIR/Alamofire/Alamofire.framework"
install_framework "$BUILT_PRODUCTS_DIR/AlamofireImage/AlamofireImage.framework"
install_framework "$BUILT_PRODUCTS_DIR/IGListKit/IGListKit.framework"
install_framework "$BUILT_PRODUCTS_DIR/SDWebImage/SDWebImage.framework"
fi
if [[ "$CONFIGURATION" == "Release" ]]; then
install_framework "$BUILT_PRODUCTS_DIR/1PasswordExtension/OnePasswordExtension.framework"
install_framework "$BUILT_PRODUCTS_DIR/Alamofire/Alamofire.framework"
install_framework "$BUILT_PRODUCTS_DIR/AlamofireImage/AlamofireImage.framework"
install_framework "$BUILT_PRODUCTS_DIR/IGListKit/IGListKit.framework"
install_framework "$BUILT_PRODUCTS_DIR/SDWebImage/SDWebImage.framework"
fi
if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then
wait

View File

@@ -1,9 +1,9 @@
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/1PasswordExtension" "$PODS_CONFIGURATION_BUILD_DIR/Alamofire" "$PODS_CONFIGURATION_BUILD_DIR/AlamofireImage" "$PODS_CONFIGURATION_BUILD_DIR/IGListKit"
FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/1PasswordExtension" "$PODS_CONFIGURATION_BUILD_DIR/Alamofire" "$PODS_CONFIGURATION_BUILD_DIR/IGListKit" "$PODS_CONFIGURATION_BUILD_DIR/SDWebImage"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/1PasswordExtension/OnePasswordExtension.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/Alamofire/Alamofire.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/AlamofireImage/AlamofireImage.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/IGListKit/IGListKit.framework/Headers"
OTHER_LDFLAGS = $(inherited) -framework "Alamofire" -framework "AlamofireImage" -framework "IGListKit" -framework "OnePasswordExtension"
OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/1PasswordExtension/OnePasswordExtension.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/Alamofire/Alamofire.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/IGListKit/IGListKit.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/SDWebImage/SDWebImage.framework/Headers"
OTHER_LDFLAGS = $(inherited) -framework "Alamofire" -framework "IGListKit" -framework "OnePasswordExtension" -framework "SDWebImage"
OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
PODS_BUILD_DIR = $BUILD_DIR
PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)

View File

@@ -1,9 +1,9 @@
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/1PasswordExtension" "$PODS_CONFIGURATION_BUILD_DIR/Alamofire" "$PODS_CONFIGURATION_BUILD_DIR/AlamofireImage" "$PODS_CONFIGURATION_BUILD_DIR/IGListKit"
FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/1PasswordExtension" "$PODS_CONFIGURATION_BUILD_DIR/Alamofire" "$PODS_CONFIGURATION_BUILD_DIR/IGListKit" "$PODS_CONFIGURATION_BUILD_DIR/SDWebImage"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/1PasswordExtension/OnePasswordExtension.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/Alamofire/Alamofire.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/AlamofireImage/AlamofireImage.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/IGListKit/IGListKit.framework/Headers"
OTHER_LDFLAGS = $(inherited) -framework "Alamofire" -framework "AlamofireImage" -framework "IGListKit" -framework "OnePasswordExtension"
OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/1PasswordExtension/OnePasswordExtension.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/Alamofire/Alamofire.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/IGListKit/IGListKit.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/SDWebImage/SDWebImage.framework/Headers"
OTHER_LDFLAGS = $(inherited) -framework "Alamofire" -framework "IGListKit" -framework "OnePasswordExtension" -framework "SDWebImage"
OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
PODS_BUILD_DIR = $BUILD_DIR
PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)

View File

@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>3.2.0</string>
<string>4.0.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>

View File

@@ -0,0 +1,5 @@
#import <Foundation/Foundation.h>
@interface PodsDummy_SDWebImage : NSObject
@end
@implementation PodsDummy_SDWebImage
@end

View File

@@ -0,0 +1,34 @@
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#else
#ifndef FOUNDATION_EXPORT
#if defined(__cplusplus)
#define FOUNDATION_EXPORT extern "C"
#else
#define FOUNDATION_EXPORT extern
#endif
#endif
#endif
#import "NSData+ImageContentType.h"
#import "NSImage+WebCache.h"
#import "SDImageCache.h"
#import "SDImageCacheConfig.h"
#import "SDWebImageCompat.h"
#import "SDWebImageDecoder.h"
#import "SDWebImageDownloader.h"
#import "SDWebImageDownloaderOperation.h"
#import "SDWebImageManager.h"
#import "SDWebImageOperation.h"
#import "SDWebImagePrefetcher.h"
#import "UIButton+WebCache.h"
#import "UIImage+GIF.h"
#import "UIImage+MultiFormat.h"
#import "UIImageView+HighlightedWebCache.h"
#import "UIImageView+WebCache.h"
#import "UIView+WebCache.h"
#import "UIView+WebCacheOperation.h"
FOUNDATION_EXPORT double SDWebImageVersionNumber;
FOUNDATION_EXPORT const unsigned char SDWebImageVersionString[];

View File

@@ -0,0 +1,6 @@
framework module SDWebImage {
umbrella header "SDWebImage-umbrella.h"
export *
module * { export * }
}

View File

@@ -1,11 +1,10 @@
CONFIGURATION_BUILD_DIR = $PODS_CONFIGURATION_BUILD_DIR/AlamofireImage
FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/Alamofire"
CONFIGURATION_BUILD_DIR = $PODS_CONFIGURATION_BUILD_DIR/SDWebImage
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public"
OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
OTHER_LDFLAGS = -framework "ImageIO"
PODS_BUILD_DIR = $BUILD_DIR
PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
PODS_TARGET_SRCROOT = ${PODS_ROOT}/AlamofireImage
PODS_TARGET_SRCROOT = ${PODS_ROOT}/SDWebImage
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
SKIP_INSTALL = YES