前言
- 網(wǎng)絡(luò)框架
本文一開始上傳圖片以調(diào)用HYNetworking的API為例骡显,這個(gè)網(wǎng)絡(luò)框架是以AFNetworking為基礎(chǔ)進(jìn)行的封裝浇冰。HYNetworking內(nèi)部實(shí)現(xiàn)上傳圖片的時(shí)候,其實(shí)就是采用AFNetworking關(guān)于上傳圖片的API洋魂,都是AFNetworking里面一個(gè)API绷旗。后面再講XMNetworking上傳圖片請求的操作方法,它也是基于AFNetworking上傳進(jìn)行的封裝副砍,不過比HYNetworking更加隱晦而已衔肢。
- 需求背景
這里的需求背景是,我們的app采用全球領(lǐng)先的AI方案提供商 -- 曠視科技 的Face++ SDK進(jìn)行身份證識別:它識別到身份證后會回調(diào)一個(gè)圖片數(shù)據(jù)豁翎,我們用此圖片向Face++公司的服務(wù)器請求驗(yàn)證角骤,該請求通過則block回調(diào)成功,接著將圖片數(shù)據(jù)保存到手機(jī)本地心剥,然后在合適的時(shí)機(jī)(比如邦尊,點(diǎn)擊“完成”或者“下一步”按鈕)把圖片數(shù)據(jù)上傳到自己公司的服務(wù)器。
- 先上總結(jié)
上傳圖片的流程圖如下所示
1. 獲取回調(diào)圖片
下面舉例一個(gè)典型Face++的例子
- 點(diǎn)擊“掃描身份證按鈕”事件處理
// 身份證掃描正面
__weak typeof(self) weakSelf = self;
BOOL idcard = [MGIDCardManager getLicense];
if (!idcard) {
[[[UIAlertView alloc] initWithTitle:@"提示" message:@"SDK授權(quán)失敗优烧,請檢查" delegate:self cancelButtonTitle:@"完成" otherButtonTitles:nil, nil] show];
return;
}
if ([CommonUtils isAllowedCamera] == NO) {
//無權(quán)限
UIAlertController *AlertController = [UIAlertController alertControllerWithTitle:@"“小滿APP”想訪問您的相機(jī)" message:@"需要您的同意胳赌,才能訪問相機(jī)" preferredStyle:UIAlertControllerStyleAlert];
[AlertController addAction:[UIAlertAction actionWithTitle:@"去設(shè)置" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];
}]];
[AlertController addAction:[UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
}]];
//彈出提示框;
[self presentViewController:AlertController animated:YES completion:nil];
return;
}
MGIDCardManager *cardManager = [[MGIDCardManager alloc] init];
[cardManager IDCardStartDetection:weakSelf IdCardSide:IDCARD_SIDE_FRONT
finish:^(MGIDCardModel *model) {
[_cardInfoVC sendFaceIDCardRequest:[model croppedImageOfIDCard]];
} errr:^(MGIDCardError) {
}];
- 其中匙隔,
croppedImageOfIDCard
是為了從回調(diào)的model中取出圖片: - Face++SDK中的MGIDCardModel.mm
#pragma mark - Return UIImage
- (UIImage *)croppedImageOfIDCard {
#if TARGET_IPHONE_SIMULATOR
return nil;
#else
return [self.result croppedImageOfIDCard];
#endif
}
- Face++SDK本地識別成功后,攜帶image向Face++后臺請求代碼(主要檢驗(yàn)身份證合法性等等)熏版,請求成功之后我們才將身份證圖片保存到本地:
- (void)sendFaceIDCardRequest:(UIImage *)image
{
[KVNProgress showWithParameters:@{KVNProgressViewParameterStatus: @"加載中...",
KVNProgressViewParameterBackgroundType: @(KVNProgressBackgroundTypeSolid),
KVNProgressViewParameterFullScreen: @(NO)}];
UIImage *cardImage = image;
NSMutableDictionary *params = [NSMutableDictionary dictionary];
[params setObject:api_key forKey:@"api_key"];
[params setObject:api_secret forKey:@"api_secret"];
[params setObject:@"1" forKey:@"legality"];
__weak typeof(self) weakSelf = self;
[HYBNetworking uploadWithImage:cardImage url:QueryCardId filename:@"image"
name:@"image" mimeType:@"image/jpeg" parameters:params progress:^(int64_t bytesWritten, int64_t totalBytesWritten) {}
success:^(id response) {
[KVNProgress dismiss];
NSDictionary *dic = response;
NSLog(@"%@",[dic mj_JSONString]);
NSDictionary *legalityDict = [dic objectForKey:@"legality"];
NSString *side = [dic objectForKey:@"side"];
if ([side isEqualToString:@"back"]) {
// 發(fā)證機(jī)關(guān)
self.cell.CREDITISSUEQRG = [dic objectForKey:@"issued_by"];
// valid_date 證件到期日
self.cell.CREDITCERTENDTIME = [dic objectForKey:@"valid_date"];
[weakSelf saveImage:image withName:@"reverseCardImage.png"];
weakSelf.cell.reverseCardImage = image;
[weakSelf.cell setClipReverseCardImage:image];
[weakSelf.cell setReverseCardImageDataInfo:legalityDict];
}
else if ([side isEqualToString:@"front"]) {
// 姓名
weakSelf.cell.nameTextField.text = [dic objectForKey:@"name"];
// 性別
//NSString *SEX = [dic objectForKey:@"race"];
// 證件號碼
weakSelf.cell.IDCardTextField.text = [dic objectForKey:@"id_card_number"];
// 戶籍所在地
weakSelf.cell.HOUSEHOLDADDR = [dic objectForKey:@"address"];
NSString *CERTID = [CommonUtils getValueInUDWithKey:kCERTID];
if (![CommonUtils isStringNilOrEmpty:CERTID]) {
if (![CERTID isEqualToString:[dic objectForKey:@"id_card_number"]]) {
[Toast showBottomWithText:@"您使用的身份證與您實(shí)名的身份證不一致"];
return;
}
}
// 更新UI
weakSelf.cell.positiveIDCardImage = image;//加載圖片
[weakSelf.cell setClipPositiveIDCardImage:image];
[weakSelf.cell setPositiveIDCardImageDataInfo:legalityDict];
// 出生年月
// NSDictionary *dict = [dic objectForKey:@"birthday"];
// NSString *day = [dict objectForKey:@"day"];
// NSString *month = [dict objectForKey:@"month"];
// NSString *year = [dict objectForKey:@"year"];
[weakSelf saveImage:image withName:@"positiveIDCardImage.png"];
}
} fail:^(NSError *error) {
[KVNProgress dismiss];
[Toast showBottomWithText:kNetworkErrorMsg];
}];
}
- 最后面有個(gè)
saveImage: withName:
就是向Face++請求成功之后纷责,進(jìn)行保存到本地操作。
#pragma mark - 保存圖片至沙盒
- (void)saveImage:(UIImage *)currentImage withName:(NSString *)imageName
{
if ([imageName isEqualToString:@"positiveIDCardImage.png"]) {
NSString *imgPath = [CommonUtils pathFileDocDir:@"/BorrowProcess/positiveIDCardImage.png"];
if ([CommonUtils fileExistAtPath:imgPath]) {
[CommonUtils fileDel:imgPath];
}
}
if ([imageName isEqualToString:@"reverseCardImage.png"]) {
NSString *imgPath1 = [CommonUtils pathFileDocDir:@"/BorrowProcess/reverseCardImage.png"];
if ([CommonUtils fileExistAtPath:imgPath1]) {
[CommonUtils fileDel:imgPath1];
}
}
float kCompressionQuality = 0.5;
NSData *imageData = UIImageJPEGRepresentation(currentImage, kCompressionQuality); //將圖片壓縮
NSString *fullPath = [[NSHomeDirectory() stringByAppendingPathComponent:@"Documents/BorrowProcess/"] stringByAppendingPathComponent:imageName]; //獲取沙盒目錄
//創(chuàng)建文件夾
[self createFile];
//保存圖片
BOOL _isWriteToFile = [imageData writeToFile:fullPath atomically:YES]; //將圖片寫入文件
}
2. 上傳回調(diào)圖片
通過上面的保存操作撼短,現(xiàn)在我們的APP到了點(diǎn)擊下一步的情形再膳,這時(shí)候需要我們向自己的后臺(不是Face++的后臺)上傳圖片了。
2.1 調(diào)用HYBNetworking的上傳圖片API
上傳圖片API
+ (HYBURLSessionTask *)uploadWithImage:(UIImage *)image
url:(NSString *)url
filename:(NSString *)filename
name:(NSString *)name
mimeType:(NSString *)mimeType
parameters:(NSDictionary *)parameters
progress:(HYBUploadProgress)progress
success:(HYBResponseSuccess)success
fail:(HYBResponseFail)fail
調(diào)用示例
#pragma mark - 上傳身份證正面
//請求接口
-(void)httpRequestFRONT{
NSString *LOANAPPLICATIONNO = nil;
NSMutableArray *dataArray = [self seekPlist];
if (dataArray) {
NSDictionary *dataDict = nil;
for (NSDictionary *dict in dataArray) {
NSInteger index = [[dict objectForKey:@"index"] integerValue];
if (index == 0) {
if (dict) {
dataDict = dict;
}
}
}
NSDictionary *dd = [dataDict objectForKey:@"BorrowProcess"];
LOANAPPLICATIONNO = [dd objectForKey:@"LOANAPPLICATIONNO"];
}
if ([CommonUtils isStringNilOrEmpty:LOANAPPLICATIONNO]) {
LOANAPPLICATIONNO = @"";
}
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *path = [paths objectAtIndex:0];
NSString *fileName = @"positiveIDCardImage";
NSString *filePath = [NSString stringWithFormat:@"%@/BorrowProcess/%@.png", path, fileName];
UIImage *image;
if ([self isFileExist:fileName] == YES)
{
//顯示本地緩存
image = [UIImage imageWithContentsOfFile:filePath];
}
[CommonUtils saveStrValueInUD:@"status016" forKey:kTRANSCODE];
[KVNProgress showWithParameters:@{KVNProgressViewParameterStatus: @"加載中...",
KVNProgressViewParameterBackgroundType: @(KVNProgressBackgroundTypeSolid),
KVNProgressViewParameterFullScreen: @(NO)}];
NSMutableDictionary *params = [NSMutableDictionary dictionary];
[params setObject:[CommonUtils getStrValueInUDWithKey:kTOKEN] forKey:@"TOKEN"];
// 用戶賬號
[params setObject:[CommonUtils getStrValueInUDWithKey:kUSERNO] forKey:@"USERNO"];
// NSArray *BUSINESSTYPE = @[];//文檔編號
// NSArray *BUSINESSNO = @[];//業(yè)務(wù)編號
// NSArray *DOCTYPE = @[];//文檔類型
[params setObject:@"LOAN_APPLY" forKey:@"BUSINESSTYPE"];
[params setObject:LOANAPPLICATIONNO forKey:@"BUSINESSNO"];
[params setObject:@"IDCARD_FRONT_APP" forKey:@"DOCTYPE"];
__weak typeof(self) weakSelf = self;
//上傳圖片
[HYBNetworking uploadWithImage:image url:uploadImage filename:@"IDFrontImage.jpg" name:@"FILE" mimeType:@"image/jpeg" parameters:params progress:^(int64_t bytesWritten, int64_t totalBytesWritten) {
//獲取進(jìn)度:bytesWritten/totalBytesWritten
} success:^(id response) {
[KVNProgress dismiss];
typeof(weakSelf) strongSelf = weakSelf;
NSString *stringData = [response mj_JSONString];
stringData = [DES3Util decrypt:stringData];
NSLog(@"stirngData: %@", stringData);
NSDictionary *responDict = [stringData mj_JSONObject];
NSString *RETCODE = [responDict objectForKey:@"RETCODE"];
if ([RETCODE isEqualToString:kSUCCESS]) {
[strongSelf httpRequestBACK];
}else if([RETCODE isEqualToString:kROKEN]){
NSDictionary *processdict = @{@"index":[NSNumber numberWithInteger:10], @"showProcessView":[NSNumber numberWithBool:YES]};
[[NSNotificationCenter defaultCenter] postNotificationName:kBorrowProcessNotication object:nil userInfo:processdict];
}else{
NSString *RETMSG = [responDict objectForKey:@"RETMSG"];
if (![CommonUtils isStringNilOrEmpty:RETMSG]) {
[Toast showBottomWithText:RETMSG];
}
}
} fail:^(NSError *error) {
[KVNProgress dismiss];
[Toast showBottomWithText:kNetworkErrorMsg];
}];
}
2.2 調(diào)用AFNetwork整合圖片的API -- 暨HYBNetworing上傳圖片封裝源碼解析
整合圖片API
- (void)appendPartWithFileData:(NSData *)data
name:(NSString *)name
fileName:(NSString *)fileName
mimeType:(NSString *)mimeType
調(diào)用示例 -- 上述2.1節(jié)中HYBNetworking的上傳圖片其實(shí)是調(diào)用AFNetworking的上傳圖片API曲横。所以喂柒,HYBNetworking框架中上傳圖片的源碼實(shí)現(xiàn)就是調(diào)用AFNetworking上傳圖片API的一個(gè)示例:
- HYBNetworking.m
+ (HYBURLSessionTask *)uploadWithImage:(UIImage *)image
url:(NSString *)url
filename:(NSString *)filename
name:(NSString *)name
mimeType:(NSString *)mimeType
parameters:(NSDictionary *)parameters
progress:(HYBUploadProgress)progress
success:(HYBResponseSuccess)success
fail:(HYBResponseFail)fail {
if ([self baseUrl] == nil) {
if ([NSURL URLWithString:url] == nil) {
HYBAppLog(@"URLString無效不瓶,無法生成URL≡纸埽可能是URL中有中文蚊丐,請嘗試Encode URL");
return nil;
}
} else {
if ([NSURL URLWithString:[NSString stringWithFormat:@"%@%@", [self baseUrl], url]] == nil) {
HYBAppLog(@"URLString無效,無法生成URL艳吠÷蟊福可能是URL中有中文,請嘗試Encode URL");
return nil;
}
}
if ([self shouldEncode]) {
url = [self encodeUrl:url];
}
NSString *absolute = [self absoluteUrlWithPath:url];
AFHTTPSessionManager *manager = [self manager];
HYBURLSessionTask *session = [manager POST:url parameters:parameters constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) {
NSData *imageData = UIImageJPEGRepresentation(image, 1);
NSString *imageFileName = filename;
if (filename == nil || ![filename isKindOfClass:[NSString class]] || filename.length == 0) {
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.dateFormat = @"yyyyMMddHHmmss";
NSString *str = [formatter stringFromDate:[NSDate date]];
imageFileName = [NSString stringWithFormat:@"%@.jpg", str];
}
// 整合圖片昭娩,以文件流的格式
[formData appendPartWithFileData:imageData name:name fileName:imageFileName mimeType:mimeType];
} progress:^(NSProgress * _Nonnull uploadProgress) {
if (progress) {
progress(uploadProgress.completedUnitCount, uploadProgress.totalUnitCount);
}
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[[self allTasks] removeObject:task];
[self successResponse:responseObject callback:success];
if ([self isDebug]) {
[self logWithSuccessResponse:responseObject
url:absolute
params:parameters];
}
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[[self allTasks] removeObject:task];
[self handleCallbackWithError:error fail:fail];
if ([self isDebug]) {
[self logWithFailError:error url:absolute params:nil];
}
}];
[session resume];
if (session) {
[[self allTasks] addObject:session];
}
return session;
}
可見凛篙,整合圖片的一句關(guān)鍵代碼在
// 整合圖片,以文件流的格式
[formData appendPartWithFileData:imageData name:name fileName:imageFileName mimeType:mimeType];
這里是設(shè)置圖片的數(shù)據(jù)流栏渺,作為AFNetwork的POST請求方法的一個(gè)constructingBodyWithBlock
參數(shù)的輸入呛梆。
AFNetwork的POST請求方法源碼
- AFHTTPSessionManager.m
- (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) {
dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
failure(nil, serializationError);
});
}
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;
}
這個(gè)方法將block傳遞給下一個(gè)API,并返回一個(gè)request:
- AFURLRequestSerialization.m
- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(NSDictionary *)parameters
constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block
error:(NSError *__autoreleasing *)error
{
NSParameterAssert(method);
NSParameterAssert(![method isEqualToString:@"GET"] && ![method isEqualToString:@"HEAD"]);
NSMutableURLRequest *mutableRequest = [self requestWithMethod:method URLString:URLString parameters:nil error:error];
__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 {
data = [[pair.value description] dataUsingEncoding:self.stringEncoding];
}
if (data) {
[formData appendPartWithFormData:data name:[pair.field description]];
}
}
}
if (block) {
block(formData);
}
return [formData requestByFinalizingMultipartFormData];
}
- 這個(gè)方法的關(guān)鍵在于最后磕诊,用block回調(diào)了formData:
if (block) {
block(formData);
}
這個(gè)formData是一個(gè)AFStreamingMultipartFormData模型的對象
圖片模型 -- AFStreamingMultipartFormData
- AFURLRequestSerialization.m
@interface AFStreamingMultipartFormData ()
@property (readwrite, nonatomic, copy) NSMutableURLRequest *request;
@property (readwrite, nonatomic, assign) NSStringEncoding stringEncoding;
@property (readwrite, nonatomic, copy) NSString *boundary;
@property (readwrite, nonatomic, strong) AFMultipartBodyStream *bodyStream;
@end
這樣填物,就執(zhí)行了前面調(diào)用時(shí)block里面的圖片數(shù)據(jù)整合操作([formData appendPartWithFileData...];)。
在
multipartFormRequestWithMethod:...
的實(shí)現(xiàn)代碼中秀仲,接著融痛,利用block體中設(shè)置好的formData,調(diào)用下述的requestByFinalizingMultipartFormData
方法以返回一個(gè)request神僵,然后進(jìn)行請求雁刷。requestByFinalizingMultipartFormData
的代碼如下:
- AFURLRequestSerialization.m
- (NSMutableURLRequest *)requestByFinalizingMultipartFormData {
if ([self.bodyStream isEmpty]) {
return self.request;
}
// Reset the initial and final boundaries to ensure correct Content-Length
[self.bodyStream setInitialAndFinalBoundaries];
[self.request setHTTPBodyStream:self.bodyStream];
[self.request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", self.boundary] forHTTPHeaderField:@"Content-Type"];
[self.request setValue:[NSString stringWithFormat:@"%llu", [self.bodyStream contentLength]] forHTTPHeaderField:@"Content-Length"];
return self.request;
}
上面代碼的關(guān)鍵在于將圖片數(shù)據(jù)整合進(jìn)request,設(shè)置HTTPBodyStream保礼,即[self.request setHTTPBodyStream:self.bodyStream];
沛励。
- 獲取上述request之后,如前面所述“ AFNetwork的POST請求方法源碼”炮障,調(diào)用返回request的API之后目派,再調(diào)用POST請求方法進(jìn)行請求操作,即
__block NSURLSessionDataTask *task = [self uploadTaskWithStreamedRequest:request progress:uploadProgress completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
這一句胁赢。
獲取帶有圖片request的API
- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(NSDictionary *)parameters
constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block
error:(NSError *__autoreleasing *)error
上傳帶有圖片request的API
- (NSURLSessionUploadTask *)uploadTaskWithStreamedRequest:(NSURLRequest *)request
progress:(void (^)(NSProgress *uploadProgress)) uploadProgressBlock
completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
2.3 源碼分析 -- AFNetwork整合圖片數(shù)據(jù)調(diào)用棧解析
- 將描述圖片的參數(shù)字符串轉(zhuǎn)化頭字典
- (void)appendPartWithFileData:(NSData *)data
name:(NSString *)name
fileName:(NSString *)fileName
mimeType:(NSString *)mimeType
{
NSParameterAssert(name);
NSParameterAssert(fileName);
NSParameterAssert(mimeType);
NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary];
[mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"; filename=\"%@\"", name, fileName] forKey:@"Content-Disposition"];
[mutableHeaders setValue:mimeType forKey:@"Content-Type"];
[self appendPartWithHeaders:mutableHeaders body:data];
}
- 為圖片數(shù)據(jù)添加頭字典 -- 數(shù)據(jù)整合
- (void)appendPartWithHeaders:(NSDictionary *)headers
body:(NSData *)body
{
NSParameterAssert(body);
AFHTTPBodyPart *bodyPart = [[AFHTTPBodyPart alloc] init];
bodyPart.stringEncoding = self.stringEncoding;
bodyPart.headers = headers;
bodyPart.boundary = self.boundary;
bodyPart.bodyContentLength = [body length];
bodyPart.body = body;
[self.bodyStream appendHTTPBodyPart:bodyPart];
}
其中企蹭,數(shù)據(jù)流self.bodyStream
是這樣定義的:
@property (readwrite, nonatomic, strong) AFMultipartBodyStream *bodyStream;
其中,AFMultipartBodyStream
是這樣定義的:
@interface AFMultipartBodyStream : NSInputStream <NSStreamDelegate>
- 再來看看怎樣向數(shù)據(jù)流添加整合好的圖片數(shù)據(jù)的:
- (void)appendHTTPBodyPart:(AFHTTPBodyPart *)bodyPart {
[self.HTTPBodyParts addObject:bodyPart];
}
- 其中智末,
HTTPBodyParts
是這樣定義的:
@property (readwrite, nonatomic, strong) NSMutableArray *HTTPBodyParts;
- 另外谅摄,
AFHTTPBodyPart
的定義是這樣的:
@interface AFHTTPBodyPart : NSObject
@property (nonatomic, assign) NSStringEncoding stringEncoding;
@property (nonatomic, strong) NSDictionary *headers;
@property (nonatomic, copy) NSString *boundary;
@property (nonatomic, strong) id body;
@property (nonatomic, assign) unsigned long long bodyContentLength;
@property (nonatomic, strong) NSInputStream *inputStream;
@property (nonatomic, assign) BOOL hasInitialBoundary;
@property (nonatomic, assign) BOOL hasFinalBoundary;
@property (readonly, nonatomic, assign, getter = hasBytesAvailable) BOOL bytesAvailable;
@property (readonly, nonatomic, assign) unsigned long long contentLength;
3. XMNetworking
XMNetworking其實(shí)也是基于ANNetwork封裝的,不過封裝的層級比HYBNetworking多系馆,看起來有點(diǎn)隱晦送漠。這里分析一下XMNetworking上傳圖片的API。
3.1 整合圖片
- 整合圖片API
- (void)addFormDataWithName:(NSString *)name fileName:(NSString *)fileName mimeType:(NSString *)mimeType fileData:(NSData *)fileData
- 調(diào)用示例
- (NSString *_Nullable)uploadWithUploadImageModels:(nullable NSArray<UploadImageModel *> *)imageModelArr params:(id _Nullable )params paramsType:(ParamsType)paramsType userNo:(NSString *)userNo APITag:(API_Tag)tag mimeType:(nullable NSString *)mimeType
OnSuccess:(SuccessBlock)successBlock onFailure:(FailureBlock)failureBlock
{
NSString *identifier = [XMCenter sendRequest:^(XMRequest * _Nonnull request) {
request.api = [self pathURLWithAPITag:tag];
if ([self respondsToSelector:@selector(cys_requestOnlyLayerParameters:)]) {
request.parameters = [self cys_requestOnlyLayerParameters:params];
}
// 上傳圖片由蘑,以文件流的格式
for (UploadImageModel *imageModel in imageModelArr) {
NSData *imageData = UIImageJPEGRepresentation(imageModel.image, 0.5);
NSString *imageFileName = imageModel.fileName;
if (imageModel.fileName == nil || ![imageModel.fileName isKindOfClass:[NSString class]] || imageModel.fileName.length == 0) {
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.dateFormat = @"yyyyMMddHHmmss";
NSString *str = [formatter stringFromDate:[NSDate date]];
imageFileName = [NSString stringWithFormat:@"%@.jpg", str];
}
// 上傳圖片
[request addFormDataWithName:imageModel.name fileName:imageFileName mimeType:mimeType fileData:imageData];
}
NSLog(@"請求的參數(shù):%@",request.parameters);
if (imageModelArr.count > 0) {
request.requestType = kXMRequestUpload;
}
request.requestSerializerType = kXMRequestSerializerJSON;
} onSuccess:^(id _Nullable responseObject) {
// 省略...
3.2 上傳圖片API源碼解析
- 將整合好的圖片數(shù)據(jù)formData添加到圖片模型數(shù)組uploadFormDatas
- (void)addFormDataWithName:(NSString *)name fileName:(NSString *)fileName mimeType:(NSString *)mimeType fileData:(NSData *)fileData {
XMUploadFormData *formData = [XMUploadFormData formDataWithName:name fileName:fileName mimeType:mimeType fileData:fileData];
[self.uploadFormDatas addObject:formData];
}
- 整合圖片數(shù)據(jù)
+ (instancetype)formDataWithName:(NSString *)name fileName:(NSString *)fileName mimeType:(NSString *)mimeType fileData:(NSData *)fileData {
XMUploadFormData *formData = [[XMUploadFormData alloc] init];
formData.name = name;
formData.fileName = fileName;
formData.mimeType = mimeType;
formData.fileData = fileData;
return formData;
}
- 圖片模型數(shù)組
- (NSMutableArray<XMUploadFormData *> *)uploadFormDatas {
if (!_uploadFormDatas) {
_uploadFormDatas = [NSMutableArray array];
}
return _uploadFormDatas;
}
- 圖片模型 -- XMUploadFormData
/**
`XMUploadFormData` is the class for describing and carring the upload file data, see `AFMultipartFormData` protocol for details.
*/
@interface XMUploadFormData : NSObject
/**
The name to be associated with the specified data. This property must not be `nil`.
*/
@property (nonatomic, copy) NSString *name;
/**
The file name to be used in the `Content-Disposition` header. This property is not recommended be `nil`.
*/
@property (nonatomic, copy, nullable) NSString *fileName;
/**
The declared MIME type of the file data. This property is not recommended be `nil`.
*/
@property (nonatomic, copy, nullable) NSString *mimeType;
/**
The data to be encoded and appended to the form data, and it is prior than `fileURL`.
*/
@property (nonatomic, strong, nullable) NSData *fileData;
/**
The URL corresponding to the file whose content will be appended to the form, BUT, when the `fileData` is assigned闽寡,the `fileURL` will be ignored.
*/
@property (nonatomic, strong, nullable) NSURL *fileURL;
// NOTE: Either of the `fileData` and `fileURL` should not be `nil`, and the `fileName` and `mimeType` must both be `nil` or assigned at the same time,
- 遍歷圖片模型數(shù)組中的圖片模型進(jìn)行上傳請求
- (void)xm_uploadTaskWithRequest:(XMRequest *)request
completionHandler:(XMCompletionHandler)completionHandler {
AFHTTPSessionManager *sessionManager = [self xm_getSessionManager:request];
AFHTTPRequestSerializer *requestSerializer = [self xm_getRequestSerializer:request];
__block NSError *serializationError = nil;
NSMutableURLRequest *urlRequest = [requestSerializer multipartFormRequestWithMethod:@"POST"
URLString:request.url
parameters:request.parameters
constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
[request.uploadFormDatas enumerateObjectsUsingBlock:^(XMUploadFormData *obj, NSUInteger idx, BOOL *stop) {
if (obj.fileData) {
if (obj.fileName && obj.mimeType) {
[formData appendPartWithFileData:obj.fileData name:obj.name fileName:obj.fileName mimeType:obj.mimeType];
} else {
[formData appendPartWithFormData:obj.fileData name:obj.name];
}
} else if (obj.fileURL) {
NSError *fileError = nil;
if (obj.fileName && obj.mimeType) {
[formData appendPartWithFileURL:obj.fileURL name:obj.name fileName:obj.fileName mimeType:obj.mimeType error:&fileError];
} else {
[formData appendPartWithFileURL:obj.fileURL name:obj.name error:&fileError];
}
if (fileError) {
serializationError = fileError;
*stop = YES;
}
}
}];
} error:&serializationError];
if (serializationError) {
if (completionHandler) {
dispatch_async(xm_request_completion_callback_queue(), ^{
completionHandler(nil, serializationError);
});
}
return;
}
[self xm_processURLRequest:urlRequest byXMRequest:request];
NSURLSessionUploadTask *uploadTask = nil;
__weak __typeof(self)weakSelf = self;
uploadTask = [sessionManager uploadTaskWithStreamedRequest:urlRequest
progress:request.progressBlock
completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
[strongSelf xm_processResponse:response
object:responseObject
error:error
request:request
completionHandler:completionHandler];
}];
[self xm_setIdentifierForReqeust:request taskIdentifier:uploadTask.taskIdentifier sessionManager:sessionManager];
[uploadTask bindingRequest:request];
[uploadTask resume];
}
- 可見代兵,XMNetworking最后還是調(diào)用AFNetwork的POST請求及
uploadTaskWithStreamedRequest
方法進(jìn)行上傳。
- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(NSDictionary *)parameters
constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block
error:(NSError *__autoreleasing *)error
- 同樣可見爷狈,同HYBNetworking一樣植影,調(diào)用AFNetworking的圖片數(shù)據(jù)整合API
appendPartWithFileData:
進(jìn)行設(shè)置
[formData appendPartWithFileData:obj.fileData name:obj.name fileName:obj.fileName mimeType:obj.mimeType];
4. 總結(jié):上傳圖片邏輯整理
AFNetwork
- 壓縮轉(zhuǎn)換:UIImage實(shí)例對象通過UIImageJPEGRepresentation壓縮轉(zhuǎn)換為NSData,下面稱之為imageData淆院。
- 信息整合:將imageData與文件名fileName何乎,文件路徑name,類型名mimeType整合成圖片模型(AFHTTPBodyPart)的一個(gè)對象bodyPart中去土辩。
- 添加圖片模型:將上面新建好的圖片模型對象bodyPart支救,向圖片輸入流(AFMultipartBodyStream)的對象bodyStream的數(shù)組屬性(HTTPBodyParts)添加。
- 設(shè)置requet的HTTPBodyStream屬性為bodyStream:封裝為
requestByFinalizingMultipartFormData
- 將圖片模型對象formData用AFNetwork的POST請求與
uploadTaskWithStreamedRequest
方法進(jìn)行上傳拷淘。
HYBNetworking
- 壓縮轉(zhuǎn)換:UIImage實(shí)例對象通過UIImageJPEGRepresentation壓縮轉(zhuǎn)換為NSData各墨,下面稱之為imageData。
- 信息整合:利用AFNetwork的
appendPartWithFileData
启涯,將imageData與文件名fileName贬堵,文件路徑name,類型名mimeType整合成圖片模型(AFStreamingMultipartFormData)的一個(gè)對象formData中去结洼。 - 將圖片模型對象formData用AFNetwork的POST請求與
uploadTaskWithStreamedRequest
方法進(jìn)行上傳黎做。
XMNetworking
- 壓縮轉(zhuǎn)換:UIImage實(shí)例對象通過UIImageJPEGRepresentation壓縮轉(zhuǎn)換為NSData,下面稱之為imageData松忍。
- 信息整合:利用AFNetwork的
appendPartWithFileData
蒸殿,將imageData與文件名fileName,文件路徑name鸣峭,類型名mimeType整合成圖片模型(XMUploadFormData)的一個(gè)對象formData中去宏所。 - 添加圖片模型:向管理器的圖片模型數(shù)組uploadFormDatas添加上面新建好的圖片模型對象formData。
- 遍歷圖片模型數(shù)組摊溶,獲得圖片模型爬骤,利用AFNetwork的POST請求與
uploadTaskWithStreamedRequest
方法進(jìn)行上傳。
5. 說明
本文示例只做了最簡單的請求方式 -- 發(fā)起一次請求就調(diào)用一次莫换。
其實(shí)霞玄,還有很多可以優(yōu)化的點(diǎn),例如拉岁,對所有request進(jìn)行管理封裝:建立一個(gè)請求的隊(duì)列或者數(shù)組溃列,相同請求不允許再添加,優(yōu)先級低的請求先等待膛薛,異步請求的最大并發(fā)線程數(shù),等等补鼻。這里只提醒哄啄,就不介紹了雅任。