源碼閱讀:AFNetworking(三)——AFURLResponseSerialization

該文章閱讀的AFNetworking的版本爲3.2.0。javascript

AFURLResponseSerialization這個類是用來解析服務器返回的數據,根據服務器返回數據的不一樣,解析數據的類也不一樣。java

1.一個協議

AFURLResponseSerialization這個協議只定義了一個方法,這個方法將服務器返回的數據解析後進行返回。git

- (nullable id)responseObjectForResponse:(nullable NSURLResponse *)response
                           data:(nullable NSData *)data
                          error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;
複製代碼

2.七個類

這七個類又分爲了一個基類和六個子類,先來看基類。github

2.1 AFHTTPResponseSerializer類

這個類是其餘六個類的基類。json

2.1.1 接口部分

2.1.1.1 屬性

/**
 字符串編碼方式,默認爲NSUTF8StringEncoding
 */
@property (nonatomic, assign) NSStringEncoding stringEncoding;

/**
 可接受的狀態碼集合,若是返回的狀態碼不在集合中則會驗證時報錯
 */
@property (nonatomic, copy, nullable) NSIndexSet *acceptableStatusCodes;

/**
 可接受的Content-Type集合,若是返回的類型不在集合中則會驗證時報錯
 */
@property (nonatomic, copy, nullable) NSSet <NSString *> *acceptableContentTypes;
複製代碼

2.1.1.2 方法

/**
 實例化方法
 */
+ (instancetype)serializer;

/**
 初始化方法
 */
- (instancetype)init;

/**
 驗證服務器返回的數據
 */
- (BOOL)validateResponse:(nullable NSHTTPURLResponse *)response
                    data:(nullable NSData *)data
                   error:(NSError * _Nullable __autoreleasing *)error;
複製代碼

2.1.2 實現部分

+ (instancetype)serializer {
    // 普通的實例化方法
    return [[self alloc] init];
}

- (instancetype)init {
    self = [super init];
    if (!self) {
        return nil;
    }

    // 初始化屬性
    self.stringEncoding = NSUTF8StringEncoding;
    
    // 可接受的狀態碼爲200~299之間
    self.acceptableStatusCodes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(200, 100)];
    self.acceptableContentTypes = nil;

    return self;
}

#pragma mark -

- (BOOL)validateResponse:(NSHTTPURLResponse *)response
                    data:(NSData *)data
                   error:(NSError * __autoreleasing *)error
{
    // 設置臨時變量
    BOOL responseIsValid = YES;
    NSError *validationError = nil;

    // 若是response有值,而且response是NSHTTPURLResponse類型的對象
    if (response && [response isKindOfClass:[NSHTTPURLResponse class]]) {
        // 若是設置了可接受的ContentTypes,而且響應的content-type不在可接受的範圍內,而且響應的content-type有值或者返回了數據
        if (self.acceptableContentTypes && ![self.acceptableContentTypes containsObject:[response MIMEType]] &&
            !([response MIMEType] == nil && [data length] == 0)) {

            // 若是返回了數據,而且響應有URL
            if ([data length] > 0 && [response URL]) {
                // 拼接錯誤信息
                NSMutableDictionary *mutableUserInfo = [@{
                                                          NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Request failed: unacceptable content-type: %@", @"AFNetworking", nil), [response MIMEType]],
                                                          NSURLErrorFailingURLErrorKey:[response URL],
                                                          AFNetworkingOperationFailingURLResponseErrorKey: response,
                                                        } mutableCopy];
                if (data) {
                    mutableUserInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] = data;
                }

                // 處理錯誤信息,將以前的錯誤信息和最新的錯誤信息合併
                validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:mutableUserInfo], validationError);
            }

            responseIsValid = NO;
        }

        // 若是設置了可接受狀態碼,而且響應的狀態碼不在可接受的範圍內,而且響應有URL
        if (self.acceptableStatusCodes && ![self.acceptableStatusCodes containsIndex:(NSUInteger)response.statusCode] && [response URL]) {
            // 拼接錯誤信息
            NSMutableDictionary *mutableUserInfo = [@{
                                               NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Request failed: %@ (%ld)", @"AFNetworking", nil), [NSHTTPURLResponse localizedStringForStatusCode:response.statusCode], (long)response.statusCode],
                                               NSURLErrorFailingURLErrorKey:[response URL],
                                               AFNetworkingOperationFailingURLResponseErrorKey: response,
                                       } mutableCopy];

            if (data) {
                mutableUserInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] = data;
            }

            // 處理錯誤信息,將以前的錯誤信息和最新的錯誤信息合併
            validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorBadServerResponse userInfo:mutableUserInfo], validationError);

            responseIsValid = NO;
        }
    }

    // 若是傳了NSError對象,而且響應無效,就賦值後返回錯誤信息
    if (error && !responseIsValid) {
        *error = validationError;
    }

    return responseIsValid;
}
複製代碼

2.1.3 AFURLResponseSerialization協議方法的實現

- (id)responseObjectForResponse:(NSURLResponse *)response
                           data:(NSData *)data
                          error:(NSError *__autoreleasing *)error
{
    // 做爲基類,僅僅實現了對響應和數據的驗證
    [self validateResponse:(NSHTTPURLResponse *)response data:data error:error];

    return data;
}
複製代碼

2.1.4 NSSecureCoding協議方法的實現

+ (BOOL)supportsSecureCoding {
    // 若是一個類符合 NSSecureCoding 協議並在 + supportsSecureCoding 返回 YES,就聲明瞭它能夠處理自己實例的編碼解碼方式,以防止替換攻擊。
    return YES;
}

- (instancetype)initWithCoder:(NSCoder *)decoder {
    self = [self init];
    if (!self) {
        return nil;
    }

    self.acceptableStatusCodes = [decoder decodeObjectOfClass:[NSIndexSet class] forKey:NSStringFromSelector(@selector(acceptableStatusCodes))];
    self.acceptableContentTypes = [decoder decodeObjectOfClass:[NSIndexSet class] forKey:NSStringFromSelector(@selector(acceptableContentTypes))];

    return self;
}

- (void)encodeWithCoder:(NSCoder *)coder {
    [coder encodeObject:self.acceptableStatusCodes forKey:NSStringFromSelector(@selector(acceptableStatusCodes))];
    [coder encodeObject:self.acceptableContentTypes forKey:NSStringFromSelector(@selector(acceptableContentTypes))];
}
複製代碼

2.1.5 NSCopying協議方法的實現

- (instancetype)copyWithZone:(NSZone *)zone {
    AFHTTPResponseSerializer *serializer = [[[self class] allocWithZone:zone] init];
    serializer.acceptableStatusCodes = [self.acceptableStatusCodes copyWithZone:zone];
    serializer.acceptableContentTypes = [self.acceptableContentTypes copyWithZone:zone];

    return serializer;
}
複製代碼

2.2 AFJSONResponseSerializer類

經過這個類的命名能夠直觀的看出這個類是用來解析返回數據爲JSON格式的。數組

2.2.1 接口部分

2.2.1.1 屬性

/**
 json數據解析選項,默認是0
 */
@property (nonatomic, assign) NSJSONReadingOptions readingOptions;

/**
 是否刪除value爲NSNull的key,默認是否
 */
@property (nonatomic, assign) BOOL removesKeysWithNullValues;
複製代碼

2.2.1.2 方法

/**
 初始化方法
 */
- (instancetype)init;

/**
 指定json解析選項的實例化工廠方法
 */
+ (instancetype)serializerWithReadingOptions:(NSJSONReadingOptions)readingOptions;
複製代碼

2.2.2 實現部分

+ (instancetype)serializer {
    // 重寫父類方法,以不選擇json解析選項的方式調用了本身的實例化工廠方法
    return [self serializerWithReadingOptions:(NSJSONReadingOptions)0];
}

+ (instancetype)serializerWithReadingOptions:(NSJSONReadingOptions)readingOptions {
    // 實例化本類對象指定json解析選項並返回
    AFJSONResponseSerializer *serializer = [[self alloc] init];
    serializer.readingOptions = readingOptions;

    return serializer;
}

- (instancetype)init {
    self = [super init];
    if (!self) {
        return nil;
    }

    // 設置了可接受的Content-Type
    self.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", nil];

    return self;
}
複製代碼

2.2.3 AFURLResponseSerialization協議方法的實現

- (id)responseObjectForResponse:(NSURLResponse *)response
                           data:(NSData *)data
                          error:(NSError *__autoreleasing *)error
{
    // 若是服務器返回的數據驗證失敗
    if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {
        // 若是驗證失敗而且沒有錯誤信息就直接返回
        // 若是驗證失敗而且返回的數據類型沒法解析就直接返回
        if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) {
            return nil;
        }
    }

    // 爲了解決在Safari上的一個bug,若是返回的數據爲空或者是一個空格就直接返回
    // Workaround for behavior of Rails to return a single space for `head :ok` (a workaround for a bug in Safari), which is not interpreted as valid input by NSJSONSerialization.
    // See https://github.com/rails/rails/issues/1742
    BOOL isSpace = [data isEqualToData:[NSData dataWithBytes:" " length:1]];
    
    if (data.length == 0 || isSpace) {
        return nil;
    }
    
    // 解析服務器返回的二級制數據
    NSError *serializationError = nil;
    
    id responseObject = [NSJSONSerialization JSONObjectWithData:data options:self.readingOptions error:&serializationError];

    // 若是沒有解析後的數據就直接返回
    if (!responseObject)
    {
        // 若是傳入了錯誤信息參數
        if (error) {
            // 就將傳入的錯誤信息添加到serializationError的NSUnderlyingErrorKey字段中
            *error = AFErrorWithUnderlyingError(serializationError, *error);
        }
        return nil;
    }
    
    // 若是想要過濾NSNull,就進行過濾
    if (self.removesKeysWithNullValues) {
        return AFJSONObjectByRemovingKeysWithNullValues(responseObject, self.readingOptions);
    }

    return responseObject;
}
複製代碼

2.2.4 NSSecureCoding協議方法的實現

同2.1.4安全

2.2.5 NSCopying協議方法的實現

同2.1.5bash

2.3 AFXMLParserResponseSerializer類

當服務器返回的數據類型爲XML時,用這個類進行解析,但這個類只返回NSXMLParser對象,具體的解析還要經過實現代理手動解析服務器

2.3.1 接口部分

接口部分沒有申明新的屬性和方法網絡

2.3.2 實現部分

+ (instancetype)serializer {
    // 實例化對象
    AFXMLParserResponseSerializer *serializer = [[self alloc] init];

    return serializer;
}

- (instancetype)init {
    self = [super init];
    if (!self) {
        return nil;
    }

    // 設置了可接受的Content-Type
    self.acceptableContentTypes = [[NSSet alloc] initWithObjects:@"application/xml", @"text/xml", nil];

    return self;
}

複製代碼

2.3.3 AFURLResponseSerialization協議方法的實現

- (id)responseObjectForResponse:(NSHTTPURLResponse *)response
                           data:(NSData *)data
                          error:(NSError *__autoreleasing *)error
{
    // 若是服務器返回的數據驗證失敗
    if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {
        // 若是驗證失敗而且沒有錯誤信息就直接返回
        // 若是驗證失敗而且返回的數據類型沒法解析就直接返回
        if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) {
            return nil;
        }
    }

    // 返回NSXMLParser對象
    return [[NSXMLParser alloc] initWithData:data];
}
複製代碼

2.4 AFXMLDocumentResponseSerializer類

這個類一樣是用來解析當服務器返回的數據類型爲XML,可是這個類用在MAC上,一樣也是隻返回NSXMLDocument對象,具體要手動解析

2.4.1 接口部分

2.4.1.1 屬性

/**
 NSXMLDocument對象輸入輸出的選項,默認爲0
 */
@property (nonatomic, assign) NSUInteger options;
複製代碼

2.4.1.2 方法

/**
 初始化方法
 */
- (instancetype)init;

/**
 實例化對象的工廠方法
 */
+ (instancetype)serializerWithXMLDocumentOptions:(NSUInteger)mask;
複製代碼

2.4.2 實現部分

+ (instancetype)serializer {
    // 調用本類的實例化工廠方法
    return [self serializerWithXMLDocumentOptions:0];
}

+ (instancetype)serializerWithXMLDocumentOptions:(NSUInteger)mask {
    // 實例化對象並設置參數
    AFXMLDocumentResponseSerializer *serializer = [[self alloc] init];
    serializer.options = mask;

    // 返回對象
    return serializer;
}

- (instancetype)init {
    self = [super init];
    if (!self) {
        return nil;
    }

    // 設置了可接受的Content-Type
    self.acceptableContentTypes = [[NSSet alloc] initWithObjects:@"application/xml", @"text/xml", nil];

    return self;
}
複製代碼

2.4.3 AFURLResponseSerialization協議方法的實現

- (id)responseObjectForResponse:(NSURLResponse *)response
                           data:(NSData *)data
                          error:(NSError *__autoreleasing *)error
{
    // 若是服務器返回的數據驗證失敗
    if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {
        // 若是驗證失敗而且沒有錯誤信息就直接返回
        // 若是驗證失敗而且返回的數據類型沒法解析就直接返回
        if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) {
            return nil;
        }
    }

    // 實例化NSXMLDocument對象
    NSError *serializationError = nil;
    NSXMLDocument *document = [[NSXMLDocument alloc] initWithData:data options:self.options error:&serializationError];

    // 若是實例化沒有成功就直接返回
    if (!document)
    {
        // 若是傳入了錯誤信息參數
        if (error) {
            // 就將傳入的錯誤信息添加到serializationError的NSUnderlyingErrorKey字段中
            *error = AFErrorWithUnderlyingError(serializationError, *error);
        }
        return nil;
    }
    
    return document;
}
複製代碼

2.4.4 NSSecureCoding協議方法的實現

同2.1.4

2.4.5 NSCopying協議方法的實現

同2.1.5

2.5 AFPropertyListResponseSerializer類

這個類是用來將服務器返回的數據解析爲plist類型

2.5.1 接口部分

2.5.1.1 屬性

/**
 指定返回的plist格式,但即便設置了也沒有用,由於在實現中並沒用用到這個參數而是直接設置爲NULL
 */
@property (nonatomic, assign) NSPropertyListFormat format;

/**
 建立plist的可變選項
 */
@property (nonatomic, assign) NSPropertyListReadOptions readOptions;
複製代碼

2.5.1.2 方法

/**
 初始化方法
 */
- (instancetype)init;

/**
 實例化對象的工廠方法
 */
+ (instancetype)serializerWithFormat:(NSPropertyListFormat)format
                         readOptions:(NSPropertyListReadOptions)readOptions;
複製代碼

2.5.2 實現部分

+ (instancetype)serializer {
    // 調用本類的實例化工廠方法
    return [self serializerWithFormat:NSPropertyListXMLFormat_v1_0 readOptions:0];
}

+ (instancetype)serializerWithFormat:(NSPropertyListFormat)format
                         readOptions:(NSPropertyListReadOptions)readOptions
{
    // 實例化對象並設置參數
    AFPropertyListResponseSerializer *serializer = [[self alloc] init];
    serializer.format = format;
    serializer.readOptions = readOptions;

    return serializer;
}

- (instancetype)init {
    self = [super init];
    if (!self) {
        return nil;
    }

    // 設置了可接受的Content-Type
    self.acceptableContentTypes = [[NSSet alloc] initWithObjects:@"application/x-plist", nil];

    return self;
}
複製代碼

2.5.3 AFURLResponseSerialization協議方法的實現

- (id)responseObjectForResponse:(NSURLResponse *)response
                           data:(NSData *)data
                          error:(NSError *__autoreleasing *)error
{
    // 若是服務器返回的數據驗證失敗
    if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {
        // 若是驗證失敗而且沒有錯誤信息就直接返回
        // 若是驗證失敗而且返回的數據類型沒法解析就直接返回
        if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) {
            return nil;
        }
    }

    // 若是沒有數據就直接返回
    if (!data) {
        return nil;
    }
    
    // 實例化NSPropertyListSerialization對象解析data
    NSError *serializationError = nil;
    
    id responseObject = [NSPropertyListSerialization propertyListWithData:data options:self.readOptions format:NULL error:&serializationError];
    
    // 若是沒有解析成功就直接返回
    if (!responseObject)
    {
        if (error) {
            // 就將傳入的錯誤信息添加到serializationError的NSUnderlyingErrorKey字段中
            *error = AFErrorWithUnderlyingError(serializationError, *error);
        }
        return nil;
    }

    return responseObject;
}
複製代碼

2.5.4 NSSecureCoding協議方法的實現

同2.1.4

2.5.5 NSCopying協議方法的實現

同2.1.5

2.6 AFImageResponseSerializer類

這個類是用來解析服務器返回的圖片數據的

2.6.1 接口部分

2.6.1.1 屬性

/**
 圖片的縮放因子,默認爲屏幕的縮放因子
 */
@property (nonatomic, assign) CGFloat imageScale;

/**
 是否對服務器返回的圖片進行自動解壓,默認爲自動解壓
 */
@property (nonatomic, assign) BOOL automaticallyInflatesResponseImage;
複製代碼

2.6.2 實現部分

- (instancetype)init {
    self = [super init];
    if (!self) {
        return nil;
    }

    // 設置了自身的屬性
    self.acceptableContentTypes = [[NSSet alloc] initWithObjects:@"image/tiff", @"image/jpeg", @"image/gif", @"image/png", @"image/ico", @"image/x-icon", @"image/bmp", @"image/x-bmp", @"image/x-xbitmap", @"image/x-win-bitmap", nil];

#if TARGET_OS_IOS || TARGET_OS_TV
    self.imageScale = [[UIScreen mainScreen] scale];
    self.automaticallyInflatesResponseImage = YES;
#elif TARGET_OS_WATCH
    self.imageScale = [[WKInterfaceDevice currentDevice] screenScale];
    self.automaticallyInflatesResponseImage = YES;
#endif

    return self;
}

複製代碼

2.6.3 AFURLResponseSerialization協議方法的實現

- (id)responseObjectForResponse:(NSURLResponse *)response
                           data:(NSData *)data
                          error:(NSError *__autoreleasing *)error
{
    // 若是服務器返回的數據驗證失敗
    if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {
        // 若是驗證失敗而且沒有錯誤信息就直接返回
        // 若是驗證失敗而且返回的數據類型沒法解析就直接返回
        if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) {
            return nil;
        }
    }

// 在移動端須要手動解壓
#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH
    // 若是設置了自動解壓則進行解壓,不然就直接生成
    if (self.automaticallyInflatesResponseImage) {
        return AFInflatedImageFromResponseWithDataAtScale((NSHTTPURLResponse *)response, data, self.imageScale);
    } else {
        return AFImageWithDataAtScale(data, self.imageScale);
    }
// 在MAC上能夠直接調用系統方法解壓
#else
    // Ensure that the image is set to it is correct pixel width and height
    NSBitmapImageRep *bitimage = [[NSBitmapImageRep alloc] initWithData:data];
    NSImage *image = [[NSImage alloc] initWithSize:NSMakeSize([bitimage pixelsWide], [bitimage pixelsHigh])];
    [image addRepresentation:bitimage];

    return image;
// 不然就直接返回
#endif

    return nil;
}
複製代碼

2.6.4 NSSecureCoding協議方法的實現

同2.1.4

2.6.5 NSCopying協議方法的實現

同2.1.5

2.7 AFCompoundResponseSerializer類

這個類能夠用來解析多個不一樣類型的數據類型

2.7.1 接口部分

2.7.1.1 屬性

/**
 響應解析對象數組
 */
@property (readonly, nonatomic, copy) NSArray <id<AFURLResponseSerialization>> *responseSerializers;
複製代碼

2.7.1.2 方法

/**
實例化工廠方法
 */
+ (instancetype)compoundSerializerWithResponseSerializers:(NSArray <id<AFURLResponseSerialization>> *)responseSerializers;
複製代碼

2.7.2 類擴展

/**
類擴展中只有一個屬性用於保存傳入的響應解對象數組
 */
@property (readwrite, nonatomic, copy) NSArray *responseSerializers;
複製代碼

2.7.3 實現部分

+ (instancetype)compoundSerializerWithResponseSerializers:(NSArray *)responseSerializers {
    // 實例化本類對象並設置屬性
    AFCompoundResponseSerializer *serializer = [[self alloc] init];
    serializer.responseSerializers = responseSerializers;

    return serializer;
}

複製代碼

2.7.4 AFURLResponseSerialization協議方法的實現

- (id)responseObjectForResponse:(NSURLResponse *)response
                           data:(NSData *)data
                          error:(NSError *__autoreleasing *)error
{
    // 遍歷傳入的響應解析對象數組
    for (id <AFURLResponseSerialization> serializer in self.responseSerializers) {
        // 若是不是AFHTTPResponseSerializer類或者其子類就跳過進行下一個
        if (![serializer isKindOfClass:[AFHTTPResponseSerializer class]]) {
            continue;
        }

        // 調用其對應的解析方法,解析成功則返回
        NSError *serializerError = nil;
        id responseObject = [serializer responseObjectForResponse:response data:data error:&serializerError];
        if (responseObject) {
            // 若是有錯誤就將傳入的錯誤信息添加到serializationError的NSUnderlyingErrorKey字段中
            if (error) {
                *error = AFErrorWithUnderlyingError(serializerError, *error);
            }

            return responseObject;
        }
    }
    
    // 若是傳入的響應解析對象都解析不了就調用其父類進行解析
    return [super responseObjectForResponse:response data:data error:error];
}
複製代碼

2.7.5 NSSecureCoding協議方法的實現

同2.1.4

2.6.6 NSCopying協議方法的實現

同2.1.5

3.三個全局靜態常量

3.1 接口聲明

/**
 用來識別是AFURLResponseSerialization對象解析的錯誤
 */
FOUNDATION_EXPORT NSString * const AFURLResponseSerializationErrorDomain;

/**
 用來識別是AFURLResponseSerialization對象解析錯誤中的服務器返回的NSURLResponse對象
 */
FOUNDATION_EXPORT NSString * const AFNetworkingOperationFailingURLResponseErrorKey;

/**
 用來識別是AFURLResponseSerialization對象解析錯誤中的服務器返回的NSData對象
 */
FOUNDATION_EXPORT NSString * const AFNetworkingOperationFailingURLResponseDataErrorKey;
複製代碼

3.2 私有實現

NSString * const AFURLResponseSerializationErrorDomain = @"com.alamofire.error.serialization.response";
NSString * const AFNetworkingOperationFailingURLResponseErrorKey = @"com.alamofire.serialization.response.error.response";
NSString * const AFNetworkingOperationFailingURLResponseDataErrorKey = @"com.alamofire.serialization.response.error.data";
複製代碼

4.五個私有靜態方法

/**
 該方法用於將一個NSError對象做爲另外一個NSError對象的附屬NSError對象,並將其放到userInfo屬性NSUnderlyingErrorKey鍵對應的值中
 */
static NSError * AFErrorWithUnderlyingError(NSError *error, NSError *underlyingError) {
    // 沒有error爲空的話就直接返回underlyingError
    if (!error) {
        return underlyingError;
    }

    // 若是underlyingError爲空或者error中已經有附屬underlyingError就直接返回error
    if (!underlyingError || error.userInfo[NSUnderlyingErrorKey]) {
        return error;
    }

    // 獲取error的userInfo,並將underlyingError做爲鍵NSUnderlyingErrorKey對應的值賦給error
    NSMutableDictionary *mutableUserInfo = [error.userInfo mutableCopy];
    mutableUserInfo[NSUnderlyingErrorKey] = underlyingError;

    return [[NSError alloc] initWithDomain:error.domain code:error.code userInfo:mutableUserInfo];
}

/**
 該方法用於判斷error或者underlyingError是否爲指定code和domain的erro
 */
static BOOL AFErrorOrUnderlyingErrorHasCodeInDomain(NSError *error, NSInteger code, NSString *domain) {
    // 若是error符合指定條件則返回YES
    if ([error.domain isEqualToString:domain] && error.code == code) {
        return YES;
    // 若是error不符合可是有underlyingError,則遞歸調用本方法
    } else if (error.userInfo[NSUnderlyingErrorKey]) {
        return AFErrorOrUnderlyingErrorHasCodeInDomain(error.userInfo[NSUnderlyingErrorKey], code, domain);
    }

    // 若是都不知足就返回NO
    return NO;
}

/**
 該方法用於過濾解析後的json數據中NSDictionary類型數據值爲Null的鍵
 */
static id AFJSONObjectByRemovingKeysWithNullValues(id JSONObject, NSJSONReadingOptions readingOptions) {
    // 若是解析後的json數據是NSArray類型的
    if ([JSONObject isKindOfClass:[NSArray class]]) {
        // 遍歷元素若是仍是NSArray類型的就遞歸調用本方法直至元素不爲NSArray類型
        NSMutableArray *mutableArray = [NSMutableArray arrayWithCapacity:[(NSArray *)JSONObject count]];
        for (id value in (NSArray *)JSONObject) {
            [mutableArray addObject:AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions)];
        }

        // 根據傳入參數的可變性來返回對應可變性的NSArray對象
        return (readingOptions & NSJSONReadingMutableContainers) ? mutableArray : [NSArray arrayWithArray:mutableArray];
    // 若是解析後的json數據是NSDictionary類型的
    } else if ([JSONObject isKindOfClass:[NSDictionary class]]) {
         // 遍歷全部的key並取出對應的value
        NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionaryWithDictionary:JSONObject];
        for (id <NSCopying> key in [(NSDictionary *)JSONObject allKeys]) {
            id value = (NSDictionary *)JSONObject[key];
            // 若是value不存在或者是NSNull類型的對象,就將其移除
            if (!value || [value isEqual:[NSNull null]]) {
                [mutableDictionary removeObjectForKey:key];
            // 若是value是NSArray或者NSDictionary類型的對象就遞歸調用本方法直至元素不爲NSArray或者NSDictionary類型的對象
            } else if ([value isKindOfClass:[NSArray class]] || [value isKindOfClass:[NSDictionary class]]) {
                mutableDictionary[key] = AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions);
            }
        }
        
        // 根據傳入參數的可變性來返回對應可變性的NSDictionary對象
        return (readingOptions & NSJSONReadingMutableContainers) ? mutableDictionary : [NSDictionary dictionaryWithDictionary:mutableDictionary];
    }

    // 若是不是是NSArray或者NSDictionary類型的對象就直接返回
    return JSONObject;
}

/**
 生成對應縮放因子的圖片
 */
static UIImage * AFImageWithDataAtScale(NSData *data, CGFloat scale) {
    // 根據二進制數據安全生成圖片對象 
    UIImage *image = [UIImage af_safeImageWithData:data];
    // 若是是gif圖就直接返回圖片對象
    if (image.images) {
        return image;
    }
    
    // 生成對應縮放因子的圖片
    return [[UIImage alloc] initWithCGImage:[image CGImage] scale:scale orientation:image.imageOrientation];
}

/**
 生成對應縮放因子的圖片並進行解壓
 */
static UIImage * AFInflatedImageFromResponseWithDataAtScale(NSHTTPURLResponse *response, NSData *data, CGFloat scale) {
    // 若是沒有數據就直接返回
    if (!data || [data length] == 0) {
        return nil;
    }

    // 建立畫布和圖片數據提供者
    CGImageRef imageRef = NULL;
    CGDataProviderRef dataProvider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data);

    // 若是是png格式直接解壓
    if ([response.MIMEType isEqualToString:@"image/png"]) {
        imageRef = CGImageCreateWithPNGDataProvider(dataProvider,  NULL, true, kCGRenderingIntentDefault);
    // 若是是jpg格式
    } else if ([response.MIMEType isEqualToString:@"image/jpeg"]) {
        // 先進行解壓
        imageRef = CGImageCreateWithJPEGDataProvider(dataProvider, NULL, true, kCGRenderingIntentDefault);
        
        // 若是解壓成功
        if (imageRef) {
            CGColorSpaceRef imageColorSpace = CGImageGetColorSpace(imageRef);
            CGColorSpaceModel imageColorSpaceModel = CGColorSpaceGetModel(imageColorSpace);
            
            // 若是jpg的色彩空間是CMKY而不是RGB的話,不進行解壓
            // CGImageCreateWithJPEGDataProvider does not properly handle CMKY, so fall back to AFImageWithDataAtScale
            if (imageColorSpaceModel == kCGColorSpaceModelCMYK) {
                CGImageRelease(imageRef);
                imageRef = NULL;
            }
        }
    }

    // 釋放圖片數據提供者
    CGDataProviderRelease(dataProvider);

    // 按照縮放因子生成對應的圖片
    UIImage *image = AFImageWithDataAtScale(data, scale);
    // 若是沒有解壓成功
    if (!imageRef) {
        // 若是是gif圖或者沒有生成圖片就直接返回
        if (image.images || !image) {
            return image;
        }
        
        // 若是沒有生成圖片畫布就直接返回
        imageRef = CGImageCreateCopy([image CGImage]);
        if (!imageRef) {
            return nil;
        }
    }

    // 獲取圖片尺寸和一個像素佔用的字節數
    size_t width = CGImageGetWidth(imageRef);
    size_t height = CGImageGetHeight(imageRef);
    size_t bitsPerComponent = CGImageGetBitsPerComponent(imageRef);

    // 若是圖片太大或者一個像素佔用的字節超過8就不解壓了
    if (width * height > 1024 * 1024 || bitsPerComponent > 8) {
        CGImageRelease(imageRef);

        return image;
    }

    // 配置繪圖上下文的參數
    // CGImageGetBytesPerRow() calculates incorrectly in iOS 5.0, so defer to CGBitmapContextCreate
    size_t bytesPerRow = 0;
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGColorSpaceModel colorSpaceModel = CGColorSpaceGetModel(colorSpace);
    CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef);

    if (colorSpaceModel == kCGColorSpaceModelRGB) {
        uint32_t alpha = (bitmapInfo & kCGBitmapAlphaInfoMask);
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wassign-enum"
        if (alpha == kCGImageAlphaNone) {
            bitmapInfo &= ~kCGBitmapAlphaInfoMask;
            bitmapInfo |= kCGImageAlphaNoneSkipFirst;
        } else if (!(alpha == kCGImageAlphaNoneSkipFirst || alpha == kCGImageAlphaNoneSkipLast)) {
            bitmapInfo &= ~kCGBitmapAlphaInfoMask;
            bitmapInfo |= kCGImageAlphaPremultipliedFirst;
        }
#pragma clang diagnostic pop
    }

    // 生成繪圖上下文
    CGContextRef context = CGBitmapContextCreate(NULL, width, height, bitsPerComponent, bytesPerRow, colorSpace, bitmapInfo);

    // 釋放色彩空間
    CGColorSpaceRelease(colorSpace);

    // 若是沒有成功生成繪圖上下文就釋放畫布並直接返回
    if (!context) {
        CGImageRelease(imageRef);

        return image;
    }

    // 繪製圖片
    CGContextDrawImage(context, CGRectMake(0.0f, 0.0f, width, height), imageRef);
    CGImageRef inflatedImageRef = CGBitmapContextCreateImage(context);

    // 釋放繪圖上下文
    CGContextRelease(context);

    // 生成圖片
    UIImage *inflatedImage = [[UIImage alloc] initWithCGImage:inflatedImageRef scale:scale orientation:image.imageOrientation];

    // 釋放畫布
    CGImageRelease(inflatedImageRef);
    CGImageRelease(imageRef);

    return inflatedImage;
}
複製代碼

爲何要費這麼大勁解壓圖片呢?當咱們調用UIImage的方法imageWithData:方法把數據轉成UIImage對象後,其實這時UIImage對象還沒準備好須要渲染到屏幕的數據,如今的網絡圖像PNG和JPG都是壓縮格式,須要把它們解壓轉成bitmap後才能渲染到屏幕上,若是不作任何處理,當你把UIImage賦給UIImageView,在渲染以前底層會判斷到UIImage對象未解壓,沒有bitmap數據,這時會在主線程對圖片進行解壓操做,再渲染到屏幕上。這個解壓操做是比較耗時的,若是任由它在主線程作,可能會致使速度慢UI卡頓的問題。

AFImageResponseSerializer除了把返回數據解析成UIImage外,還會把圖像數據解壓,這個處理是在子線程(AFNetworking專用的一條線程,詳見AFURLConnectionOperation),處理後上層使用返回的UIImage在主線程渲染時就不須要作解壓這步操做,主線程減輕了負擔,減小了UI卡頓問題。

具體實現上在AFInflatedImageFromResponseWithDataAtScale裏,建立一個畫布,把UIImage畫在畫布上,再把這個畫布保存成UIImage返回給上層。只有JPG和PNG纔會嘗試去作解壓操做,期間若是解壓失敗,或者遇到CMKY顏色格式的jpg,或者圖像太大(解壓後的bitmap太佔內存,一個像素3-4字節,搞很差內存就爆掉了),就直接返回未解壓的圖像。

另外在代碼裏看到iOS才須要這樣手動解壓,MacOS上已經有封裝好的對象NSBitmapImageRep能夠作這個事。以上解釋摘自這篇博客

5.一個私有分類

5.1.類擴展

/**
 利用二級制數據安全生成圖片對象的方法
 */
+ (UIImage *)af_safeImageWithData:(NSData *)data;
複製代碼

5.2.靜態對象

/**
 聲明瞭一個鎖對象用於安全生成圖片
 */
static NSLock* imageLock = nil;
複製代碼

5.3.私有實現

+ (UIImage *)af_safeImageWithData:(NSData *)data {
    // 聲明圖片變量
    UIImage* image = nil;
    // 只實例化一遍鎖對象
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        imageLock = [[NSLock alloc] init];
    });
    
    // 安全的利用二級制數據生成圖片對象
    [imageLock lock];
    image = [UIImage imageWithData:data];
    [imageLock unlock];
    return image;
}
複製代碼

源碼閱讀系列:AFNetworking

源碼閱讀:AFNetworking(一)——從使用入手

源碼閱讀:AFNetworking(二)——AFURLRequestSerialization

源碼閱讀:AFNetworking(三)——AFURLResponseSerialization

源碼閱讀:AFNetworking(四)——AFSecurityPolicy

源碼閱讀:AFNetworking(五)——AFNetworkReachabilityManager

源碼閱讀:AFNetworking(六)——AFURLSessionManager

源碼閱讀:AFNetworking(七)——AFHTTPSessionManager

源碼閱讀:AFNetworking(八)——AFAutoPurgingImageCache

源碼閱讀:AFNetworking(九)——AFImageDownloader

源碼閱讀:AFNetworking(十)——AFNetworkActivityIndicatorManager

源碼閱讀:AFNetworking(十一)——UIActivityIndicatorView+AFNetworking

源碼閱讀:AFNetworking(十二)——UIButton+AFNetworking

源碼閱讀:AFNetworking(十三)——UIImageView+AFNetworking

源碼閱讀:AFNetworking(十四)——UIProgressView+AFNetworking

源碼閱讀:AFNetworking(十五)——UIRefreshControl+AFNetworking

源碼閱讀:AFNetworking(十六)——UIWebView+AFNetworking

相關文章
相關標籤/搜索