前言
至于為什么要用AFNetworking 做APP網絡請求,我這里不copy 別人的優點了,至于為什么要封裝,用第三方的風險都知道,萬一哪天AFNetworking 的作者不跟新了,需要使用另外一款第三方網絡請求,更換也更好操作一點。
所以從綜合性能,開發便利 以及后期維護風險的角度考慮,對網絡請求層 進行 封裝是一個APP開發工作者應有的考量。
個人對AFNetworking 的偏愛,相信大多數iOS開發者 都是跟我一樣的,所以這里就基于產品開發業務需求的角度,做了一個封裝。
產品需求
- 提供基本的請求傳入參數接口,比如請求的ViewController,請求的地址等等
- 提供基本的請求回掉,使用 block 回掉,必須的有錯誤回掉,請求成功的回掉,還可以根據自己實際需求 回掉 statusCode等等內容
- 判斷當前網絡狀態,如果無網絡狀態,直接返回錯誤回掉。
- 這些方法返回值 最好為 NSURLSessionDataTask,方便對這個網絡請求做相關的取消操作。
- 對于 GET 類請求,如果請求成功,增加緩存策略
基本實現
創建一個MyBaseNetWorking文件,繼承NSObject即可,
引入頭文件 AFNetworkActivityIndicatorManager.h 和AFNetworking.h
step 1 準備工作 網絡狀態的判斷
我建議大家 在AppDelegate 中直接加入這個判斷,并創建一個單例Model->MyAppConfigModel,記錄APP的網絡狀態,這個會非常有用。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
/*********** AFNetworkActivityIndicatorManager 所有通過AF發送的請求, 都會在電池條上出現圓圈提示 ***********/
[AFNetworkActivityIndicatorManager sharedManager].enabled = YES;
/*********** 監聽當前網絡狀態 *************/
[[AFNetworkReachabilityManager sharedManager] setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
NSLog(@"當前網絡狀態: %@", AFStringFromNetworkReachabilityStatus(status));
}];
// 開始監測網絡狀態
[[AFNetworkReachabilityManager sharedManager] startMonitoring];
[self configReachabilityStatusByAFNetwork];
}
/**
監測網絡類型
*/
-(void)configReachabilityStatusByAFNetwork {
AFNetworkReachabilityManager *manager = [AFNetworkReachabilityManager sharedManager];
[manager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
// 用單例model 記錄網絡狀態,這樣可以在APP任何地方調用這個熟悉 都可以得到當前網絡狀態。
[MyAppConfigModel sharedSingleton].netWorkReachabilityStatus = status;
switch (status) {
case AFNetworkReachabilityStatusUnknown:
NSLog(@"當前網絡:未知");
break;
case AFNetworkReachabilityStatusNotReachable:
NSLog(@"當前網絡:網絡無連接\n請檢查網絡");
break;
case AFNetworkReachabilityStatusReachableViaWWAN:
NSLog(@"當前網絡:3G|4G");
break;
case AFNetworkReachabilityStatusReachableViaWiFi:
NSLog(@"當前網絡:WiFi");
break;
default:
break;
}
}];
}
step 2 緩存策略
我這里就使用非常簡單的NSKeyedArchiver
寫操作:
if (cache == YES) {
NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObjectInArr];
NSString *cachePath = [docPath stringByAppendingPathComponent:task.currentRequest.URL.absoluteString.MD5];
[[NSOperationQueue new] addOperationWithBlock:^{
[NSKeyedArchiver archiveRootObject:responseObject toFile:cachePath];
}];
}
讀操作:
//如果需要緩存 讀取緩存
if (cache == YES) {
NSString *cachePath = [docPath stringByAppendingPathComponent:task.currentRequest.URL.absoluteString.MD5];
id responseObj = [NSKeyedUnarchiver unarchiveObjectWithFile:cachePath];
NSLog(@" 讀取緩存 get 失敗 url = %@ \n statusCode =%ld",url,(long)responses.statusCode);
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
if (responseObj) {
!completionHandler ?: completionHandler(responseObj, nil,(long)responses.statusCode);
}else{
!completionHandler ?: completionHandler(nil, error, (long)responses.statusCode);
}
}];
}
step 3 設置AFHTTPSessionManager對象,配置統一屬性
MyBaseNetWorking.m文件中
+ (AFHTTPSessionManager *)manager {
[AFNetworkActivityIndicatorManager sharedManager].enabled = YES;
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
//默認解析模式
manager.requestSerializer = [AFHTTPRequestSerializer serializer];
manager.responseSerializer = [AFJSONResponseSerializer serializer];
//配置請求序列化
AFJSONResponseSerializer *serializer = [AFJSONResponseSerializer serializer];
[serializer setRemovesKeysWithNullValues:YES];
manager.requestSerializer.stringEncoding = NSUTF8StringEncoding;
//配置響應序列化
manager.responseSerializer.acceptableContentTypes = [NSSet setWithArray:@[@"application/json",
@"text/html",
@"text/json",
@"text/plain",
@"text/javascript",
@"text/xml",
@"image/*",
@"application/octet-stream",
@"application/zip"]];
//請求時間
manager.requestSerializer.timeoutInterval = 15.f;
//設置請求頭 舉例
NSString *userID = [NSString stringWithFormat:@"%@",[kUserDefaults valueForKey:@"UserID"]];
[manager.requestSerializer setValue:[kUserDefaults valueForKey:@"Authorization"] forHTTPHeaderField:@"Authorization"];
[manager.requestSerializer setValue:userID forHTTPHeaderField:@"UserID"];
return manager;
}
step 4 設置AFHTTPSessionManager對象,配置統一屬性
MyBaseNetWorking.h 文件中 設計自己的 API 接口
封裝 GET 請求
MyBaseNetWorking.h文件中
/**
get 網絡請求
@param path 路徑 不要Base路徑
@param controller 網絡請求所在的控制器
@param cache 是否需要緩存
@param parameters 參數
@param completionHandler 處理事件
@return 返回NSURLSessionDataTask對象
*/
+ (id)setGET:(NSString *)path controller:(UIViewController *)controller cache:(BOOL)cache parameters:(NSDictionary *)parameters completionHandler:(void(^)(id responseObj, NSError *error,NSInteger statusCode))completionHandler;
MyBaseNetWorking.m文件中實現
/******************** 實現方法 **********************/
+(id)setGET:(NSString *)path controller:(UIViewController *)controller cache:(BOOL)cache parameters:(NSDictionary *)parameters completionHandler:(void (^)(id, NSError *, NSInteger))completionHandler{
kDefineWeakSelf;
AFHTTPSessionManager *manager = [self manager];
NSString *url = [NSString stringWithFormat:@"%@%@",BaseHttpsURL,path];
NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObjectInArr];
NSString *encodedPath = [path stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURLSessionDataTask *task = nil;
// 無網絡狀態
if ([MyAppConfigModel sharedSingleton].netWorkReachabilityStatus == AFNetworkReachabilityStatusNotReachable) {
NSError *err = [NSError errorWithDomain:@"ErrorDomain" code:-999 userInfo:[NSDictionary dictionaryWithObject:@"網絡出現錯誤,請檢查網絡連接" forKey:NSLocalizedDescriptionKey]];
!completionHandler ?: completionHandler(nil, err, -999);
return task;
}
task= [manager GET:encodedPath parameters:parameters progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSHTTPURLResponse * responses = (NSHTTPURLResponse *)task.response;
NSLog(@" get 請求成功 statusCode = %ld \n url=%@ \n responseObject = %@ ",(long)responses.statusCode,url,responseObject);
//如果需要緩存
if (cache == YES) {
NSString *cachePath = [docPath stringByAppendingPathComponent:task.currentRequest.URL.absoluteString.MD5];
[[NSOperationQueue new] addOperationWithBlock:^{
[NSKeyedArchiver archiveRootObject:responseObject toFile:cachePath];
}];
}
!completionHandler?:completionHandler(responseObject, nil,(long)responses.statusCode);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[[NSOperationQueue new] addOperationWithBlock:^{
NSHTTPURLResponse * responses = (NSHTTPURLResponse *)task.response;
//如果需要緩存 讀取緩存
if (cache == YES) {
NSString *cachePath = [docPath stringByAppendingPathComponent:task.currentRequest.URL.absoluteString.MD5];
id responseObj = [NSKeyedUnarchiver unarchiveObjectWithFile:cachePath];
NSLog(@" 讀取緩存 get 失敗 url = %@ \n statusCode =%ld",url,(long)responses.statusCode);
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
if (responseObj) {
NSLog(@" 有緩存 ");
!completionHandler ?: completionHandler(responseObj, nil,(long)responses.statusCode);
}else{
NSLog(@" 緩存 nil");
!completionHandler ?: completionHandler(nil, error, (long)responses.statusCode);
}
}];
}else{
NSLog(@" 無緩存 get 失敗 url = %@ \n statusCode =%ld",url,(long)responses.statusCode);
!completionHandler ?: completionHandler(nil, error, (long)responses.statusCode);
}
}];
}];
return task;
}
封裝 POST 請求
MyBaseNetWorking.h文件中
/**
post 網絡請求
@param path 路徑 不要Base路徑
@param controller 網絡請求所在的控制器
@param parameters 參數
@param completionHandler 處理事件
@return 返回NSURLSessionDataTask對象
*/
+ (id)setPOST:(NSString *)path controller:(UIViewController *)controller parameters:(NSDictionary *)parameters completionHandler:(void(^)(id responseObj, NSError *error,NSInteger statusCode))completionHandler;
MyBaseNetWorking.m文件中實現
+(id)setPOST:(NSString *)path controller:(UIViewController *)controller parameters:(NSDictionary *)parameters completionHandler:(void (^)(id, NSError *, NSInteger))completionHandler{
kDefineWeakSelf;
AFHTTPSessionManager *manager = [self manager];
NSString *url = [NSString stringWithFormat:@"%@%@",BaseHttpsURL,path];
NSURLSessionDataTask *task = nil;
if ([MyAppConfigModel sharedSingleton].netWorkReachabilityStatus == AFNetworkReachabilityStatusNotReachable) {
NSError *err = [NSError errorWithDomain:@"ErrorDomain" code:-999 userInfo:[NSDictionary dictionaryWithObject:@"網絡出現錯誤,請檢查網絡連接" forKey:NSLocalizedDescriptionKey]];
!completionHandler ?: completionHandler(nil, err, -999);
return task;
}
task = [manager POST:url parameters:parameters progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSHTTPURLResponse * responses = (NSHTTPURLResponse *)task.response;
NSLog(@" Post url =%@ statusCode =%ld \n成功 \n responseObject = %@ ",url,(long)responses.statusCode,responseObject);
!completionHandler?:completionHandler(responseObject, nil,(long)responses.statusCode);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[[NSOperationQueue new] addOperationWithBlock:^{
NSHTTPURLResponse * responses = (NSHTTPURLResponse *)task.response;
!completionHandler ?: completionHandler(nil, error,responses.statusCode);
}
}];
}else{
NSLog(@" POST 失敗 url = %@ statusCode= %ld \n error = %@ ",url,(long)responses.statusCode,error);
!completionHandler ?: completionHandler(nil, error, (long)responses.statusCode);
}];
}];
return task;
}
封裝 取消 請求
MyBaseNetWorking.h文件
/**
取消指定網絡請求任務。
@param task 指定的網絡請求任務
*/
+ (void)cancleNSURLSessionTask:(NSURLSessionTask *)task;
MyBaseNetWorking.m文件中實現
+(void)cancleNSURLSessionTask:(NSURLSessionTask *)task{
[task cancel];
}
至此 AFNetworking 基本封裝完畢
如何使用?
在控制器可以創建一個對象NSURLSessionDataTask *netWorkingTask
-(void)netWorking{
[self.view showNetWorkingLoadingWithInfo:@"加載中..."];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
self.netWorkingTask = [MyNormalNetWorking getHistroyCarNoListController:self completionHandler:^(id responseObj, NSError *error, NSInteger statusCode) {
dispatch_async(dispatch_get_main_queue(), ^{
if (error) {
[self.view showErrorWithInfo:@"請求失敗"];
NSLog(@"error = %@",error);
}else{
[self.view showDarkTextColorToastInfo:@"請求成功" userInteractionEnabled:NO];
self.textView.text = [NSString stringWithFormat:@"%@",responseObj];
}
});
}];
});
}
// 以下方法是 繼承 了MyBaseNetWorking創建的一個統一管理請求的基礎類MyNormalNetWorking
+ (id)getHistroyCarNoListController:(UIViewController *)controller completionHandler:(void (^)(id , NSError *, NSInteger))completionHandler{
NSString *path = @"http://192.168.20.70:8080/parkingService/userCarNo/getHistoryCarNo.action?userId=utopa&limit=6";
return [self setGET:path controller:controller cache:YES parameters:nil completionHandler:^(id responseObj, NSError *error, NSInteger statusCode) {
!completionHandler ?: completionHandler(responseObj ,error, statusCode);
}];
}
// 取消網絡請求
[MyNormalNetWorking cancleNSURLSessionTask:self.netWorkingTask];