OC-AFNetworking
文章目录
- AFNetworking
- 简介
- 问题
- 🤔
- 优化策略
- 解决AFNetworking局限性
- 使用单例进行网络请求的优势
- 使用单例进行网络请求的风险
- 最优使用
- 使用
- 参数讲解
- POST请求
AFNetworking
简介
这篇文章旨在实现使用AFNetworking设置一个集中的单通道网络对象,该对象与MVC组建分离,可以在整个解耦架构应用程序中重复使用
资料来源:https://www.toptal.com/ios/afnetworking-tutorial-with-a-singleton-class?utm_source=chatgpt.com
虽然苹果抽象的许多iOS SDK在管理移动硬件的许多复杂性方面做的很好,但是在某些情况下为了保持SDK的灵活性,使用会很复杂。AFNetworking作为开源框架之一,简化了developer的日程任务。 他简化了RESTful API网络,并创建了具有成功、进度、和失败的模块化请求/响应模式。消除了对开发人员实施的委托方法和自定义请求/连接设置的需求,并且可以非常快速的包含在任何类中
问题
虽然AFN是一个功能强大、模块化的网络库,但是也会存在一些低效实施。如下
🤔
- 分散的网络请求逻辑
- 单个视图控制器包含多个几乎相同的请求,难以复用,后期维护成本高
- 如果API发生变更,需要逐个排查所有使用的地方,容易遗漏
- 共享状态混乱
- 一些通用参数可能分散在不同的控制器中,容易出现不一致或者同步问题
- 职责分离不清
- 视图控制器承担了数据获取的职责,导致控制器代码臃肿
- 拓展性不足
- 如果API发生版本化或者新增参数,修改成本大
优化策略
- 集中化网络层设计
- 创建独立的NetworkManager或APIClient集中管理所有请求
- 将通用的请求头、参数注入逻辑同意处理
- 抽象请求方法
- 引入模型层
- 使用YY_Model等库,将API响应解析为强类型模型对象
- 控制器只关心模型,不关心JSON结构
- 支持版本或者参数设置
- 在APIClient内部设计时预留一些字段
解决AFNetworking局限性
我们可以通过创建一个网络单例类来集中处理请求、响应以及其参数
苹果官方的原话:“A singleton object provides a global point of access to the resources of its class. Singletons are used in situations where this single point of control is desirable, such as with classes that offer some general service or resource. You obtain the global instance from a singleton class through a factory method. – Apple“
就是单例模式的介绍,想了解的可以看笔者前面有关单例模式的文章
使用单例进行网络请求的优势
- 全局唯一,是静态初始化。保证了所有类访问的都是同一个实例,避免了因为不同实例不同步导致的奇怪bug
- 同义API的调用和速率限制。单例可以集中管理请求队列和节流逻辑,防止因多个视图控制器并发请求导致速率限制
- 集中化配置管理
- 重复利用公共属性。通用的请求头、超时设置、解析策略都能复用。
- 延迟加载、节省内存。单例在第一次使用前不加载,不占用内存
- 解耦视图与网络层,即使视图控制器销毁,网络请求仍能继续,避免了因为界面消失导致网络请求中断的问题
- 统一日志与错误处理
- 跨项目复用
使用单例进行网络请求的风险
- 可能承担过多职责,我们在使用单例时,每个单例应该只做一件事
- 无法继承,单例不能被子类化,如果未来需要拓展,不够灵活。可以通过使用协议和依赖注入提供可替换的实现
- 共享状态可能被意外修改
- 长时间持有大量数据,不释放。可以定期清理缓存或者重置单例内部状态
最优使用
- 单一职责原则
- 线程安全,使用GCD或加锁
- 轻量化存储,单例只存放必要的全局设置
- 支持依赖注入
- 提供重置能力,在必要时可以手动清空状态
使用
我们实现一个简单的GET请求来演示AFNetworking的效果
首先,我们先为项目导入需要的AFNetworking第三方库,这里的操作就略过了,我们直接进入正文
- 创建单例类, 并实现GET方法
#import <Foundation/Foundation.h>
#import "AFNetworking.h"
NS_ASSUME_NONNULL_BEGIN@interface NetworkManager : NSObject
@property (nonatomic, strong)AFHTTPSessionManager* sessionManager;+ (instancetype)sharedManager;- (void)GET:(NSString* ) URLString parameters:(nullable id)parameters headers:(nullable NSDictionary<NSString *,NSString *> *)headers progress:(nullable void (^)(NSProgress * _Nonnull))downloadProgress success:(nullable void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success failure:(nullable void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure;@endNS_ASSUME_NONNULL_END
// Created by xiaoli pop on 2025/9/15.
//#import "NetworkManager.h"
static NetworkManager* sharedManager = nil;
@implementation NetworkManager
+ (instancetype)sharedManager {static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{sharedManager = [[super allocWithZone:NULL] init];});return sharedManager;
}+ (instancetype)allocWithZone:(struct _NSZone *)zone {static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{sharedManager = [super allocWithZone:zone];});return sharedManager;
}- (instancetype)init {if (self = [super init]) {self.sessionManager = [AFHTTPSessionManager manager];self.sessionManager.requestSerializer = [AFJSONRequestSerializer serializer];self.sessionManager.requestSerializer.timeoutInterval = 15.0;self.sessionManager.responseSerializer = [AFJSONResponseSerializer serializer];self.sessionManager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/plain", @"text/html", nil];}return self;
}- (void)GET:(NSString *)URLString parameters:(id)parameters headers:(NSDictionary<NSString *,NSString *> *)headers progress:(void (^)(NSProgress * _Nonnull))downloadProgress success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure {[self.sessionManager GET:URLString parameters:parameters headers:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {if (success) {success(task, responseObject);}} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {if (failure) {failure(task, error);}}];
}@end
- 使用
NSString* str = @"安康";NSString* urlString = [NSString stringWithFormat:@"https://api.weatherapi.com/v1/forecast.json?key=8f123b0cdc654b149aa92217252607&q=%@&days=3&lang=zh",str];urlString = [urlString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];[[NetworkManager sharedManager] GET:urlString parameters:nil headers:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {NSLog(@"%@", responseObject);} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {NSLog(@"error");}];
我们对比一下原生的网络请求
- (void)creatURL {NSLog(@"!!!");NSString* str = @"安康";NSString* urlString = [NSString stringWithFormat:@"https://api.weatherapi.com/v1/forecast.json?key=8f123b0cdc654b149aa92217252607&q=%@&days=3&lang=zh",str];urlString = [urlString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];NSURL* url = [NSURL URLWithString:urlString];NSURLRequest* request = [NSURLRequest requestWithURL:url];NSURLSession* session = [NSURLSession sharedSession];NSURLSessionTask* task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {if (!error && data) {NSError* jsonError;NSDictionary* jsonDict = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError];if (!jsonError && [jsonDict isKindOfClass:[NSDictionary class]]) {// NSLog(@"%@", jsonDict);Model* model = [Model yy_modelWithDictionary:jsonDict];NSLog(@"%@", model.current[@"cloud"]);NSLog(@"%@", model);}} else {NSLog(@"ERROR: %@", error);}}];[task resume];
}
显而易见,当我们使用一个单例类来处理网络申请时,代码逻辑更清晰,后期维护更方便
参数讲解
- URL
- NSURL或者其字符串表示形式
- 描述:网络资源的地址,制定了请求的目标位置
- parameters
- 类型:NSDictionary
- 描述:字典,用于传递请求的参数,在GET请求中这些参数会附加到URL字符串中,以便于服务器根据这些参数返回对应数据
- progress
- 类型:通常是一个void(^)(NSprogress* downloadProgress)类型的块
- 这个参数用于跟踪下载进度,他接收一个NSProgress对象,该对象包含了已下载的数据量、总数据量等信息。。可以在此实现更新进度条等操作,不需要可以传nil
- headers
- NSDictionary
- 用于设置HTTP请求头部信息。
- 每个请求头都是一对键值对,
- success
- 类型:void(^)NSURLSessionDataTask* task, id responseObject)类型的块
- 请求成功后的回调块,通常接受两个参数,一个是包含响应数据的NSURLSessionDataTask对象,一个是响应数据,通常是一个NSDictionary或其他数据结构
- failure
- 失败后的回调块,第一个参数也是包含请求任务的NSURLSessionDataTask对象,第二个参数是一个NSError对象,包含了请求失败的信息
POST请求
在单例中:
- (void)POST:(NSString *)URLString parameters:(id)parameters headers:(NSDictionary<NSString *,NSString *> *)headers progress:(void (^)(NSProgress * _Nonnull))uploadProgress success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure {[self.sessionManager POST:URLString parameters:parameters headers:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {if (success) {success(task, responseObject);}} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {if (failure) {failure(task, error);}}];
}
使用:
- (void)LoginWithUsrname:(NSString* )username withPassword:(NSString* )password {NSString* urlString = @"https://......./login";NSDictionary* parameters = @{@"username" : username , @"password" : password};[[NetworkManager sharedManager] POST:urlString parameters:parameters headers:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {NSLog(@"%@", responseObject);} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {NSLog(@"%@", error);}];
}
APIURL是接口的基本路径,而parameters是传给接口的查询参数或请求体,AFNetworking会自动把parameters序列化并拼接到URL或者body,前提是必须得有一个基础URL来承接这些参数