【iOS】AFNetworking学习
【iOS】AFNetworking学习
- AFNetworking
- 概念
- 使用原因
- 文件构成
- 单例封装网络请求
- 认识GET和POST请求
- GET
- POST
- 总结
AFNetworking
概念
AFNetworking是一个基于NSURLSession/NSURLConnection封装的HTTP网络通信框架,可以帮我们更方便地发送请求、接收数据。常用功能有:
- 发起网络请求
- 处理返回数据,解析JSON数据
- 网络状态监听,根据网络状态调整请求策略
- 支持队列管理,可以同时发送多个请求
使用原因
当我们需要网络请求时,如果不用AFNetworking,代码量很大。以仿写天气预报时的网络请求为例:
//做URL编码
NSString *location = [cityName stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
//查城市ID
NSString *cityURLString = [NSString stringWithFormat:@"https://geoapi.qweather.com/v2/city/lookup?location=%@&key=3344bfece6c74eaa911a5f857c30df82", location];
//创建NSURL对象
NSURL *cityURL = [NSURL URLWithString:cityURLString];
//创建请求对象
NSURLRequest *cityRequest = [NSURLRequest requestWithURL:cityURL];
//创建NSURLSession会话
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
//协议方法在主线程中执行
//UI更新必须在主线程
NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:nil delegateQueue:[NSOperationQueue mainQueue]];
//根据会话创建请求任务
NSURLSessionDataTask *cityTask = [session dataTaskWithRequest:cityRequest completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {if (!error) {//解析JSON数据获取城市IDNSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];NSArray *locations = json[@"location"];if (locations.count > 0) {NSString *cityId = locations[0][@"id"];[self requestWeatherWithCityId:cityId cityName:cityName];}}
}];
[cityTask resume];
对比一下通过AFNetworking进行网络请求:
#import "ViewController.h"
#import "AFNetworking.h"@interface ViewController ()@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];NSString *cityName = @"西安";//URL编码NSString *location = [cityName stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];//API地址NSString *cityURLString = [NSString stringWithFormat:@"https://geoapi.qweather.com/v2/city/lookup?location=%@&key=3344bfece6c74eaa911a5f857c30df82",location];//创建AFHTTPSessionManagerAFHTTPSessionManager *manager = [AFHTTPSessionManager manager];manager.responseSerializer = [AFJSONResponseSerializer serializer];//GET请求[manager GET:cityURLString parameters:nil headers:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {NSLog(@"返回结果:%@", responseObject);NSDictionary *json = (NSDictionary*)responseObject;NSArray *locations = json[@"location"];if (locations.count > 0) {NSString *cityId = locations[0][@"id"];NSLog(@"城市ID:%@", cityId);}} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {NSLog(@"请求失败:%@", error.localizedDescription);}];
}@end
编译结果:
可以看的出,通过AFNetworking实现网络请求代码量会更少。
文件构成
AFNetworking由四部分构成:
- Manager:管理请求的核心类,封装了网络请求。
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
这里对比一下AFURLSessionManager和AFHTTPSessionManager:
在AFNetworking中,网络请求的manger主要由AFURLSessionManager和AFHTTPSessionManager构成,AFURLSessionManager是AFHTTPSessionManager的父类。
父类AFURLSessionManager负责处理一些基础的网络代码,并且接受NSURLRequest对象,而子类AFHTTPSessionManager负责处理和HTTP协议有关的逻辑。
其中,我们使用频率最高的是AFHTTPSessionManager,负责各种HTTP请求的发起和处理,是各种请求的直接执行者。
- Reachability:网络状态监控,监控网络变化(如WIFI、蜂窝、无网)来调整逻辑。
- Security:处理网络安全和HTTPS相关的。
- Serialization:请求和返回数据的序列化器,让AFNetworking自动完成参数封装和JSON数据解析。
manager.responseSerializer = [AFJSONResponseSerializer serializer];
单例封装网络请求
在之前天气预报的仿写中,我们有很多个界面,每个界面中都进行了网络请求,这样非常浪费内存,因此使用单例模式专门负责进行网络请求就会避免这个问题。同时,将网络请求逻辑封装到一个单例类中负责创建、管理和发送网络请求会使得代码集中,提供代码可维护性和可读性。
具体的步骤如下:
- 创建一个单例类
这里笔者使用了饿汉模式的GCD写法:
+ (instancetype)sharedManager {static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{shareManager = [[super allocWithZone:NULL] init];});return shareManager;
}-(instancetype)init {if (self = [super init]) {//管理请求self.sessionManager = [AFHTTPSessionManager manager];//设置请求序列化器,统一请求参数如何编码,方便服务器约定格式self.sessionManager.requestSerializer = [AFJSONRequestSerializer serializer];//请求超时时间,超过时间触发超时错误self.sessionManager.requestSerializer.timeoutInterval = 15.0;//设置响应序列化器//当服务器返回数据时,响应序列化器负责把它解析成可用Foudation对象。AFJSONResponseSerializer尝试把返回的数据解析成JSON,解析失败把错误抛给failure回调self.sessionManager.responseSerializer = [AFJSONResponseSerializer serializer];//响应序列化器可以接受的服务器端返回的Content-Type类型有哪些,如果不在集合中,序列化失败,AFNetworking会报错//Content-Type:HTTP响应头里的一个字段,用来告诉客户端,服务器返回的数据是什么类型//@"application/json":返回的是JSON格式//@"text/plain":返回的是纯文本//@"text/html":返回的是HTML网页内容//@"image/png":返回的是一张PNG图片self.sessionManager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/plain", @"text/html", nil];}return self;
}
- 通过AFNetworking中的封装GET方法进行网络请求
//GET封装
//通过封装,只用在NetworkingManager中配置好一次,以后全局通用,并且便于维护
- (void)GET:(NSString *)URLString parameters:(nullable id)parameters headers:(nullable NSDictionary<NSString *,NSString *> *)headers progress:(nullable void (^)(NSProgress * _Nonnull __strong))downloadProgress success:(nullable void (^)(NSURLSessionDataTask * _Nonnull __strong, id _Nullable __strong))success failure:(nullable void (^)(NSURLSessionDataTask * _Nullable __strong, NSError * _Nonnull __strong))failure {//AFNetworking的GET发送请求,把外部传进来的参数传给AFNetworking[self.sessionManager GET:URLString parameters:parameters headers:headers progress:downloadProgress success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {if (success) {success(task, responseObject);}} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {if (failure) {failure(task, error);}}];
}
我们具体实现一个实例:
NSString *cityName = @"西安";
NSString *location = [cityName stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
NSString *urlString = [NSString stringWithFormat: @"https://geoapi.qweather.com/v2/city/lookup?location=%@&key=3344bfece6c74eaa911a5f857c30df82", location];
[[NetworkingManager sharedManager] GET:urlString parameters:nil headers:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {NSLog(@"城市:%@", responseObject);NSArray *locations = responseObject[@"location"];NSString *cityId = locations[0][@"id"];NSString *weatherUrl = [NSString stringWithFormat: @"https://geoapi.qweather.com/v2/city/lookup?location=%@&key=3344bfece6c74eaa911a5f857c30df82", cityId];[[NetworkingManager sharedManager] GET:weatherUrl parameters:nil headers:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {NSLog(@"天气:%@", responseObject);} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {NSLog(@"天气查询失败:@%@", error);}];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {NSLog(@"城市查询失败:%@",error);
}];
这样我们就查询到了天气数据:
认识GET和POST请求
GET常用于获取资源,而POST常用于向服务器提供数据,即创建和修改资源。
GET
解释一下上述代码中的有关GET方法请求数据的几个参数:
- GET:NSString类型,存放URL,即网络请求地址。
- parameters:NSDictionary类型,存放用于传递请求的数据,在GET请求中这些参数会附加到URL字符串中,以便服务器根据参数返回对应数据。
- headers:NSDictionary类型,用于设置HTTP请求头的信息,每个请求头都是一个键值对。
- progress:通常是一个
void(^)(NSprogress* downloadProgress)
类型的块,它接受一个NSProgress对象(该对象包含已下载的数据量、总数据量等信息),实现更新进度条等操作,用于跟踪下载进度,不需要时传入nil。 - success:通常是一个
void(^)NSURLSessionDataTask* task, id responseObject)
类型的块,请求成功后返回该回调块。- task:包含响应数据的NSURLSessionDataTask对象
- responseObject:响应数据,通常是NSDictionary或其他数据结构
- failure:通常是
void(^)(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error)
类型的块,请求失败返回该回调块。- task:也是包含响应数据的NSURLSessionDataTask对象
- error:是一个NSError对象,包含请求失败的信息
这里将上面demo的每个参数都不为nil来实现每个参数的作用:
NSString *cityName = @"西安";
NSString *urlString = @"https://geoapi.qweather.com/v2/city/lookup";
NSDictionary *parameters = @{@"location":cityName, @"key": @"3344bfece6c74eaa911a5f857c30df82"};
NSDictionary *headers = @{@"Content-Type": @"application/json", @"Custom-Header": @"DemoRequest"};
[[NetworkingManager sharedManager] GET:urlString parameters:parameters headers:headers progress:^(NSProgress * _Nonnull downloadProgress) {NSLog(@"下载进度:%.2f", 100.0 * downloadProgress.completedUnitCount);
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {NSLog(@"请求成功");NSLog(@"请求的Task:%@", task);NSLog(@"响应数据:%@", responseObject);NSArray *locations = responseObject[@"location"];NSString *cityId = locations[0][@"id"];NSLog(@"%@", cityId);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {NSLog(@"请求失败");NSLog(@"请求的Task:%@", task);NSLog(@"错误信息:%@", error);
}];
从编译结果中我们就可以看出这几个参数的作用:
POST
对比一下GET,POST通常会多一个请求体。POST中,参数放在请求体中,服务器通过body解析参数,而GET的参数全部放在URL上,服务器通过URL解析参数。
这里我们针对上面天气预报的查询,需要提交用户反馈,那我们需要实现POST请求:
//请求url
NSString *urlString = @"https://httpbin.org/post";
//请求参数(请求体)
NSDictionary *parameters = @{@"userId":@"1001", @"city":@"西安", @"feedback":@"好用好用"};
//请求头字典,用来告诉服务器请求的额外信息
//"Content-Type": @"application/json":表示请求数据是JSON格式
//@"Custom-Header": @"DemoRequest":自定义的请求头字段,一般用来做标识,告诉服务端这是测试请求
NSDictionary *headers = @{@"Content-Type": @"application/json", @"Custom-Header": @"DemoRequest"};
//创建manager
[[NetworkingManager sharedManager] POST:urlString parameters:parameters headers:headers progress:^(NSProgress * _Nonnull uploadProgress) {NSLog(@"上传进度:%.2f%%", 100.0 * uploadProgress.completedUnitCount / uploadProgress.totalUnitCount);
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {NSLog(@"POST请求成功:%@", responseObject);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {NSLog(@"POST请求失败:%@", error.localizedDescription);
}];
实现结果:
这样我们就成功地用POST请求把JSON数据上传到服务器,并且服务器将确认信息返回给我们。
总结
笔者对AFNetworking仅仅只是进行了学习,具体后面在Spotify中应用会补充在博客中。