You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

431 lines
14 KiB

2 years ago
  1. // The MIT License (MIT)
  2. //
  3. // Copyright (c) 2015-2021 Alexander Grebenyuk (github.com/kean).
  4. import Foundation
  5. // MARK: - ImageRequest
  6. /// Represents an image request.
  7. public struct ImageRequest: CustomStringConvertible {
  8. // MARK: Parameters of the Request
  9. /// The `URLRequest` used for loading an image.
  10. public var urlRequest: URLRequest {
  11. get {
  12. switch ref.resource {
  13. case let .url(url):
  14. var request = URLRequest(url: url) // create lazily
  15. if cachePolicy == .reloadIgnoringCachedData {
  16. request.cachePolicy = .reloadIgnoringLocalCacheData
  17. }
  18. return request
  19. case let .urlRequest(urlRequest):
  20. return urlRequest
  21. }
  22. }
  23. set {
  24. mutate {
  25. $0.resource = Resource.urlRequest(newValue)
  26. $0.urlString = newValue.url?.absoluteString
  27. }
  28. }
  29. }
  30. var urlString: String? {
  31. ref.urlString
  32. }
  33. /// The execution priority of the request. The priority affects the order in which the image
  34. /// requests are executed.
  35. public enum Priority: Int, Comparable {
  36. case veryLow = 0, low, normal, high, veryHigh
  37. var taskPriority: TaskPriority {
  38. switch self {
  39. case .veryLow: return .veryLow
  40. case .low: return .low
  41. case .normal: return .normal
  42. case .high: return .high
  43. case .veryHigh: return .veryHigh
  44. }
  45. }
  46. public static func < (lhs: Priority, rhs: Priority) -> Bool {
  47. lhs.rawValue < rhs.rawValue
  48. }
  49. }
  50. /// The relative priority of the operation. The priority affects the order in which the image
  51. /// requests are executed.`.normal` by default.
  52. public var priority: Priority {
  53. get { ref.priority }
  54. set { mutate { $0.priority = newValue } }
  55. }
  56. public enum CachePolicy {
  57. case `default`
  58. /// The image should be loaded only from the originating source.
  59. ///
  60. /// If you initialize the request with `URLRequest`, make sure to provide
  61. /// the correct policy in the request too.
  62. case reloadIgnoringCachedData
  63. }
  64. public var cachePolicy: CachePolicy {
  65. get { ref.cachePolicy }
  66. set { mutate { $0.cachePolicy = newValue } }
  67. }
  68. /// The request options. See `ImageRequestOptions` for more info.
  69. public var options: ImageRequestOptions {
  70. get { ref.options }
  71. set { mutate { $0.options = newValue } }
  72. }
  73. /// Processor to be applied to the image. `nil` by default.
  74. public var processors: [ImageProcessing] {
  75. get { ref.processors }
  76. set { mutate { $0.processors = newValue } }
  77. }
  78. // MARK: Initializers
  79. /// Initializes a request with the given URL.
  80. ///
  81. /// - parameter priority: The priority of the request, `.normal` by default.
  82. /// - parameter options: Advanced image loading options.
  83. /// - parameter processors: Image processors to be applied after the image is loaded.
  84. ///
  85. /// `ImageRequest` allows you to set image processors, change the request priority and more:
  86. ///
  87. /// ```swift
  88. /// let request = ImageRequest(
  89. /// url: URL(string: "http://...")!,
  90. /// processors: [ImageProcessors.Resize(size: imageView.bounds.size)],
  91. /// priority: .high
  92. /// )
  93. /// ```
  94. public init(url: URL,
  95. processors: [ImageProcessing] = [],
  96. cachePolicy: CachePolicy = .default,
  97. priority: Priority = .normal,
  98. options: ImageRequestOptions = .init()) {
  99. self.ref = Container(resource: Resource.url(url), processors: processors, cachePolicy: cachePolicy, priority: priority, options: options)
  100. self.ref.urlString = url.absoluteString
  101. // creating `.absoluteString` takes 50% of time of Request creation,
  102. // it's still faster than using URLs as cache keys
  103. }
  104. /// Initializes a request with the given request.
  105. ///
  106. /// - parameter priority: The priority of the request, `.normal` by default.
  107. /// - parameter options: Advanced image loading options.
  108. /// - parameter processors: Image processors to be applied after the image is loaded.
  109. ///
  110. /// `ImageRequest` allows you to set image processors, change the request priority and more:
  111. ///
  112. /// ```swift
  113. /// let request = ImageRequest(
  114. /// url: URLRequest(url: URL(string: "http://...")!),
  115. /// processors: [ImageProcessors.Resize(size: imageView.bounds.size)],
  116. /// priority: .high
  117. /// )
  118. /// ```
  119. public init(urlRequest: URLRequest,
  120. processors: [ImageProcessing] = [],
  121. cachePolicy: CachePolicy = .default,
  122. priority: ImageRequest.Priority = .normal,
  123. options: ImageRequestOptions = .init()) {
  124. self.ref = Container(resource: Resource.urlRequest(urlRequest), processors: processors, cachePolicy: cachePolicy, priority: priority, options: options)
  125. self.ref.urlString = urlRequest.url?.absoluteString
  126. }
  127. // CoW:
  128. private var ref: Container
  129. private mutating func mutate(_ closure: (Container) -> Void) {
  130. if !isKnownUniquelyReferenced(&ref) {
  131. ref = Container(container: ref)
  132. }
  133. closure(ref)
  134. }
  135. /// Just like many Swift built-in types, `ImageRequest` uses CoW approach to
  136. /// avoid memberwise retain/releases when `ImageRequest` is passed around.
  137. private class Container {
  138. var resource: Resource
  139. var urlString: String? // memoized absoluteString
  140. var cachePolicy: CachePolicy
  141. var priority: ImageRequest.Priority
  142. var options: ImageRequestOptions
  143. var processors: [ImageProcessing]
  144. deinit {
  145. #if TRACK_ALLOCATIONS
  146. Allocations.decrement("ImageRequest.Container")
  147. #endif
  148. }
  149. /// Creates a resource with a default processor.
  150. init(resource: Resource, processors: [ImageProcessing], cachePolicy: CachePolicy, priority: Priority, options: ImageRequestOptions) {
  151. self.resource = resource
  152. self.processors = processors
  153. self.cachePolicy = cachePolicy
  154. self.priority = priority
  155. self.options = options
  156. #if TRACK_ALLOCATIONS
  157. Allocations.increment("ImageRequest.Container")
  158. #endif
  159. }
  160. /// Creates a copy.
  161. init(container ref: Container) {
  162. self.resource = ref.resource
  163. self.urlString = ref.urlString
  164. self.processors = ref.processors
  165. self.cachePolicy = ref.cachePolicy
  166. self.priority = ref.priority
  167. self.options = ref.options
  168. #if TRACK_ALLOCATIONS
  169. Allocations.increment("ImageRequest.Container")
  170. #endif
  171. }
  172. var preferredURLString: String {
  173. options.filteredURL ?? urlString ?? ""
  174. }
  175. }
  176. /// Resource representation (either URL or URLRequest).
  177. private enum Resource: CustomStringConvertible {
  178. case url(URL)
  179. case urlRequest(URLRequest)
  180. var description: String {
  181. switch self {
  182. case let .url(url):
  183. return "\(url)"
  184. case let .urlRequest(urlRequest):
  185. return "\(urlRequest)"
  186. }
  187. }
  188. }
  189. public var description: String {
  190. return """
  191. ImageRequest {
  192. resource: \(ref.resource)
  193. priority: \(ref.priority)
  194. processors: \(ref.processors)
  195. options: {
  196. memoryCacheOptions: \(ref.options.memoryCacheOptions)
  197. filteredURL: \(String(describing: ref.options.filteredURL))
  198. cacheKey: \(String(describing: ref.options.cacheKey))
  199. loadKey: \(String(describing: ref.options.loadKey))
  200. userInfo: \(String(describing: ref.options.userInfo))
  201. }
  202. }
  203. """
  204. }
  205. }
  206. // MARK: - ImageRequestOptions (Advanced Options)
  207. public struct ImageRequestOptions {
  208. /// The policy to use when reading or writing images to the memory cache.
  209. ///
  210. /// Soft-deprecated in Nuke 9.2.
  211. public struct MemoryCacheOptions {
  212. /// `true` by default.
  213. public var isReadAllowed = true
  214. /// `true` by default.
  215. public var isWriteAllowed = true
  216. public init(isReadAllowed: Bool = true, isWriteAllowed: Bool = true) {
  217. self.isReadAllowed = isReadAllowed
  218. self.isWriteAllowed = isWriteAllowed
  219. }
  220. }
  221. /// `MemoryCacheOptions()` (read allowed, write allowed) by default.
  222. public var memoryCacheOptions: MemoryCacheOptions
  223. /// Provide a `filteredURL` to be used as a key for caching in case the original URL
  224. /// contains transient query parameters.
  225. ///
  226. /// ```
  227. /// let request = ImageRequest(
  228. /// url: URL(string: "http://example.com/image.jpeg?token=123")!,
  229. /// options: ImageRequestOptions(
  230. /// filteredURL: "http://example.com/image.jpeg"
  231. /// )
  232. /// )
  233. /// ```
  234. public var filteredURL: String?
  235. /// The **memory** cache key for final processed images. Set if you are not
  236. /// happy with the default behavior.
  237. ///
  238. /// By default, two requests are considered equivalent if they have the same
  239. /// URLs and the same processors.
  240. public var cacheKey: AnyHashable?
  241. /// Returns a key that compares requests with regards to loading images.
  242. ///
  243. /// The default key considers two requests equivalent if they have the same
  244. /// `URLRequests` and the same processors. `URLRequests` are compared by
  245. /// their `URL`, `cachePolicy`, and `allowsCellularAccess` properties.
  246. public var loadKey: AnyHashable?
  247. /// Custom info passed alongside the request.
  248. public var userInfo: [AnyHashable: Any]
  249. public init(memoryCacheOptions: MemoryCacheOptions = .init(),
  250. filteredURL: String? = nil,
  251. cacheKey: AnyHashable? = nil,
  252. loadKey: AnyHashable? = nil,
  253. userInfo: [AnyHashable: Any] = [:]) {
  254. self.memoryCacheOptions = memoryCacheOptions
  255. self.filteredURL = filteredURL
  256. self.cacheKey = cacheKey
  257. self.loadKey = loadKey
  258. self.userInfo = userInfo
  259. }
  260. }
  261. // MARK: - ImageRequestKeys (Internal)
  262. extension ImageRequest {
  263. // MARK: - Cache Keys
  264. /// A key for processed image in memory cache.
  265. func makeCacheKeyForFinalImage() -> ImageRequest.CacheKey {
  266. CacheKey(request: self)
  267. }
  268. /// A key for processed image data in disk cache.
  269. func makeCacheKeyForFinalImageData() -> String {
  270. "\(ref.preferredURLString)\(ImageProcessors.Composition(processors).identifier)"
  271. }
  272. /// A key for original image data in disk cache.
  273. func makeCacheKeyForOriginalImageData() -> String {
  274. ref.preferredURLString
  275. }
  276. // MARK: - Load Keys
  277. /// A key for deduplicating operations for fetching the processed image.
  278. func makeLoadKeyForFinalImage() -> LoadKeyForProcessedImage {
  279. LoadKeyForProcessedImage(
  280. cacheKey: makeCacheKeyForFinalImage(),
  281. loadKey: makeLoadKeyForOriginalImage()
  282. )
  283. }
  284. /// A key for deduplicating operations for fetching the original image.
  285. func makeLoadKeyForOriginalImage() -> LoadKeyForOriginalImage {
  286. LoadKeyForOriginalImage(request: self)
  287. }
  288. // MARK: - Internals (Keys)
  289. // Uniquely identifies a cache processed image.
  290. struct CacheKey: Hashable {
  291. let request: ImageRequest
  292. func hash(into hasher: inout Hasher) {
  293. if let customKey = request.ref.options.cacheKey {
  294. hasher.combine(customKey)
  295. } else {
  296. hasher.combine(request.ref.preferredURLString)
  297. }
  298. }
  299. static func == (lhs: CacheKey, rhs: CacheKey) -> Bool {
  300. let lhs = lhs.request.ref, rhs = rhs.request.ref
  301. if lhs.options.cacheKey != nil || rhs.options.cacheKey != nil {
  302. return lhs.options.cacheKey == rhs.options.cacheKey
  303. }
  304. return lhs.preferredURLString == rhs.preferredURLString && lhs.processors == rhs.processors
  305. }
  306. }
  307. // Uniquely identifies a task of retrieving the processed image.
  308. struct LoadKeyForProcessedImage: Hashable {
  309. let cacheKey: CacheKey
  310. let loadKey: AnyHashable
  311. }
  312. // Uniquely identifies a task of retrieving the original image.
  313. struct LoadKeyForOriginalImage: Hashable {
  314. let request: ImageRequest
  315. func hash(into hasher: inout Hasher) {
  316. if let customKey = request.ref.options.loadKey {
  317. hasher.combine(customKey)
  318. } else {
  319. hasher.combine(request.ref.preferredURLString)
  320. }
  321. }
  322. static func == (lhs: LoadKeyForOriginalImage, rhs: LoadKeyForOriginalImage) -> Bool {
  323. let (lhs, rhs) = (lhs.request, rhs.request)
  324. if lhs.options.loadKey != nil || rhs.options.loadKey != nil {
  325. return lhs.options.loadKey == rhs.options.loadKey
  326. }
  327. return Parameters(lhs) == Parameters(rhs)
  328. }
  329. private struct Parameters: Hashable {
  330. let urlString: String?
  331. let requestCachePolicy: CachePolicy
  332. let cachePolicy: URLRequest.CachePolicy
  333. let allowsCellularAccess: Bool
  334. init(_ request: ImageRequest) {
  335. self.urlString = request.ref.urlString
  336. self.requestCachePolicy = request.cachePolicy
  337. switch request.ref.resource {
  338. case .url:
  339. self.cachePolicy = .useProtocolCachePolicy
  340. self.allowsCellularAccess = true
  341. case let .urlRequest(urlRequest):
  342. self.cachePolicy = urlRequest.cachePolicy
  343. self.allowsCellularAccess = urlRequest.allowsCellularAccess
  344. }
  345. }
  346. }
  347. }
  348. }
  349. // MARK: - ImageRequestConvertible
  350. public protocol ImageRequestConvertible {
  351. func asImageRequest() -> ImageRequest
  352. }
  353. extension ImageRequest: ImageRequestConvertible {
  354. public func asImageRequest() -> ImageRequest {
  355. self
  356. }
  357. }
  358. extension URL: ImageRequestConvertible {
  359. public func asImageRequest() -> ImageRequest {
  360. ImageRequest(url: self)
  361. }
  362. }
  363. extension URLRequest: ImageRequestConvertible {
  364. public func asImageRequest() -> ImageRequest {
  365. ImageRequest(urlRequest: self)
  366. }
  367. }