2018-08-06

 #AFNetworking源码阅读系列



图解各个模块

一 前言:

AFNetWorking一款轻量级网络请求开源框架,基于iOS和mac os 网络进行扩展的高性能框架,大大降低了iOS开发工程师处理网络请求的难度,让iOS开发变成一件愉快的事情。

   GitHub地址:https://github.com/AFNetworking/AFNetworking

二 模块 :

AFNetWorking分为六大模块

1 :AFURLSessionManager 首先浏览完这个类从API,发现其主要提供了数据的请求、上传和下载功能

在属性方面:tasks, dataTasks,uploadTasks,downloadTasks

通过这四个属性,我们分别可以拿到总的任务集合、数据任务集合、上传任务集合和下载任务集合

attemptsToRecreateUploadTasksForBackgroundSessions 在创建失败的时候,会重新尝试创建,次数默认为3次

实现文件:

网络请求是delegate返回结果

-(NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request

                               uploadProgress:(nullable void(^)(NSProgress *uploadProgress))uploadProgressBlock

                             downloadProgress:(nullable void(^)(NSProgress *downloadProgress))downloadProgressBlock

                            completionHandler:(nullable void(^)(NSURLResponse *response,id _Nullable responseObject,  NSError * _Nullable error))completionHandler {

__block NSURLSessionDataTask *dataTask = nil;

    url_session_manager_create_task_safely(^{

        dataTask =[self.session dataTaskWithRequest:request];

    });

//每个task里面都会调用addDelegate方法

    [self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];

    return dataTask;

}

-(void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask

                uploadProgress:(nullable void(^)(NSProgress *uploadProgress))uploadProgressBlock

              downloadProgress:(nullable void(^)(NSProgress *downloadProgress))downloadProgressBlock

             completionHandler:(void(^)(NSURLResponse *response,id responseObject,NSError *error))completionHandler

{

    //初始化delegate对象

    AFURLSessionManagerTaskDelegate *delegate =[[AFURLSessionManagerTaskDelegate alloc]init];

    delegate.manager = self;

    //将task的completionHandler赋给delegate,系统网络请求delegate调用该block,返回结果

    delegate.completionHandler = completionHandler;

    dataTask.taskDescription = self.taskDescriptionForSessionTasks;

    //对task进行delegate

    [self setDelegate:delegate forTask:dataTask];

//设置上传和下载进度回调

    delegate.uploadProgressBlock = uploadProgressBlock;

    delegate.downloadProgressBlock = downloadProgressBlock;

}

delegate对象利用kvo将task对一些方法进行监听

// task使用kvo对一些方法监听,返回上传或者下载的进度

    [delegate setupProgressForTask:task];

    // sessionManager对暂停task和恢复task进行注册通知

    [self addNotificationObserverForTask:task];

// task对接收到的字节数、期望接收到的字节数、发送的字节数、期望发送的字节数设置监听

    [task addObserver:self

           forKeyPath:NSStringFromSelector(@selector(countOfBytesReceived))

              options:NSKeyValueObservingOptionNew

              context:NULL];

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {

    if([object isKindOfClass:[NSURLSessionTask class]]||[object isKindOfClass:[NSURLSessionDownloadTask class]]){

        //设置上传和下载的新值

    }

    else if([object isEqual:self.downloadProgress]){

        if(self.downloadProgressBlock){

            self.downloadProgressBlock(object);

        }

    }

    else if([object isEqual:self.uploadProgress]){

        if(self.uploadProgressBlock){

            self.uploadProgressBlock(object);

        }

    }

}

在NSURLSessionTaskDelegate的代理里面,只是做了两件事情,第一个是获取数据,将responseSerializer和downloadFileURL或data存到userInfo里面,第二个是根据error是否为空值,做下一步处理

#pragma mark - NSURLSessionTaskDelegate

-(void)URLSession:(__unused NSURLSession *)session

              task:(NSURLSessionTask *)task

didCompleteWithError:(NSError *)error

{

#pragma clang diagnostic push

#pragma clang diagnostic ignored "-Wgnu"

    //获取数据,将responseSerializer和downloadFileURL或data存到userInfo里面

    __strong AFURLSessionManager *manager = self.manager;

    __block id responseObject = nil;

    __block NSMutableDictionary *userInfo =[NSMutableDictionary dictionary];

    userInfo[AFNetworkingTaskDidCompleteResponseSerializerKey]= manager.responseSerializer;

    //Performance Improvement from #2672

    NSData *data = nil;

    if(self.mutableData){

        data =[self.mutableData copy];

        //We no longer need the reference,so nil it out to gain back some memory.

        self.mutableData = nil;

    }

    if(self.downloadFileURL){

        userInfo[AFNetworkingTaskDidCompleteAssetPathKey]= self.downloadFileURL;

    } else if(data){

        userInfo[AFNetworkingTaskDidCompleteResponseDataKey]= data;

    }

    if(error){

      //有error时处理

    } else {

      //无error时正常处理

    }

#pragma clang diagnostic pop

}


2:AFHTTPSessionManager

AFHTTPSessionManager继承于AFURLSessionManager,提供了更方便的HTTP请求方法,包括了GET、POST、PUT、PATCH、DELETE这五种方式,并且AF鼓励我们在AFHTTPSessionManager再进行一次封装来满足我们自己的业务需求

baseURL 在初始化的方法里面,我们看到这个方法

-(instancetype)initWithBaseURL:(nullable NSURL *)url;

-(instancetype)initWithBaseURL:(nullable NSURL *)url

           sessionConfiguration:(nullable NSURLSessionConfiguration *)configuration NS_DESIGNATED_INITIALIZER;

NS_DESIGNATED_INITIALIZER的作用是什么呢?

指定的构造器通过发送初始化消息到父类来保证object被完全初始化

-(nullable NSURLSessionDataTask *)GET:(NSString *)URLString

                   parameters:(nullable id)parameters

                      success:(nullable void(^)(NSURLSessionDataTask *task,id _Nullable responseObject))success

                      failure:(nullable void(^)(NSURLSessionDataTask * _Nullable task,NSError *error))failure DEPRECATED_ATTRIBUTE;

下面POST、GET、PUT、PATCH、DELETE方法传参基本都是大同小异

URLString表示请求的URL,parameters表示客户端请求内容的存储器,progress表示请求的进度,constructingBodyWithBlock里面只有一个formData用来拼接到HTTP的请求体,success表示请求成功后的block回调,failure表示请求失败的block回调

-(NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method

                                       URLString:(NSString *)URLString

                                      parameters:(id)parameters

                                  uploadProgress:(nullable void(^)(NSProgress *uploadProgress))uploadProgress

                                downloadProgress:(nullable void(^)(NSProgress *downloadProgress))downloadProgress

                                         success:(void(^)(NSURLSessionDataTask *,id))success

                                         failure:(void(^)(NSURLSessionDataTask *,NSError *))failure

{

    NSError *serializationError = nil;

    NSMutableURLRequest *request =[self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL]absoluteString]parameters:parameters error:&serializationError];

    if(serializationError){

        if(failure){

#pragma clang diagnostic push

#pragma clang diagnostic ignored "-Wgnu"

            dispatch_async(self.completionQueue ?: dispatch_get_main_queue(),^{

                failure(nil,serializationError);

            });

#pragma clang diagnostic pop

        }

        return nil;

    }

    __block NSURLSessionDataTask *dataTask = nil;

    dataTask =[self dataTaskWithRequest:request

                          uploadProgress:uploadProgress

                        downloadProgress:downloadProgress

                       completionHandler:^(NSURLResponse * __unused response,id responseObject,NSError *error){

        //失败成功处理

    }];

    return dataTask;

}

3:NSAppTransportSecurity

AFSecurityPolicy主要的作用就是验证HTTPS请求的证书的有效性

AFSecurityPolicy是安全策略类,有三种SSL Pinning模式

typedef NS_ENUM(NSUInteger,AFSSLPinningMode){

    AFSSLPinningModeNone,//在证书列表中校验服务端返回的证书

    AFSSLPinningModePublicKey,//客户端要有服务端的证书拷贝,只是验证时只验证证书里的公钥

    AFSSLPinningModeCertificate,// 客户端要有服务端的证书拷贝,第一步先验证证书域名/有效期等信息,第二步对服务端返回的证书和客户端返回的是否一致

};

这个是证书集合,泛型里面表示了集合里面是NSData类型,表明这个是用来存证书数据的集合,这些证书根据SSL Pinning模式来和服务器进行校验,默认是没有证书的,我们需要调用+ certificatesInBundle:方法将bundle里面的证书文件转成里面是data类型的集合

第三种是需要我们多传入一个证书集合

+(instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode withPinnedCertificates:(NSSet *)pinnedCertificates {

    AFSecurityPolicy *securityPolicy =[[self alloc]init];

    securityPolicy.SSLPinningMode = pinningMode;

    [securityPolicy setPinnedCertificates:pinnedCertificates];

    return securityPolicy;

}

设置证书的时候,就是把上面初始化时传入的证书取出公钥,再把公钥保存到mutablePinnedPublicKeys集合中

-(void)setPinnedCertificates:(NSSet *)pinnedCertificates {

    _pinnedCertificates = pinnedCertificates;

    if(self.pinnedCertificates){

        NSMutableSet *mutablePinnedPublicKeys =[NSMutableSet setWithCapacity:[self.pinnedCertificates count]];

        for(NSData *certificate in self.pinnedCertificates){

            //取出公钥

id publicKey = AFPublicKeyForCertificate(certificate);

            if(!publicKey){

                continue;

            }

            //将公钥存到集合

            [mutablePinnedPublicKeys addObject:publicKey];

        }

        self.pinnedPublicKeys =[NSSet setWithSet:mutablePinnedPublicKeys];

    } else {

        self.pinnedPublicKeys = nil;

    }

}

在AFPublicKeyForCertificate方法里面,做了一系列操作后返回公钥,如下:

-(void)setPinnedCertificates:(NSSet *)pinnedCertificates {

    _pinnedCertificates = pinnedCertificates;

    if(self.pinnedCertificates){

        NSMutableSet *mutablePinnedPublicKeys =[NSMutableSet setWithCapacity:[self.pinnedCertificates count]];

        for(NSData *certificate in self.pinnedCertificates){

            //取出公钥

id publicKey = AFPublicKeyForCertificate(certificate);

            if(!publicKey){

                continue;

            }

            //将公钥存到集合

            [mutablePinnedPublicKeys addObject:publicKey];

        }

        self.pinnedPublicKeys =[NSSet setWithSet:mutablePinnedPublicKeys];

    } else {

        self.pinnedPublicKeys = nil;

    }

}

4:AFNetworkReachabilityManager

是用来监测网络状态的类,可以通过设置状态改变回调来获得当前网络状态

网络的状态值有以下四种

typedef NS_ENUM(NSInteger,AFNetworkReachabilityStatus){

    AFNetworkReachabilityStatusUnknown          = -1,//未知

    AFNetworkReachabilityStatusNotReachable    = 0,//不可用

    AFNetworkReachabilityStatusReachableViaWWAN = 1,//无线广域网连接

    AFNetworkReachabilityStatusReachableViaWiFi = 2,// WiFi连接

};

AFNetworkReachabilityManager提供了五种初始化的方法  

如:+(instancetype)sharedManager {

    static AFNetworkReachabilityManager *_sharedManager = nil;

    static dispatch_once_t onceToken;

    dispatch_once(&onceToken,^{

        //调用+ manager初始化方法

        _sharedManager =[self manager];

    });

    return _sharedManager;

}

开启监听 

-(void)startMonitoring {

    //停止监听

    [self stopMonitoring];

    if(!self.networkReachability){

        return;

    }

    //收到callback调用后,将status通过networkReachabilityStatusBlock回调出去

    __weak __typeof(self)weakSelf = self;

    AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status){

        __strong __typeof(weakSelf)strongSelf = weakSelf;

        strongSelf.networkReachabilityStatus = status;

        if(strongSelf.networkReachabilityStatusBlock){

            strongSelf.networkReachabilityStatusBlock(status);

        }

    };

//声明SCNetworkReachabilityContext结构体

    SCNetworkReachabilityContext context = {0,(__bridge void *)callback,AFNetworkReachabilityRetainCallback,AFNetworkReachabilityReleaseCallback,NULL};

    //设置回调

SCNetworkReachabilitySetCallback(self.networkReachability,AFNetworkReachabilityCallback,&context);

    //加到Main runloop里面对其进行监测

SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability,CFRunLoopGetMain(),kCFRunLoopCommonModes);

//获取当前的网络状态,调用callback

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0),^{

        SCNetworkReachabilityFlags flags;

        if(SCNetworkReachabilityGetFlags(self.networkReachability,&flags)){

            AFPostReachabilityStatusChange(flags,callback);

        }

    });

}

如果没有设置回调的话,也可以通过注册通知的方式,收到网络状态的变化 

static void AFPostReachabilityStatusChange(SCNetworkReachabilityFlags flags,AFNetworkReachabilityStatusBlock block){

    //获取当前的status

AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusForFlags(flags);

    dispatch_async(dispatch_get_main_queue(),^{

        //返回status值

if(block){

            block(status);

        }

//同时会发送一个通知

        NSNotificationCenter *notificationCenter =[NSNotificationCenter defaultCenter];

        NSDictionary *userInfo = @{ AFNetworkingReachabilityNotificationStatusItem: @(status)};

        [notificationCenter postNotificationName:AFNetworkingReachabilityDidChangeNotification object:nil userInfo:userInfo];

    });

}

5:AFURLRequestSerialization 

是用来对发出的请求进行一些处理

AFPercentEscapedStringFromString方法将string里面的:#[]@!$&’()*+,;=字符替换成% 

NSString * AFPercentEscapedStringFromString(NSString *string){

static NSString * const kAFCharactersGeneralDelimitersToEncode = @":#[]@";// does not include "?" or "/" due to RFC 3986 - Section 3.4

    static NSString * const kAFCharactersSubDelimitersToEncode = @"!$&'()*+,;=";

//从可用字符替换删除掉:#[]@!$&'()*+,;=这些字符

    NSMutableCharacterSet * allowedCharacterSet =[[NSCharacterSet URLQueryAllowedCharacterSet]mutableCopy];

    [allowedCharacterSet removeCharactersInString:[kAFCharactersGeneralDelimitersToEncode stringByAppendingString:kAFCharactersSubDelimitersToEncode]];

// FIXME: https://github.com/AFNetworking/AFNetworking/pull/3028

    // return[string stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];

//声明批量处理的大小为50

    static NSUInteger const batchSize = 50;

    NSUInteger index = 0;

    NSMutableString *escaped = @"".mutableCopy;

//循环将string里面:#[]@!$&'()*+,;=的字符替换成%

    while(index < string.length){

#pragma GCC diagnostic push

#pragma GCC diagnostic ignored "-Wgnu"

        NSUInteger length = MIN(string.length - index,batchSize);

#pragma GCC diagnostic pop

        NSRange range = NSMakeRange(index,length);

        // To avoid breaking up character sequences such as👴🏻👮🏽

        range =[string rangeOfComposedCharacterSequencesForRange:range];

        NSString *substring =[string substringWithRange:range];

        NSString *encoded =[substring stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];

        [escaped appendString:encoded];

        index += range.length;

    }

return escaped;

}

在AFQueryStringPair类里面有个- URLEncodedStringValue方法,将请求里面的URL参数转成field=value形式 

-(NSString *)URLEncodedStringValue {

    if(!self.value ||[self.value isEqual:[NSNull null]]){

        return AFPercentEscapedStringFromString([self.field description]);

    } else {

        return[NSString stringWithFormat:@"%@=%@",AFPercentEscapedStringFromString([self.field description]),AFPercentEscapedStringFromString([self.value description])];

    }

}

字典里面是我们查询的key和value,我们通过将字典内容转成AFQueryStringPair对象,调用- URLEncodedStringValue方法,转成key=value,放到mutablePairs数组里,最后用&符拼接起来 

NSString * AFQueryStringFromParameters(NSDictionary *parameters){

    NSMutableArray *mutablePairs =[NSMutableArray array];

    for(AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)){

        [mutablePairs addObject:[pair URLEncodedStringValue]];

    }

    return[mutablePairs componentsJoinedByString:@"&"];

}

NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key,id value){

    NSMutableArray *mutableQueryStringComponents =[NSMutableArray array];

    NSSortDescriptor *sortDescriptor =[NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)];

//如果是字典,遍历后返回key[nestedKey]=nestedValue

    if([value isKindOfClass:[NSDictionary class]]){

        NSDictionary *dictionary = value;

        // Sort dictionary keys to ensure consistent ordering in query string,which is important when deserializing potentially ambiguous sequences,such as an array of dictionaries

        for(id nestedKey in[dictionary.allKeys sortedArrayUsingDescriptors:@[sortDescriptor]]){

            id nestedValue = dictionary[nestedKey];

            if(nestedValue){

                [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue((key ?[NSString stringWithFormat:@"%@[%@]",key,nestedKey]: nestedKey),nestedValue)];

            }

        }

    } //如果是数组,遍历后返回key[]=nestedValue

else if([value isKindOfClass:[NSArray class]]){

        NSArray *array = value;

        for(id nestedValue in array){

            [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue([NSString stringWithFormat:@"%@[]",key],nestedValue)];

        }

    } //如果是集合,遍历后返回key=obj

else if([value isKindOfClass:[NSSet class]]){

        NSSet *set = value;

        for(id obj in[set sortedArrayUsingDescriptors:@[sortDescriptor]]){

            [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue(key,obj)];

        }

    } //其他返回key=value 

else {

        [mutableQueryStringComponents addObject:[[AFQueryStringPair alloc]initWithField:key value:value]];

    }

    return mutableQueryStringComponents;

}

我们使用AFHTTPRequestSerializer对HTTP请求的头部进行处理

首先调用+ serializer进行初始化,里面调用了自己init方法

init里面先将Accept-Language存到mutableHTTPRequestHeaders里

然后拼接User-Agent,格式为”%@/%@ (%@; iOS %@; Scale/%0.2f)”,里面需要5个参数,第一个参数先获取项目名,如果没有,就用BundleIdentifier,第二个参数先获取短版本号,如果没有就用版本号,第三个参数是当前设备的类型,第四个参数是当前设备的版本号,第五个参数是屏幕的比例 

然后设置属性的监听,这些属性在头文件里面都可以找到,实现文件里面也实现了set方法 

通过KVO判断是否是新值,如果是的话,就加到mutableObservedChangedKeyPaths里面 

设置验证字段 

初始化之后,需要调用 

-(NSMutableURLRequest *)requestWithMethod:(NSString *)method

                                 URLString:(NSString *)URLString

                                parameters:(id)parameters

                                     error:(NSError *__autoreleasing *)error

{

    //断言                                     

    NSParameterAssert(method);

    NSParameterAssert(URLString);

    NSURL *url =[NSURL URLWithString:URLString];

    NSParameterAssert(url);

//根据url初始化request

    NSMutableURLRequest *mutableRequest =[[NSMutableURLRequest alloc]initWithURL:url];

    //设置HTTP方法                                   

    mutableRequest.HTTPMethod = method;

//根据mutableObservedChangedKeyPaths存储的属性,设置到mutableRequest

    for(NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()){

        if([self.mutableObservedChangedKeyPaths containsObject:keyPath]){

            [mutableRequest setValue:[self valueForKeyPath:keyPath]forKey:keyPath];

        }

    }

//调用-[requestBySerializingRequest:withParameters:error]方法

    mutableRequest =[[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error]mutableCopy];

return mutableRequest;

}

6:AFURLResponseSerialization

是用来将返回的response处理成相应的格式,它通过协议对特定response的data进行解码

-(nullable id)responseObjectForResponse:(nullable NSURLResponse *)response

                           data:(nullable NSData *)data

                          error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;

acceptableStatusCodes和acceptableContentTypes可以通过外部进行设置

@property(nonatomic,copy,nullable)NSIndexSet *acceptableStatusCodes;

@property(nonatomic,copy,nullable)NSSet *acceptableContentTypes;

然后可以调用- [validateResponse:data:error:]检查这个response是否包含可接受的状态码和可接受MIME类型来验证response的有效性,子类也可以增加特定域名检查,- [responseObjectForResponse:data:error]也是调用了这个方法,返回data 

-(id)responseObjectForResponse:(NSURLResponse *)response

                           data:(NSData *)data

                          error:(NSError *__autoreleasing *)error

{

    //调用-[validateResponse:data:error:]方法,返回data 

    [self validateResponse:(NSHTTPURLResponse *)response data:data error:error];

    return data;

}

-(BOOL)validateResponse:(NSHTTPURLResponse *)response

                    data:(NSData *)data

                   error:(NSError * __autoreleasing *)error

{

    //设置初始值

    BOOL responseIsValid = YES;

    NSError *validationError = nil;

    //检查这个response是否包含可接受的状态码和可接受MIME类型

    if(error && !responseIsValid){

        *error = validationError;

    }

    //返回response是否有效性

    return responseIsValid;

}

检查这个response是否包含可接受的状态码和可接受MIME类型 

//检查response是否为空,以及response是否是NSHTTPURLResponse类

    if(response &&[response isKindOfClass:[NSHTTPURLResponse class]]){

        // acceptableContentTypes不为空并且response的MIME类型不在可接受的范围里

        if(self.acceptableContentTypes && ![self.acceptableContentTypes containsObject:[response MIMEType]]){


            //包装错误信息

            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;

        }

        // acceptableStatusCodes不为空并且acceptableStatusCodes包含response的状态码,response的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;

        }

    }

AFImageResponseSerializer在验证response之后,会根据设置是否自动解压automaticallyInflatesResponseImage布尔值,来对imageData按图片比例返回UIImage对象 

如果用imageWithData转成UIImage对象后,由于网络图片PNG和JPG都是压缩格式,需要解压成bitmap后才能渲染到屏幕,这时会在主线程对图片进行解压操作,这是比较耗时的,可能还会对主线程造成阻塞,所以AF还提供了AFInflatedImageFromResponseWithDataAtScale方法,对PNG和JPG解压后,返回UIImage对象,这样避免了在主线程的解压操作,不会对主线程造成卡顿

static UIImage * AFInflatedImageFromResponseWithDataAtScale(NSHTTPURLResponse *response,NSData *data,CGFloat scale){

    if(!data ||[data length]== 0){

        return nil;

    }

//创建CGImageRef

    CGImageRef imageRef = NULL;

    //用data创建CGDataProviderRef

    CGDataProviderRef dataProvider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data);

    if([response.MIMEType isEqualToString:@"image/png"]){

        imageRef = CGImageCreateWithPNGDataProvider(dataProvider,  NULL,true,kCGRenderingIntentDefault);

    } else if([response.MIMEType isEqualToString:@"image/jpeg"]){

        imageRef = CGImageCreateWithJPEGDataProvider(dataProvider,NULL,true,kCGRenderingIntentDefault);

        if(imageRef){

            CGColorSpaceRef imageColorSpace = CGImageGetColorSpace(imageRef);

            CGColorSpaceModel imageColorSpaceModel = CGColorSpaceGetModel(imageColorSpace);

            //如果色彩空间是CMKY,CGImageCreateWithJPEGDataProvider是不会进行处理的,也就是不进行解压,将调用AFImageWithDataAtScale返回image

            if(imageColorSpaceModel == kCGColorSpaceModelCMYK){

                CGImageRelease(imageRef);

                imageRef = NULL;

            }

        }

    }

    CGDataProviderRelease(dataProvider);

//不符合解压条件的,将调用AFImageWithDataAtScale返回image,但是这里如果符合解压条件的也会调用,以及下面会对超出大小的,直接返回image,这里我觉得应该统一对不符合条件的返回image,符合条件的就不需要调用AFImageWithDataAtScale

    UIImage *image = AFImageWithDataAtScale(data,scale);

    if(!imageRef){

        if(image.images || !image){

            return image;

        }

//这里调用CGImageCreateCopy,只会对图形本身结构进行拷贝,底层的数据是不会拷贝的

        imageRef = CGImageCreateCopy([image CGImage]);

        if(!imageRef){

            return nil;

        }

    }

//设置图片的宽和高和存储一个像素所需要用到的字节

    size_t width = CGImageGetWidth(imageRef);

    size_t height = CGImageGetHeight(imageRef);

    size_t bitsPerComponent = CGImageGetBitsPerComponent(imageRef);

//如果图片大小宽高乘积超过1024*1024或者bitsPerComponent大于8都不解压了,因为bitmap是一直存在UIImage对象里的,可能会把内存爆了

    if(width * height > 1024 * 1024 || bitsPerComponent > 8){

        CGImageRelease(imageRef);

        return image;

    }

    //画布参数

    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

CGImageRef inflatedImageRef = CGBitmapContextCreateImage(context);

    CGContextRelease(context);

//再转成UIImage对象

    UIImage *inflatedImage =[[UIImage alloc]initWithCGImage:inflatedImageRef scale:scale orientation:image.imageOrientation];

    CGImageRelease(inflatedImageRef);

    CGImageRelease(imageRef);

    return inflatedImage;

}

AFCompoundResponseSerializer

-(id)responseObjectForResponse:(NSURLResponse *)response

                           data:(NSData *)data

                          error:(NSError *__autoreleasing *)error

{

    //遍历responseSerializers                   

    for(id serializer in self.responseSerializers){

        //如果serializer不是AFHTTPResponseSerializer类,则继续

if(![serializer isKindOfClass:[AFHTTPResponseSerializer class]]){

            continue;

        }

        NSError *serializerError = nil;

        //一层一层的调用自己的-[responseObjectForResponse:data:error:],直到返回responseObject

        id responseObject =[serializer responseObjectForResponse:response data:data error:&serializerError];

        if(responseObject){

            if(error){

                *error = AFErrorWithUnderlyingError(serializerError,*error);

            }

            return responseObject;

        }

    }

    return[super responseObjectForResponse:response data:data error:error];

}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容