iOS網(wǎng)絡(luò)請求之上傳圖片:從示例到源碼解析 -- 以上傳Face++SDK回調(diào)的圖片為例(HYNetworking拧粪,AFNetworking兑巾,XMNetworking)

前言

  • 網(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...];)。


圖片數(shù)據(jù)整合操作

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ù),等等补鼻。這里只提醒哄啄,就不介紹了雅任。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市咨跌,隨后出現(xiàn)的幾起案子沪么,更是在濱河造成了極大的恐慌,老刑警劉巖锌半,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件禽车,死亡現(xiàn)場離奇詭異,居然都是意外死亡刊殉,警方通過查閱死者的電腦和手機(jī)殉摔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來记焊,“玉大人逸月,你說我怎么就攤上這事”槟ぃ” “怎么了碗硬?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長瓢颅。 經(jīng)常有香客問我恩尾,道長,這世上最難降的妖魔是什么挽懦? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任翰意,我火速辦了婚禮,結(jié)果婚禮上巾兆,老公的妹妹穿的比我還像新娘猎物。我一直安慰自己,他們只是感情好角塑,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布蔫磨。 她就那樣靜靜地躺著,像睡著了一般圃伶。 火紅的嫁衣襯著肌膚如雪堤如。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天窒朋,我揣著相機(jī)與錄音搀罢,去河邊找鬼。 笑死侥猩,一個(gè)胖子當(dāng)著我的面吹牛榔至,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播欺劳,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼唧取,長吁一口氣:“原來是場噩夢啊……” “哼铅鲤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起枫弟,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤邢享,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后淡诗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體骇塘,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年韩容,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了款违。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,690評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡宙攻,死狀恐怖奠货,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情座掘,我是刑警寧澤递惋,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站溢陪,受9級特大地震影響萍虽,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜形真,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一杉编、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧咆霜,春花似錦邓馒、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至脉课,卻和暖如春救军,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背倘零。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工唱遭, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人呈驶。 一個(gè)月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓拷泽,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子跌穗,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理订晌,服務(wù)發(fā)現(xiàn),斷路器蚌吸,智...
    卡卡羅2017閱讀 134,651評論 18 139
  • AFHTTPRequestOperationManager 網(wǎng)絡(luò)傳輸協(xié)議UDP、TCP砌庄、Http羹唠、Socket、X...
    Carden閱讀 4,337評論 0 12
  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時(shí)...
    歐辰_OSR閱讀 29,371評論 8 265
  • 因?yàn)閷W(xué)校要體測,所以沒有像往常一樣周五回家,周六一早就起床收拾了,中午才到家娄昆。大概是春天到了,小區(qū)的的桃花開的正好...
    mute_b583閱讀 132評論 0 0
  • 這幾天一直忙著學(xué)習(xí)佩微,我每天早間抽出一些時(shí)間畫了幾幅畫。 這幾幅都是來自村上春樹作品《沒有女人的男人們》中的小插畫萌焰。...
    輝高閱讀 1,153評論 4 4