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:@"--%@\nContent-Disposition: form-data; name=\"file1\"; filename=\"%@\"\nContent-Type: %@\n\n",kBOUNDARY_STRING,fileName,@"image/jpg"];
NSString *strBottom=[NSString stringWithFormat:@"\n--%@--",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协议.

结尾

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

AFNetworking二次封装的那些事的更多相关文章

  1. iOS项目相关@AFN&SDWeb的二次封装

    一,AFNetworking跟SDWebImge是功能强大且常用的第三方,然而在实际应用中需要封装用来复用今天就跟大家分享一下AFN&SDWeb的二次封装 1. HttpClient.h及.m ...

  2. Quick Cocos (2.2.5plus)CoinFlip解析(MenuScene display AdBar二次封装)

    转载自:http://cn.cocos2d-x.org/tutorial/show?id=1621 从Samples中找到CoinFlip文件夹,复制其中的 res 和 script 文件夹覆盖新建工 ...

  3. volley二次封装

    产品中使用Volley框架已有多时,本身已有良好封装的Volley确实给程序开发带来了很多便利与快捷.但随着产品功能的不断增加,服务器接口的不断复杂化,直接使用Volley原生的JSONObjectR ...

  4. iOS菜鸟之AFN的二次封装

    我用一个单例类将一些常用的网络请求进行了二次封装,主要包括post请求 get请求  图片文件上传下载  视频的断点续传等功能. 首先大家先去github上下载AFN,将文件夹内的AFNetworki ...

  5. 借鉴Glide思想二次封装Fresco

    本篇文章已授权微信公众号 dasu_Android(大苏)独家发布 最近封装了个 Fresco 的组件库:DFresco,就顺便来讲讲. 背景 Fresco 图片库很强大,我们项目中就是使用的 Fre ...

  6. Glide二次封装库的使用

    更多代码可以查询本人GitHub:欢迎阅读,star点起来. Glide二次封装库源码 前言 为什么选择Glide? Glide 轻量级 速度快 可以根据所需加载图片的大小自动适配所需分辨率的图 支持 ...

  7. DRF框架(五)——context传参,二次封装Response类,两个视图基类(APIView/GenericAPIView),视图扩展类(mixins),子类视图(工具视图),视图集(viewsets),工具视图集

    复习 1.整体修改与局部修改 # 序列化get (给前端传递参数) #查询 ser_obj = ModelSerializer(model_obj) #只传递一个参数,默认是instance的参数,查 ...

  8. 二次封装 Reponse,视图家族

    复习 """ 1.整体修改与局部修改 # 序列化 ser_obj = ModelSerializer(model_obj) # 反序列化,save() => cre ...

  9. drf二次封装response-APIViews视图家族-视图工具集-工具视图-路由组件

    视图类传递参数给序列化类 (1).在视图类中实例化 序列化对象时,可以设置context内容. (2).在序列化类中的局部钩子.全局钩子.create.update方法中,都可以用self.conte ...

随机推荐

  1. UWP开发入门(二十三)——WebView

    本篇讨论在UWP开发中使用WebView控件时常见的问题,以及一些小技巧. WebView是实际开发中常用的控件,很多大家抱怨的套网页的应用都是通过WebView来实现的.这里要澄清一个问题,套网页的 ...

  2. 浏览器退出之后php还会继续执行么?

    浏览器退出之后php还会继续执行么? 前提:这里说的是典型的lnmp结构,nginx+php-fpm的模式 如果我有个php程序执行地非常慢,甚至于在代码中sleep(),然后浏览器连接上服务的时候, ...

  3. JavaScript最佳实践

    作者:Grey 原文地址: http://www.cnblogs.com/greyzeng/p/5540469.html 举个例子:用户在点击某个链接的时候弹出一个新窗口 弹出窗口的方法采用:wind ...

  4. C#中协变与抗变(逆变)

    泛型在.NET 2.0中正式的引入.在使用泛型的过程中,联系上面向对象的继承性.往往很容易想当然敲出类似以下代码 List<Animal> animalLst=new List<Do ...

  5. Wojilu学习笔记 (01)

    “我记录”开发框架(wojilu framework) 是 .net 平台下的综合开发框架,主要用于 web 方面的快速开发. 官方网址:http://www.wojilu.com源码托管网址: ht ...

  6. C# ACCESS数据库操作类

    这个是针对ACCESS数据库操作的类,同样也是从SQLHELPER提取而来,分页程序的调用可以参考MSSQL那个类的调用,差不多的,只是提取所有记录的数量的时候有多一个参数,这个需要注意一下! usi ...

  7. linq 实现查询字符串拼接 : And 和 OR 两种方式

    N年前我们是这样来 拼接查询字符串的: // 何问起 hovertree.com public string Test(string a, string b, string c,string d) { ...

  8. linux 新建文件的命令

    图形界面下就不用说了,终端下键入: touch test.java 就创建了一个新文件 test.java http://hovertree.com/menu/linux/ http://www.cn ...

  9. Note: This element neither has attached source nor attached Javadoc and hence no Javadoc could be found.

    在Eclipse中开发Android项目时,鼠标停放在想要查看帮助文档的类上面,发现没有显示帮助文档,显示了下面一句话: Note: This element neither has attached ...

  10. php取默认值以及类的继承

    (1)对于php的默认值的使用和C++有点类似,都是在函数的输入中填写默认值,以下是php方法中对于默认值的应用: <?phpfunction makecoffee($types = array ...