文章目錄
- 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來承接這些參數