這個模塊主要的類就是下面的這幾個
<AFURLRequestSerialization>(協(xié)議)
AFHTTPRequestSerializer(根類)
<AFMultipartFormData>(多部分表單,協(xié)議)
AFJSONRequestSerializer
AFPropertyListRequestSerializer
<AFURLResponseSerialization>(協(xié)議)
AFHTTPResponseSerializer(根類)
AFJSONResponseSerializer(默認的)
AFXMLParserResponseSerializer
AFXMLDocumentResponseSerializer (macOS)
AFPropertyListResponseSerializer
AFImageResponseSerializer(重要的類)
AFCompoundResponseSerializer
實例
本模塊通過上傳一張圖片引入
- (void)multipart{
// 1.使用AFHTTPSessionManager的接口
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
NSDictionary *dic = @{@"businessType":@"CC_USER_CENTER",
@"fileType":@"image",
@"file":@"img.jpeg"
};
[manager POST:@"http://114.215.186.169:9002/api/demo/test/file" parameters:dic constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) {
// 2.在這個block中設置需要上傳的文件
NSString *path = [[NSBundle mainBundle] pathForResource:@"1" ofType:@"png"];
// 將本地圖片數(shù)據(jù)拼接到formData中 指定name
[formData appendPartWithFileURL:[NSURL fileURLWithPath:path] name:@"file" error:nil];
// 或者使用這個接口拼接 指定name和filename
// NSData *picdata =[NSData dataWithContentsOfFile:path];
// [formData appendPartWithFileData:picdata name:@"image" fileName:@"image.jpg" mimeType:@"image/jpeg"];
} progress:^(NSProgress * _Nonnull uploadProgress) {
NSLog(@"progress --- %@",uploadProgress.localizedDescription);
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"responseObject-------%@", responseObject);
dispatch_async(dispatch_get_main_queue(), ^{
});
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"Error-------%@", error);
}];
}
POST向服務器提交數(shù)據(jù)時,一般包括三個部分:請求行、請求頭、請求體。當我們要從文件、socket、NSData讀取較大數(shù)據(jù)到內(nèi)存時,對CPU的消耗是非常大的。所以為了防止內(nèi)存爆長,AFN采用了分片上傳的方式。
對于 [AFHTTPSessionManager manager]這個還是一樣,做了一個初始化。然而,[manager POST_xxx]方法卻做了很多,用我們提供的數(shù)據(jù)給我們把請求三部分做了一個封裝,這樣就省去了我們很多的拼接。
具體的來看
- (NSURLSessionDataTask *)POST:(NSString *)URLString
parameters:(id)parameters
constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block
progress:(nullable void (^)(NSProgress * _Nonnull))uploadProgress
success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure
{
NSError *serializationError = nil;
NSMutableURLRequest *request = [self.requestSerializer multipartFormRequestWithMethod:@"POST" URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters constructingBodyWithBlock:block 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 *task = [self uploadTaskWithStreamedRequest:request progress:uploadProgress completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
if (error) {
if (failure) {
failure(task, error);
}
} else {
if (success) {
success(task, responseObject);
}
}
}];
[task resume];
return task;
}
requestSerialization
點進POST方法,我們看到,這里做了兩步:生成request、用request生成task返回。用request生成的task這一步,主要是SessionManager做的,上一篇文章已經(jīng)講述,下面我們重點看一下,如何生成的request的請求體。源代碼如下
//構(gòu)建一個multipartForm的request。并且通過`AFMultipartFormData`類型的formData來構(gòu)建請求體
- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(NSDictionary *)parameters
constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block
error:(NSError *__autoreleasing *)error
{
NSParameterAssert(method);
//method不能是get、head
NSParameterAssert(![method isEqualToString:@"GET"] && ![method isEqualToString:@"HEAD"]);
NSMutableURLRequest *mutableRequest = [self requestWithMethod:method URLString:URLString parameters:nil error:error];
// 使用initWithURLRequest:stringEncoding:來初始化一個AFStreamingMultipartFormData變量
// 主要是為了構(gòu)建bodyStream
__block AFStreamingMultipartFormData *formData = [[AFStreamingMultipartFormData alloc] initWithURLRequest:mutableRequest stringEncoding:NSUTF8StringEncoding];
if (parameters) {
for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
NSData *data = nil;
if ([pair.value isKindOfClass:[NSData class]]) {
data = pair.value;
} else if ([pair.value isEqual:[NSNull null]]) {
data = [NSData data];
} else {
//通常nslog打印會調(diào)用description,打印出來的是地址,但是可以重寫description,來實現(xiàn)打印出我們想要的類型
data = [[pair.value description] dataUsingEncoding:self.stringEncoding];
}
if (data) {
// bodyStream構(gòu)造最主要的部分就在這了(雖然后面requestByFinalizingMultipartFormData函數(shù)還會稍微處理一下)
// 根據(jù)data和name構(gòu)建Request的header和body,后面詳解
[formData appendPartWithFormData:data name:[pair.field description]];
}
}
}
// 參考上面的例子,其實還是往formData中添加數(shù)據(jù)
if (block) {
block(formData);
}
// 做最終的處理,比如設置一下MultipartRequest的bodyStream或者其特有的content-type等等,后面也會詳解
return [formData requestByFinalizingMultipartFormData];
}
普通的post和multipart的post區(qū)別:
1.content-type:
2.httpbody/httpbodystrean
requestserialization:1.動態(tài)監(jiān)聽我們的屬性;2.設置請求頭;3.生成查詢字符串;4.分片上傳
responseSerialization
對于response的序列化,我們需要注意的一個點就是,當我們下載圖片時,圖片是經(jīng)過壓縮的。所以,當我們下載圖片時,需要手動解壓圖片。若不手動解壓,這個過程就會在渲染圖片時解壓,這樣這個解壓過程就在主線程進行了,嚴重影響性能,還好這個過程AFN已經(jīng)為我們做了。
下面,我們看看圖片解壓的核心代碼(AFInflatedImageFromResponseWithDataAtScale),代碼有點長,
static UIImage * AFInflatedImageFromResponseWithDataAtScale(NSHTTPURLResponse *response, NSData *data, CGFloat scale) {
if (!data || [data length] == 0) {
return nil;
}
CGImageRef imageRef = NULL;
// CoreGraphics對data的封裝
CGDataProviderRef dataProvider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data);
// 根據(jù)不同的格式進行轉(zhuǎn)化
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);
// CGImageCreateWithJPEGDataProvider does not properly handle CMKY, so fall back to AFImageWithDataAtScale
// 不能支持cmyk 轉(zhuǎn)為位圖
/*
CMKY:印刷色彩模式,用來印刷
RGB:
*/
if (imageColorSpaceModel == kCGColorSpaceModelCMYK) {
CGImageRelease(imageRef);
imageRef = NULL;
}
}
}
CGDataProviderRelease(dataProvider);
//根據(jù)創(chuàng)建原格式圖片
UIImage *image = AFImageWithDataAtScale(data, scale);
if (!imageRef) {
//如果imageRef為空,說明不是壓縮格式的圖片,或者無法進一步轉(zhuǎn)成Bitmap格式,直接返回原格式圖片
if (image.images || !image) {
return image;
}
// 如果imageRef為空
imageRef = CGImageCreateCopy([image CGImage]);
if (!imageRef) {
return nil;
}
}
size_t width = CGImageGetWidth(imageRef);
size_t height = CGImageGetHeight(imageRef);
// 獲得圖片的位數(shù)
size_t bitsPerComponent = CGImageGetBitsPerComponent(imageRef);
// 大于8位或者像素大于1024*1024 如果圖片太大,直接返回原格式圖片
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);
//如果是RGB顏色模型,根據(jù)像素是否包含alpha通道進行相應處理
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
}
//創(chuàng)建Bitmap的上下文
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);
//獲取Bitmap格式的圖片
CGImageRef inflatedImageRef = CGBitmapContextCreateImage(context);
CGContextRelease(context);
//轉(zhuǎn)化為UIImage對象
UIImage *inflatedImage = [[UIImage alloc] initWithCGImage:inflatedImageRef scale:scale orientation:image.imageOrientation];
CGImageRelease(inflatedImageRef);
CGImageRelease(imageRef);
return inflatedImage;
}