1 |
efrain |
1 |
//
|
|
|
2 |
// Request+AlamofireImage.swift
|
|
|
3 |
//
|
|
|
4 |
// Copyright (c) 2015 Alamofire Software Foundation (http://alamofire.org/)
|
|
|
5 |
//
|
|
|
6 |
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
7 |
// of this software and associated documentation files (the "Software"), to deal
|
|
|
8 |
// in the Software without restriction, including without limitation the rights
|
|
|
9 |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
10 |
// copies of the Software, and to permit persons to whom the Software is
|
|
|
11 |
// furnished to do so, subject to the following conditions:
|
|
|
12 |
//
|
|
|
13 |
// The above copyright notice and this permission notice shall be included in
|
|
|
14 |
// all copies or substantial portions of the Software.
|
|
|
15 |
//
|
|
|
16 |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
17 |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
18 |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
19 |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
20 |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
21 |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
|
22 |
// THE SOFTWARE.
|
|
|
23 |
//
|
|
|
24 |
|
|
|
25 |
import Alamofire
|
|
|
26 |
import Foundation
|
|
|
27 |
|
|
|
28 |
#if os(iOS) || os(tvOS)
|
|
|
29 |
import UIKit
|
|
|
30 |
#elseif os(watchOS)
|
|
|
31 |
import UIKit
|
|
|
32 |
import WatchKit
|
|
|
33 |
#elseif os(macOS)
|
|
|
34 |
import Cocoa
|
|
|
35 |
#endif
|
|
|
36 |
|
|
|
37 |
public final class ImageResponseSerializer: ResponseSerializer {
|
|
|
38 |
// MARK: Properties
|
|
|
39 |
|
|
|
40 |
public static var deviceScreenScale: CGFloat { DataRequest.imageScale }
|
|
|
41 |
|
|
|
42 |
public let imageScale: CGFloat
|
|
|
43 |
public let inflateResponseImage: Bool
|
|
|
44 |
public let emptyResponseCodes: Set<Int>
|
|
|
45 |
public let emptyRequestMethods: Set<HTTPMethod>
|
|
|
46 |
|
|
|
47 |
static var acceptableImageContentTypes: Set<String> = {
|
|
|
48 |
var contentTypes: Set<String> = ["application/octet-stream",
|
|
|
49 |
"image/tiff",
|
|
|
50 |
"image/jpg",
|
|
|
51 |
"image/jpeg",
|
|
|
52 |
"image/jp2",
|
|
|
53 |
"image/gif",
|
|
|
54 |
"image/png",
|
|
|
55 |
"image/ico",
|
|
|
56 |
"image/x-icon",
|
|
|
57 |
"image/bmp",
|
|
|
58 |
"image/x-bmp",
|
|
|
59 |
"image/x-xbitmap",
|
|
|
60 |
"image/x-ms-bmp",
|
|
|
61 |
"image/x-win-bitmap"]
|
|
|
62 |
|
|
|
63 |
#if os(macOS) || os(iOS) // No WebP support on tvOS or watchOS.
|
|
|
64 |
if #available(macOS 11, iOS 14, *) {
|
|
|
65 |
contentTypes.formUnion(["image/webp"])
|
|
|
66 |
}
|
|
|
67 |
#endif
|
|
|
68 |
|
|
|
69 |
if #available(macOS 10.13, iOS 11, tvOS 11, watchOS 4, *) {
|
|
|
70 |
contentTypes.formUnion(["image/heic", "image/heif"])
|
|
|
71 |
}
|
|
|
72 |
|
|
|
73 |
return contentTypes
|
|
|
74 |
}()
|
|
|
75 |
|
|
|
76 |
static let streamImageInitialBytePattern = Data([255, 216]) // 0xffd8
|
|
|
77 |
|
|
|
78 |
// MARK: Initialization
|
|
|
79 |
|
|
|
80 |
public init(imageScale: CGFloat = ImageResponseSerializer.deviceScreenScale,
|
|
|
81 |
inflateResponseImage: Bool = true,
|
|
|
82 |
emptyResponseCodes: Set<Int> = ImageResponseSerializer.defaultEmptyResponseCodes,
|
|
|
83 |
emptyRequestMethods: Set<HTTPMethod> = ImageResponseSerializer.defaultEmptyRequestMethods) {
|
|
|
84 |
self.imageScale = imageScale
|
|
|
85 |
self.inflateResponseImage = inflateResponseImage
|
|
|
86 |
self.emptyResponseCodes = emptyResponseCodes
|
|
|
87 |
self.emptyRequestMethods = emptyRequestMethods
|
|
|
88 |
}
|
|
|
89 |
|
|
|
90 |
// MARK: Serialization
|
|
|
91 |
|
|
|
92 |
public func serialize(request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: Error?) throws -> Image {
|
|
|
93 |
guard error == nil else { throw error! }
|
|
|
94 |
|
|
|
95 |
guard let data = data, !data.isEmpty else {
|
|
|
96 |
guard emptyResponseAllowed(forRequest: request, response: response) else {
|
|
|
97 |
throw AFError.responseSerializationFailed(reason: .inputDataNilOrZeroLength)
|
|
|
98 |
}
|
|
|
99 |
|
|
|
100 |
print("Returning empty image!")
|
|
|
101 |
return Image()
|
|
|
102 |
}
|
|
|
103 |
|
|
|
104 |
try validateContentType(for: request, response: response)
|
|
|
105 |
let image = try serializeImage(from: data)
|
|
|
106 |
|
|
|
107 |
return image
|
|
|
108 |
}
|
|
|
109 |
|
|
|
110 |
public func serializeImage(from data: Data) throws -> Image {
|
|
|
111 |
guard !data.isEmpty else {
|
|
|
112 |
throw AFError.responseSerializationFailed(reason: .inputDataNilOrZeroLength)
|
|
|
113 |
}
|
|
|
114 |
|
|
|
115 |
#if os(iOS) || os(tvOS) || os(watchOS)
|
|
|
116 |
guard let image = UIImage.af.threadSafeImage(with: data, scale: imageScale) else {
|
|
|
117 |
throw AFIError.imageSerializationFailed
|
|
|
118 |
}
|
|
|
119 |
|
|
|
120 |
if inflateResponseImage { image.af.inflate() }
|
|
|
121 |
#elseif os(macOS)
|
|
|
122 |
guard let bitmapImage = NSBitmapImageRep(data: data) else {
|
|
|
123 |
throw AFIError.imageSerializationFailed
|
|
|
124 |
}
|
|
|
125 |
|
|
|
126 |
let image = NSImage(size: NSSize(width: bitmapImage.pixelsWide, height: bitmapImage.pixelsHigh))
|
|
|
127 |
image.addRepresentation(bitmapImage)
|
|
|
128 |
#endif
|
|
|
129 |
|
|
|
130 |
return image
|
|
|
131 |
}
|
|
|
132 |
|
|
|
133 |
// MARK: Content Type Validation
|
|
|
134 |
|
|
|
135 |
/// Adds the content types specified to the list of acceptable images content types for validation.
|
|
|
136 |
///
|
|
|
137 |
/// - parameter contentTypes: The additional content types.
|
|
|
138 |
public class func addAcceptableImageContentTypes(_ contentTypes: Set<String>) {
|
|
|
139 |
ImageResponseSerializer.acceptableImageContentTypes.formUnion(contentTypes)
|
|
|
140 |
}
|
|
|
141 |
|
|
|
142 |
public func validateContentType(for request: URLRequest?, response: HTTPURLResponse?) throws {
|
|
|
143 |
if let url = request?.url, url.isFileURL { return }
|
|
|
144 |
|
|
|
145 |
guard let mimeType = response?.mimeType else {
|
|
|
146 |
let contentTypes = Array(ImageResponseSerializer.acceptableImageContentTypes)
|
|
|
147 |
throw AFError.responseValidationFailed(reason: .missingContentType(acceptableContentTypes: contentTypes))
|
|
|
148 |
}
|
|
|
149 |
|
|
|
150 |
guard ImageResponseSerializer.acceptableImageContentTypes.contains(mimeType) else {
|
|
|
151 |
let contentTypes = Array(ImageResponseSerializer.acceptableImageContentTypes)
|
|
|
152 |
|
|
|
153 |
throw AFError.responseValidationFailed(
|
|
|
154 |
reason: .unacceptableContentType(acceptableContentTypes: contentTypes, responseContentType: mimeType)
|
|
|
155 |
)
|
|
|
156 |
}
|
|
|
157 |
}
|
|
|
158 |
}
|
|
|
159 |
|
|
|
160 |
// MARK: - Image Scale
|
|
|
161 |
|
|
|
162 |
extension DataRequest {
|
|
|
163 |
public class var imageScale: CGFloat {
|
|
|
164 |
#if os(iOS) || os(tvOS)
|
|
|
165 |
return UIScreen.main.scale
|
|
|
166 |
#elseif os(watchOS)
|
|
|
167 |
return WKInterfaceDevice.current().screenScale
|
|
|
168 |
#elseif os(macOS)
|
|
|
169 |
return 1.0
|
|
|
170 |
#endif
|
|
|
171 |
}
|
|
|
172 |
}
|
|
|
173 |
|
|
|
174 |
// MARK: - iOS, tvOS, and watchOS
|
|
|
175 |
|
|
|
176 |
#if os(iOS) || os(tvOS) || os(watchOS)
|
|
|
177 |
|
|
|
178 |
extension DataRequest {
|
|
|
179 |
/// Adds a response handler to be called once the request has finished.
|
|
|
180 |
///
|
|
|
181 |
/// - parameter imageScale: The scale factor used when interpreting the image data to construct
|
|
|
182 |
/// `responseImage`. Specifying a scale factor of 1.0 results in an image whose
|
|
|
183 |
/// size matches the pixel-based dimensions of the image. Applying a different
|
|
|
184 |
/// scale factor changes the size of the image as reported by the size property.
|
|
|
185 |
/// This is set to the value of scale of the main screen by default, which
|
|
|
186 |
/// automatically scales images for retina displays, for instance.
|
|
|
187 |
/// `Screen.scale` by default.
|
|
|
188 |
/// - parameter inflateResponseImage: Whether to automatically inflate response image data for compressed formats
|
|
|
189 |
/// (such as PNG or JPEG). Enabling this can significantly improve drawing
|
|
|
190 |
/// performance as it allows a bitmap representation to be constructed in the
|
|
|
191 |
/// background rather than on the main thread. `true` by default.
|
|
|
192 |
/// - parameter queue: The queue on which the completion handler is dispatched. `.main` by default.
|
|
|
193 |
/// - parameter completionHandler: A closure to be executed once the request has finished. The closure takes 4
|
|
|
194 |
/// arguments: the URL request, the URL response, if one was received, the image,
|
|
|
195 |
/// if one could be created from the URL response and data, and any error produced
|
|
|
196 |
/// while creating the image.
|
|
|
197 |
///
|
|
|
198 |
/// - returns: The request.
|
|
|
199 |
@discardableResult
|
|
|
200 |
public func responseImage(imageScale: CGFloat = DataRequest.imageScale,
|
|
|
201 |
inflateResponseImage: Bool = true,
|
|
|
202 |
queue: DispatchQueue = .main,
|
|
|
203 |
completionHandler: @escaping (AFDataResponse<Image>) -> Void)
|
|
|
204 |
-> Self {
|
|
|
205 |
response(queue: queue,
|
|
|
206 |
responseSerializer: ImageResponseSerializer(imageScale: imageScale,
|
|
|
207 |
inflateResponseImage: inflateResponseImage),
|
|
|
208 |
completionHandler: completionHandler)
|
|
|
209 |
}
|
|
|
210 |
}
|
|
|
211 |
|
|
|
212 |
// MARK: - macOS
|
|
|
213 |
|
|
|
214 |
#elseif os(macOS)
|
|
|
215 |
|
|
|
216 |
extension DataRequest {
|
|
|
217 |
/// Adds a response handler to be called once the request has finished.
|
|
|
218 |
///
|
|
|
219 |
/// - Parameters:
|
|
|
220 |
/// - queue: The queue on which the completion handler is dispatched. `.main` by default.
|
|
|
221 |
/// - completionHandler: A closure to be executed once the request has finished. The closure takes 4 arguments:
|
|
|
222 |
/// the URL request, the URL response, if one was received, the image, if one could be
|
|
|
223 |
/// created from the URL response and data, and any error produced while creating the image.
|
|
|
224 |
///
|
|
|
225 |
/// - returns: The request.
|
|
|
226 |
@discardableResult
|
|
|
227 |
public func responseImage(queue: DispatchQueue = .main,
|
|
|
228 |
completionHandler: @escaping (AFDataResponse<Image>) -> Void)
|
|
|
229 |
-> Self {
|
|
|
230 |
response(queue: queue,
|
|
|
231 |
responseSerializer: ImageResponseSerializer(inflateResponseImage: false),
|
|
|
232 |
completionHandler: completionHandler)
|
|
|
233 |
}
|
|
|
234 |
}
|
|
|
235 |
|
|
|
236 |
#endif
|