zoukankan      html  css  js  c++  java
  • AFNetworking二次封装的那些事

    AFNetworking可是iOS网络开发的神器,大大简便了操作.不过网络可是重中之重,不能只会用AFNetworking.我觉得网络开发首先要懂基本的理论,例如tcp/ip,http协议,之后要了解web的请求和响应,会使用苹果自带的NSURLSession,最后是把AFNetworking的源码啃掉.

    前言

    一直以来网络开发用的都是前面同事基于AFNetworking二次封装好的框架,一直都没什么问题,也就没往深处去了解.然后公司开始新项目了,iOS端由我负责,这可是我的第一次啊,从零开始,构建整个项目.这是个挑战,内心还是有点小激动的.

    轮子肯定是不用重复造的,网络框架就拿的老项目的,结果出现了两个问题.

    • 上传多张图片,服务端解析不了
    • 无文件上传, Content-Type还是multipart/form-data

    为了解决这个问题,从就没用过的NSURLSession到http协议,追本溯源,终于解决了.

    http协议

    关于http协议的理论就不多讲了,主要就讲使用POST方法传递数据时,发送的请求头和请求体.

    Content-Type

    我们提交的数据是什么编码方式服务端是不知道的,其实我们完全可以自定义格式,只要服务端取到数据后能解析就可以了.
    一般服务端语言如 php、python 等,以及它们的 framework,都内置了自动解析常见数据格式的功能。所以我们一般都用那几种常见的数据格式,数据格式就是请求头里的Content-Type,服务端根据Content-Type来解析请求体里的数据.
    一般有四种最常见的方式:

    • application/x-www-form-urlencoded
      这是默认的方式,以key1=val1&key2=val2的方式进行编码.
      country=0&pro=1023&city=102301
    • multipart/form-data
      这个首先生成了一个 boundary 用于分割不同的字段,为了避免与正文内容重复, boundary 很长很复杂,一般有文件上传的时候用这种方法
      --Boundary+A675D0398A56493A
      Content-Disposition: form-data; name="photoFiles";    filename="photoFiles.jpeg"
      Content-Type: image/jpeg  
    
    • application/json
      这个非常常见了,感觉它适合格式支持比键值对复杂得多的结构化数据.
      {"country":0,"pro":1023,"city":102301}
    • text/xml
      它是一种使用 HTTP 作为传输协议,XML 作为编码方式的远程调用规范.
      移动端一般不会用它,太臃肿了,加的标签完全是浪费.

    所以传文件用multipart/form-data.
    application/json和application/x-www-form-urlencoded差不多,但是application/json数据更加直观,更适合结构更加复杂的数据.
    我特地抓包了网易新闻等app,没有文件的情况下是application/json

    NSMutableURLRequest设置

    既然知道了请求头,请求体.那么给服务器端发送的请求按照上面的格式设置即可.

        //上传一张图片为例
        NSMutableURLRequest *request= [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:5.0f];
        request.HTTPMethod=@"POST";
       
        //数据体设置
        NSMutableData *dataM=[NSMutableData data];
        NSString *strTop=[NSString stringWithFormat:@"--%@
    Content-Disposition: form-data; name="file1"; filename="%@"
    Content-Type: %@
    
    ",kBOUNDARY_STRING,fileName,@"image/jpg"];
        NSString *strBottom=[NSString stringWithFormat:@"
    --%@--",kBOUNDARY_STRING];
        NSString *filePath=[[NSBundle mainBundle] pathForResource:fileName ofType:nil];
        NSData *fileData=[NSData dataWithContentsOfFile:filePath];
        [dataM appendData:[strTop dataUsingEncoding:NSUTF8StringEncoding]];
        [dataM appendData:fileData];
        [dataM appendData:[strBottom dataUsingEncoding:NSUTF8StringEncoding]];
    
        
        //通过请求头设置
        [request setValue:[NSString stringWithFormat:@"%lu",(unsigned long)dataM.length] forHTTPHeaderField:@"Content-Length"];
        [request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@",kBOUNDARY_STRING] forHTTPHeaderField:@"Content-Type"];
        
        //设置数据体
        request.HTTPBody=dataM;
    

    AFNetworking

    AFNetworking就是基于NSMutableURLRequest,NSURLSession的封装,非常好用.
    以POST为例,它有两个方法:

    - (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
                                 parameters:(nullable id)parameters
                                   progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress
                                    success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
                                    failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
    
    - (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
                                 parameters:(nullable id)parameters
                  constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block
                                   progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress
                                    success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
                                    failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
    
    

    区别就在于constructingBodyWithBlock,这也是我遇到的两个问题的根源.

    往下看,会发现,第一个方法:
    [mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];

    而第二个方法:

    //先执行我们传进去的block
    if (block) {
        block(formData);
    }
    return [formData requestByFinalizingMultipartFormData];
    
    //如果参数为空,则content-type为默认的,不为空则为multipart/form-data
    - (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;
    }
    
    
    

    这样我的问题就迎刃而解了.
    前同事为了统一处理,所有的请求都是经第二个函数,所以即使不是传文件,如果有参数, Content-Type还是multipart/form-data.
    而block是统一处理的:

        if ([obj isKindOfClass:[UIImage class]]) {
                    UIImage *image = obj;
                    if (image.size.height > 1080 || image.size.width > 1080) {
                        
                        image = [image imageWithMaxSide:1080];
                        
                        [formData appendPartWithFileData:UIImageJPEGRepresentation(image, 0.3f) name:key fileName:[NSString stringWithFormat:@"%@.jpeg",key] mimeType:@"image/jpeg"];
                    } else if (image.size.height > 600 || image.size.width > 600)  {
                        [formData appendPartWithFileData:UIImageJPEGRepresentation(image, 0.5f) name:key fileName:[NSString stringWithFormat:@"%@.jpeg",key] mimeType:@"image/jpeg"];
                    } else {
                        [formData appendPartWithFileData:UIImageJPEGRepresentation(image, 1) name:key fileName:[NSString stringWithFormat:@"%@.jpeg",key] mimeType:@"image/jpeg"];
                    }
                }
    

    只处理了传一张照片,没有考虑到传数组里面放多张图片的情况,所以服务器不知道这是图片啊,怎么解析呢!
    上一个项目也没有传多张图片的情况,所以一直没有发现这个问题.

    最后的解决方法是:

    .h  (继承AFHTTPSessionManager)
    //这个函数调用AF的第一个方法,不传文件时调用
    + (void)requestDataWithHTTPPath:(NSString *)path
                         parameters:(NSDictionary *)parameters
                            success:(RequestSuccessBlock)success
                            failure:(RequestFailureBlock)failure;
    
    //这个函数调用AF的第二个方法,传文件时用
    + (void)uploadFileWithHTTPPath:(NSString *)path
                         parameters:(NSDictionary *)parameters
                            success:(RequestSuccessBlock)success
                            failure:(RequestFailureBlock)failure;  
    
    .m
    + (void)requestDataWithHTTPPath:(NSString *)path
                         parameters:(NSDictionary *)parameters
                            success:(RequestSuccessBlock)success
                            failure:(RequestFailureBlock)failure {
        [KGAPIClient sharedRequestDataClient].isUploadFile = NO;
        [[KGAPIClient sharedRequestDataClient] requestDataWithHTTPPath:path parameters:parameters success:success failure:failure];
    }
    
    + (void)uploadFileWithHTTPPath:(NSString *)path
                        parameters:(NSDictionary *)parameters
                           success:(RequestSuccessBlock)success
                           failure:(RequestFailureBlock)failure {
        [KGAPIClient sharedRequestDataClient].isUploadFile = YES;
        [[KGAPIClient sharedRequestDataClient] requestDataWithHTTPPath:path parameters:parameters success:success failure:failure];
    }
    
    //统一处理一些错误
    #pragma mark - Request Method
    - (void)requestDataWithHTTPPath:(NSString *)path
                         parameters:(NSDictionary *)parameters
                            success:(RequestSuccessBlock)success
                            failure:(RequestFailureBlock)failure {
        [self requestWithHTTPPath:path parameters:parameters success:^(NSURLSessionDataTask *task, id responseObject) {
            NSString *status = [responseObject valueForKey:kHTTPResponseStatsusKey];
            if (status.length) {
                if ([status isEqualToString:kHTTPSuccessCode0000]) {
                    if (success) {
                        success(task, responseObject);
                    }
                } else if ([status isEqualToString:kHTTPErrorCode0001]) {
                    if (failure) {
                        [SVProgressHUD showErrorWithStatus:[responseObject valueForKey:kHTTPResponseMessageKey]];
                        failure(task, [self requestErrorWithDomin:[responseObject valueForKey:kHTTPResponseMessageKey] errorCode:nil]);
                    }
                }//.......还有别的一些状态码
            } else {
                if (failure) {
                    NSString *message = [responseObject valueForKey:kHTTPResponseMessageKey];
                    failure(task, [NSError errorWithDomain:message code:[status integerValue] userInfo:responseObject]);
                }
            }
        } failure:^(NSURLSessionDataTask *task, NSError *error) {
            if (failure) {
                NSError *aError = [NSError errorWithDomain:@"网络不给力" code:error.code userInfo:error.userInfo];
                failure(task, aError);
            }
        }];
    }
    
    //区分两种POST方式
    - (void)requestWithHTTPPath:(NSString *)path
                     parameters:(NSDictionary *)parameters
                        success:(RequestSuccessBlock)success
                        failure:(RequestFailureBlock)failure {
        NSDictionary *tmpDict = [KGAPIClient getNewParamsWithOldParams:parameters.mutableCopy];
        
        if (_isUploadFile) {
            [self POST:path parameters:tmpDict constructingBodyWithBlock:^(id<AFMultipartFormData>  _Nonnull formData) {
                [self appendPartWithparameters:parameters formData:formData];
            } progress:^(NSProgress * _Nonnull uploadProgress) {
                NSLog(@"******progress***");
            } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
                if (success) {
                    success(task, responseObject);
                }
            } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
                if (failure) {
                    failure(task, error);
                }
            }];
        }
        else {
            [self POST:path parameters:tmpDict progress:^(NSProgress * _Nonnull uploadProgress) {
                NSLog(@"******progress***");
            } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
                if (success) {
                    success(task, responseObject);
                }
            } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
                if (failure && ![path isEqualToString:APIPathWithUserReportGeo] && ![path isEqualToString:APIPathWithUserNickExsits]) {
                    failure(task, error);
                }
            }];
        }
    }  
    
    //用于设置data的type,还不全,暂时考虑image,image数组,.mp3文件.
    - (void)appendPartWithparameters:(NSDictionary *)parameters formData:(id<AFMultipartFormData>  _Nonnull)formData {
        if (parameters) {
            [parameters enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
                if ([obj isKindOfClass:[NSString class]] &&
                    ([obj hasSuffix:@".png"] ||
                     [obj hasSuffix:@".jpg"] ||
                     [obj hasSuffix:@".jpeg"])) {
                        [formData appendPartWithFileURL:obj name:key fileName:[NSString stringWithFormat:@"%@.jpg",key] mimeType:@"image/jpeg" error:nil];
                    }
                else if ([obj isKindOfClass:[UIImage class]]) {
                    UIImage *image = obj;
                    if (image.size.height > 1080 || image.size.width > 1080) {
                        
                        image = [image imageWithMaxSide:1080];
                        
                        [formData appendPartWithFileData:UIImageJPEGRepresentation(image, 0.3f) name:key fileName:[NSString stringWithFormat:@"%@.jpeg",key] mimeType:@"image/jpeg"];
                    } else if (image.size.height > 600 || image.size.width > 600)  {
                        [formData appendPartWithFileData:UIImageJPEGRepresentation(image, 0.5f) name:key fileName:[NSString stringWithFormat:@"%@.jpeg",key] mimeType:@"image/jpeg"];
                    } else {
                        [formData appendPartWithFileData:UIImageJPEGRepresentation(image, 1) name:key fileName:[NSString stringWithFormat:@"%@.jpeg",key] mimeType:@"image/jpeg"];
                    }
                }
                else if ([obj isKindOfClass:[NSArray class]]) {
                    NSArray *array = parameters[key];
                    for (int i = 0; i < array.count; i++) {
                        id subvalue = array[i];
                        if ([subvalue isKindOfClass:[UIImage class]]) {
                            UIImage *image = subvalue;
                            if (image.size.height > 1080 || image.size.width > 1080) {
                                
                                image = [image imageWithMaxSide:1080];
                                
                                [formData appendPartWithFileData:UIImageJPEGRepresentation(image, 0.3f) name:key fileName:[NSString stringWithFormat:@"%@.jpeg",key] mimeType:@"image/jpeg"];
                            } else if (image.size.height > 600 || image.size.width > 600)  {
                                [formData appendPartWithFileData:UIImageJPEGRepresentation(image, 0.5f) name:key fileName:[NSString stringWithFormat:@"%@.jpeg",key] mimeType:@"image/jpeg"];
                            } else {
                                [formData appendPartWithFileData:UIImageJPEGRepresentation(image, 1) name:key fileName:[NSString stringWithFormat:@"%@.jpeg",key] mimeType:@"image/jpeg"];
                            }
                        }
                        else if ([subvalue isKindOfClass:[NSString class]] &&
                                 ([subvalue hasSuffix:@".png"] ||
                                  [subvalue hasSuffix:@".jpg"] ||
                                  [subvalue hasSuffix:@".jpeg"])) {
                                     [formData appendPartWithFileURL:obj name:key fileName:[NSString stringWithFormat:@"%@.jpg",key] mimeType:@"image/jpeg" error:nil];
                                 }
                    }
                }
                else if ([obj isKindOfClass:[NSData class]]) {
                    [formData appendPartWithFileData:obj name:key fileName:[NSString stringWithFormat:@"%@.mp3",key] mimeType:@"audio/mp3"];
                }
            }];
        }
    }
    

    对于AF的二次封装很多,不过追本溯源就是NSURLSession,就是http协议.

    结尾

    这次独立带项目,真的学到了很多,明白了自己的很多不足.更让我明白理论真的很重要.大学的课程真的有他的必要性.

  • 相关阅读:
    VS2015 出现 .NETSystem.Runtime.Remoting.RemotingException: TCP 错误
    C#学习笔记------参数
    C#简单工厂和抽象类的实例
    css基础1
    html中的div span和frameset框架标签
    关于C#委托的一些学习笔记
    html基础加强2
    HTML基础加强
    利用GDI+在Winfrom绘制验证码
    winfrom如何在listview中添加控件
  • 原文地址:https://www.cnblogs.com/stevenfukua/p/5752147.html
Copyright © 2011-2022 走看看