從自己剛開始做項(xiàng)目時(shí),一直都是用AFNetworking進(jìn)行網(wǎng)絡(luò)請(qǐng)求。每次涉及網(wǎng)絡(luò)請(qǐng)求的時(shí)候都手寫一次,寫的整個(gè)人都不好不好了。特別AFNetworking升級(jí)3.0的時(shí)候真想剁了自己的手。從那以后只要是第三方的我基本都是自己做一層封裝。廢話不多說下面來聊聊下載類的封裝。
所謂下載管理類:無非就是,在對(duì)AFNetworking封裝便用的時(shí)候,在進(jìn)行一次對(duì)下載工具的封裝。這樣的好處有以下幾點(diǎn):
- 同一個(gè)網(wǎng)址的數(shù)據(jù)請(qǐng)求只會(huì)進(jìn)行一次。 例如用戶對(duì)TableView的瘋狂下拉刷新 數(shù)據(jù)源處理不好可能會(huì)崩潰。
- 控制器消失的時(shí)候取消當(dāng)前數(shù)據(jù)請(qǐng)求。 AFNetworking進(jìn)行數(shù)據(jù)請(qǐng)求,如果不手動(dòng)取消請(qǐng)求的話在控制器銷毀的時(shí)候此次任務(wù)還是存在的。
當(dāng)請(qǐng)求完成的時(shí)候走回調(diào)Block這就有崩潰的危險(xiǎn)存在。當(dāng)初這里理解了,當(dāng)AF請(qǐng)求沒有完成就pop的話,由于AF引用當(dāng)前界面,所以當(dāng)前界面不會(huì)釋放。直至完成才會(huì)釋放故不存在crash一說。返回只是節(jié)省用戶流量罷了。
QQsession.h中的方法
@interface QQsession : NSObject
@property (copy, nonatomic) NSString *urlStr;
- (NSURLSessionDataTask *)conenctWithstr:(NSString *)urlStr dic:(NSDictionary *)dic fromController:(UIViewController *)controller success:(void(^)(id resposeObject))success andfalse:(void(^)(NSError *error))falseBlock;
- (NSURLSessionDataTask *)postWithstr:(NSString *)urlStr dic:(NSDictionary *)dic fromController:(UIViewController *)controller success:(void(^)(id resposeObject))success andfalse:(void(^)(NSError *error))falseBlock;
//
- (void)cancelWithOperation:(NSURLSessionDataTask *)operation;
@end
QQsession.m中的方法
@interface QQsession ()
@end
@implementation QQsession
- (NSURLSessionDataTask *)conenctWithstr:(NSString *)urlStr dic:(NSDictionary *)dic fromController:(UIViewController *)controller success:(void(^)(id resposeObject))success andfalse:(void(^)(NSError *error))falseBlock
{
//沒網(wǎng)絡(luò)的情況時(shí)直接返回
if (![self requestBeforeJudgeConnect]) {
[[NSNotificationCenter defaultCenter] postNotificationName:@"QQtableview" object:nil];
[[QQNetManager defaultManager] showProgressHUDWithType:0];
return nil;
}
NSMutableDictionary *tokenDic = [NSMutableDictionary dictionaryWithDictionary:dic];
[tokenDic setValue:[[NSUserDefaults standardUserDefaults] objectForKey:@"token"] forKey:@"token"];
[[QQNetManager defaultManager] insertQQConnection:self];
[controller.view Loading_0314];
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.requestSerializer.timeoutInterval = 30.f;
manager.responseSerializer = [AFJSONResponseSerializer serializer];
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", @"text/html", nil];
NSString *encoded = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURLSessionDataTask * operation = [manager GET:encoded parameters:tokenDic progress:^(NSProgress * _Nonnull downloadProgress) {
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[self handleResponseObject:responseObject Controller:controller Success:success];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[self handleResponseObject:error Controller:controller failure:falseBlock];
}];
[[QQNetManager defaultManager] insertConnectionVC:controller QQConnection:self SessionDataTask:operation];
return operation;
}
- (NSURLSessionDataTask *)postWithstr:(NSString *)urlStr dic:(NSDictionary *)dic fromController:(UIViewController *)controller success:(void(^)(id resposeObject))success andfalse:(void(^)(NSError *error))falseBlock
{
//沒網(wǎng)絡(luò)的情況時(shí)直接返回
if (![self requestBeforeJudgeConnect]) {
[[NSNotificationCenter defaultCenter] postNotificationName:@"QQtableview" object:nil];
[[QQNetManager defaultManager] showProgressHUDWithType:0];
return nil;
}
NSMutableDictionary *tokenDic = [NSMutableDictionary dictionaryWithDictionary:dic];
[tokenDic setValue:[[NSUserDefaults standardUserDefaults] objectForKey:@"token"] forKey:@"token"];
[[QQNetManager defaultManager] insertQQConnection:self];
[controller.view Loading_0314];
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.requestSerializer.timeoutInterval = 30.f;
manager.responseSerializer = [AFJSONResponseSerializer serializer];
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", @"text/html", nil];
NSString *encoded = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURLSessionDataTask * operation = [manager POST:encoded parameters:tokenDic progress:^(NSProgress * _Nonnull uploadProgress) {
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[self handleResponseObject:responseObject Controller:controller Success:success];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[self handleResponseObject:error Controller:controller failure:falseBlock];
}];
[[QQNetManager defaultManager] insertConnectionVC:controller QQConnection:self SessionDataTask:operation];
return operation;
}
#pragma mark - 統(tǒng)一處理下載返回的數(shù)據(jù)
- (void)handleResponseObject:(id)responseObject Controller:(UIViewController *)controller Success:(void(^)( id _Nullable responseObject))successBlock;
{
[[NSNotificationCenter defaultCenter] postNotificationName:@"QQtableview" object:nil];
}
successBlock(responseObject);
[UIApplication sharedApplication].networkActivityIndicatorVisible =NO;
[[QQNetManager defaultManager] deleteQQConnection:self];
}
- (void)handleResponseObject:(NSError *)error Controller:(UIViewController *)controller failure:(void(^)(NSError *error))failureBlock
{
failureBlock(error);
[controller.view Hidden];
#warning 主動(dòng)退出怎么才能不顯示失敗的提示 -999就是取消此次下載
if (error.code != -999) {
//非主動(dòng)取消鏈接
[[QQNetManager defaultManager] showProgressHUDWithType:0];
}
[UIApplication sharedApplication].networkActivityIndicatorVisible =NO;
[[QQNetManager defaultManager] deleteQQConnection:self];
}
#pragma mark - 取消下載
- (void)cancelWithOperation:(NSURLSessionDataTask *)operation
{
[operation cancel];
[[QQNetManager defaultManager] deleteQQConnection:self];
}
#pragma mark 網(wǎng)絡(luò)判斷
-(BOOL)requestBeforeJudgeConnect
{
struct sockaddr zeroAddress;
bzero(&zeroAddress, sizeof(zeroAddress));
zeroAddress.sa_len = sizeof(zeroAddress);
zeroAddress.sa_family = AF_INET;
SCNetworkReachabilityRef defaultRouteReachability =
SCNetworkReachabilityCreateWithAddress(NULL, (struct sockaddr *)&zeroAddress);
SCNetworkReachabilityFlags flags;
BOOL didRetrieveFlags =
SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags);
CFRelease(defaultRouteReachability);
if (!didRetrieveFlags) {
QMLog(@"Error. Count not recover network reachability flags\n");
return NO;
}
BOOL isReachable = flags & kSCNetworkFlagsReachable;
BOOL needsConnection = flags & kSCNetworkFlagsConnectionRequired;
BOOL isNetworkEnable =(isReachable && !needsConnection) ? YES : NO;
dispatch_async(dispatch_get_main_queue(), ^{
[UIApplication sharedApplication].networkActivityIndicatorVisible =isNetworkEnable;/* 網(wǎng)絡(luò)指示器的狀態(tài): 有網(wǎng)絡(luò) : 開 沒有網(wǎng)絡(luò): 關(guān) */
});
return isNetworkEnable;
}
QQNetManager.h中的方法
@interface QQNetManager : NSObject
{
NSMutableDictionary *_dataDic;///<紀(jì)錄下載的url
NSMutableArray *_VCS; ///<紀(jì)錄當(dāng)前控制器有哪些下載
UIAlertView * _alert;///<防治alert重復(fù)出現(xiàn)
}
+ (id)defaultManager;
- (NSURLSessionDataTask *)startGetHttpWithStr:(NSString *)url Dictionary:(NSDictionary *)parameters fromController:(UIViewController *)controller Success:(void(^)( id responseObject))Success andFalse:(void(^)(NSError *error))falseBlock;
- (NSURLSessionDataTask *)startPostHttpWithStr:(NSString *)url Dictionary:(NSDictionary *)parameters fromController:(UIViewController *)controller Success:(void(^)( id responseObject))Success andFalse:(void(^)(NSError *error))falseBlock;
- (void)insertQQConnection:(QQsession *)hc;///<插入對(duì)象
- (void)deleteQQConnection:(QQsession *)hc;//<刪除對(duì)象
//- (void)stopAllConnection;
- (void)showProgressHUDWithType:(NSInteger)type;//<顯示提示
//控制器用
- (void)insertConnectionVC:(UIViewController *)VC QQConnection:(QQsession *)hc SessionDataTask:(NSURLSessionDataTask *)task;//<插入但前控制器的
- (void)deleteConnectionVC:(UIViewController *)vc;//<銷毀控制器詩取消下載```
`QQNetManager.m`中的方法
@implementation QQNetManager
{
NSURLSessionDataTask * operation;
NSLock *lock;
}
- (id)init
{
if (self = [super init]) {
_dataDic = [[NSMutableDictionary alloc]init];
_VCS = [NSMutableArray array];
}
return self;
}
- (id)defaultManager{
static QQNetManager manager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [[self alloc]init];
[manager startMonitorConnection];
});
return manager;
}
/*
- 帶控制器的get請(qǐng)求
- @param url url
- @param parameters parameter
- @param controller contortion
- @param Success
- @param falseBlock
*/
-
(NSURLSessionDataTask *)startGetHttpWithStr:(NSString *)url Dictionary:(NSDictionary *)parameters fromController:(UIViewController *)controller Success:(void(^)( id responseObject))Success andFalse:(void(^)(NSError error))falseBlock
{
/ 傳入url開始下載前在數(shù)組來查找有沒有此Url對(duì)應(yīng)的鍵值
有 表示此下載存在返回
無 表示無此下載 開始下載/
QQsession *QS= [_dataDic objectForKey:url];
if (!QS) {
QS = [[QQsession alloc]init];
QS.urlStr = url;
operation = [QS conenctWithstr:url dic:parameters fromController:controller success:^(id resposeObject) {
Success(resposeObject);} andfalse:^(NSError *error) { falseBlock(error); }]; }return operation;
}
/**
- 帶控制器的post請(qǐng)求
- @param url url description
- @param parameters parameters description
- @param controller controller description
- @param Success Success description
- @param falseBlock falseBlock description
*/
- (NSURLSessionDataTask *)startPostHttpWithStr:(NSString *)url Dictionary:(NSDictionary *)parameters fromController:(UIViewController *)controller Success:(void(^)( id responseObject))Success andFalse:(void(^)(NSError *error))falseBlock
{
QQsession *QS= [_dataDic objectForKey:url];
if (!QS) {
QS = [[QQsession alloc]init];
QS.urlStr = url;
operation = [QS postWithstr:url dic:parameters fromController:controller success:^(id resposeObject) {
Success(resposeObject);
} andfalse:^(NSError *error) {
falseBlock(error);
}];
}
return operation;
}
//為了防止單個(gè)網(wǎng)絡(luò)請(qǐng)求多次請(qǐng)求 例如刷新 - (void)insertQQConnection:(QQsession *)hc
{
[_dataDic setObject:hc forKey:hc.urlStr];
} - (void)deleteQQConnection:(QQsession )hc
{
[_dataDic removeObjectForKey:hc.urlStr];
}
/*
某個(gè)線程A調(diào)用lock方法。這樣,NSLock將被上鎖。可以執(zhí)行“關(guān)鍵部分”,完成后,調(diào)用unlock方法。
如果,在線程A 調(diào)用unlock方法之前,另一個(gè)線程B調(diào)用了同一鎖對(duì)象的lock方法。那么,線程B只有等待。直到線程A調(diào)用了unlock
*/
- (void)insertConnectionVC:(UIViewController *)VC QQConnection:(QQsession *)hc SessionDataTask:(NSURLSessionDataTask *)task
{
[lock lock];
NSDictionary *Dic = @{String(VC):hc,@"Task":task};
[_VCS addObject:Dic];
[lock unlock];
}
//控制器消失的時(shí)候取消下載 - (void)deleteConnectionVC:(UIViewController *)vc
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[lock lock];
NSMutableArray tempArr = [NSMutableArray arrayWithArray:_VCS];
NSMutableArray VCArray = [NSMutableArray arrayWithArray:vc.childViewControllers];
if ([TXUtilsString IsNull:vc]) {
[VCArray addObject:[UIViewController new]];
}else{
[VCArray addObject:vc];
}
/
有的三方滑動(dòng)視圖 或者自己的寫的segment都會(huì)用到addchildviewcontroller:
但是點(diǎn)擊返回直接返回到上一個(gè)界面 segment的父視圖是自己寫的另一個(gè)界面 而三方的滑動(dòng)視圖是別人的
*/
warning 這里主要還是根據(jù)入庫的控制器來寫不是一定的 根據(jù)個(gè)人需要
// //針對(duì)滑動(dòng)的三方
for (UIViewController *VC in VCArray) {
for (NSDictionary *TempDic in tempArr) {
if (TempDic[String(VC)]) {
[self deleteQQConnection:TempDic[String(VC)]];
[(QQsession *)TempDic[String(VC)] cancelWithOperation:TempDic[@"Task"]];
if (_IsConsolePrint) {
QMLog(@"Cancel this url '%@'",[(QQsession *)TempDic[String(VC)] urlStr]);
}
[_VCS removeObject:TempDic];
}
}
}
[lock unlock];
});
}
- (void)showProgressHUDWithType:(NSInteger)type {
//解決alert重彈出
if (_alert.isVisible) {
return;
}
NSString *msg = nil;
switch (type) {
case 0:
{
msg =@"網(wǎng)絡(luò)連接異常,請(qǐng)檢查網(wǎng)絡(luò)!";
break;
}
case 1:
{
msg = @"服務(wù)故障,正在搶修!";
break;
}
default:
break;
}
_alert = [[UIAlertView alloc]initWithTitle:@"" message:msg delegate:nil cancelButtonTitle:@"確定" otherButtonTitles:nil, nil] ;
[_alert show];
}
@end
下面是自定義Navgationcontroller配合下載類做控制器銷毀取消下載
`QQNavigationController.m`文件中的方法
@interface QQNavigationController ()<UIGestureRecognizerDelegate,UINavigationControllerDelegate>
{
UIViewController *tempVC;///棧頂?shù)目刂破?br>
}
@end
@implementation QQNavigationController
- (void)initialize{
}
-
(void)viewDidLoad {
[super viewDidLoad];
//添加系統(tǒng)自帶的手勢(shì)返回
self.delegate = self;
if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
self.interactivePopGestureRecognizer.delegate = self;
}self.view.backgroundColor = [UIColor whiteColor];
//設(shè)置透明度 相差64像素
self.navigationBar.translucent = YES;
//設(shè)置顏色
self.navigationBar.barTintColor = RGB(0, 122, 255);
//處理ScrollViewInsets的自動(dòng)下沉
self.automaticallyAdjustsScrollViewInsets = NO;[self.navigationBar setTitleTextAttributes:[NSDictionary dictionaryWithObjectsAndKeys:[UIColor whiteColor], NSForegroundColorAttributeName, [UIFont systemFontOfSize:20], NSFontAttributeName, nil]];
if( ([[[UIDevice currentDevice] systemVersion] doubleValue]>=7.0)){
//如應(yīng)用中出現(xiàn)seachbar的跳動(dòng),視圖位置出現(xiàn)問題,有可能是這里引起的
self.edgesForExtendedLayout = UIRectEdgeNone;//視圖控制器,四條邊不指定self.extendedLayoutIncludesOpaqueBars = NO;//不透明的操作欄 //設(shè)置view的位置 self.modalPresentationCapturesStatusBarAppearance = NO;
// [[UINavigationBar appearance] setBackgroundImage:[UIImage new]
// forBarPosition:UIBarPositionTop
// barMetrics:UIBarMetricsDefault];
//設(shè)置navi透明需要translucent = yes,設(shè)置圖片,barMetrics(更改類型的道不同的效果)
}else{
// [self.navigationBar setBackgroundImage:[UIImage new]
// forBarMetrics:UIBarMetricsDefault];
}
//nav下面的橫線消失
// self.navigationBar.shadowImage = [UIImage new];
}
//配合修改狀態(tài)欄顏色
(UIViewController *)childViewControllerForStatusBarStyle{
return self.topViewController;
}(void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
//防止在push的過程中觸發(fā)返回的時(shí)間導(dǎo)致崩潰
if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
self.interactivePopGestureRecognizer.enabled = NO;
}
if (self.childViewControllers.count > 0) {
viewController.hidesBottomBarWhenPushed = YES;
UIButton *button = [[UIButton alloc] init];
[button setImage:[UIImage imageNamed:@"arrow_left"] forState:UIControlStateNormal];
button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
[button setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[button addTarget:self action:@selector(back) forControlEvents:UIControlEventTouchUpInside];
button.bounds = CGRectMake(0, 0, 70, 30);
button.contentEdgeInsets = UIEdgeInsetsMake(0, -5, 0, 0);
button.titleLabel.font = [UIFont systemFontOfSize:15];
viewController.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:button];
}
[super pushViewController:viewController animated:animated];
}-
(void)back
{
/UIViewController * VC = [self popViewControllerAnimated:YES];
//這里打印的是剛剛出棧的VC
QMLog(@"%@",VC);
[[QQNetManager defaultManager] deleteConnectionVC:VC];/[[QQNetManager defaultManager] deleteConnectionVC:self.viewControllers.lastObject];
QMLog(@"viewControllers-pop:%@",self.viewControllers.lastObject);
}
warning 這里可以設(shè)置返回手勢(shì)的開關(guān)
//推送的視圖將要出現(xiàn)時(shí)將側(cè)滑返回設(shè)置為真
- (void)navigationController:(UINavigationController *)navigationController
didShowViewController:(UIViewController *)viewController
animated:(BOOL)animated
{
if (navigationController.viewControllers.count == 1){
navigationController.interactivePopGestureRecognizer.enabled = NO;
}else{
navigationController.interactivePopGestureRecognizer.enabled = YES;
}
//獲取棧頂?shù)腣c
tempVC = navigationController.viewControllers.lastObject;
} - (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
id<UIViewControllerTransitionCoordinator> tc = navigationController.topViewController.transitionCoordinator;
[tc notifyWhenInteractionEndsUsingBlock:^(id<UIViewControllerTransitionCoordinatorContext> context) {
//手勢(shì)返回 成功取消
if (![context isCancelled]) {
QMLog(@"%@",viewController);//這里打印的是棧頂?shù)腣C 所以要tempVC中間過渡
[[QQNetManager defaultManager] deleteConnectionVC:tempVC];
}
}];
}
// 為了解決與scroll的手勢(shì)沖突 - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]
&& [otherGestureRecognizer isKindOfClass:[UIScreenEdgePanGestureRecognizer class]]){
// [_scrollView.panGestureRecognizer requireGestureRecognizerToFail:screenEdgePanGestureRecognizer];
return NO;
}else{
return YES;
}
}
文件中的注釋已經(jīng)很詳細(xì)了,在這里就不多做贅述了。
有個(gè)要注意的是`AFNetworking` 手動(dòng)取消下載也會(huì)執(zhí)行下載失敗的回調(diào),打印`error`為
```Error Domain=NSURLErrorDomain Code=-999 "已取消" UserInfo={NSErrorFailingURLKey=http://172.16.10.34:8080/wisdomCampus_interface/assessment/queryList?flag=1&pageindex=1&pagesize=10&token=932b80a56a38bcc1b22796c2fdf57383, NSLocalizedDescription=已取消, NSErrorFailingURLStringKey=http://172.16.10.34:8080/wisdomCampus_interface/assessment/queryList?flag=1&pageindex=1&pagesize=10&token=932b80a56a38bcc1b22796c2fdf57383}```
可以利用`error ==-999`來判斷是不是取消下載執(zhí)行的失敗回調(diào)。
##2016.11.14日更新內(nèi)容
之前的
```UIViewController * VC = [self popViewControllerAnimated:YES];
//這里打印的是剛剛出棧的VC
QMLog(@"%@",VC);
[[QQNetManager defaultManager] deleteConnectionVC:VC]```
會(huì)有一個(gè)問題 就是當(dāng)我A界面push進(jìn)B界面,然后迅速(就是很快很快)pop回B界面,雖然返回成功但是`popViewControllerAnimated:YES` 的返回值是`nil`之前在`- (void)deleteConnectionVC:(UIViewController *)vc
`里面沒有做空的判斷因此會(huì)崩潰。不過此時(shí)`viewControllers`里面是有B界面的。所以現(xiàn)在用` [[QQNetManager defaultManager] deleteConnectionVC:self.viewControllers.lastObject];
`來取值
##2017.03.18日更新內(nèi)容
`popViewControllerAnimated:YES `返回為nil,大概是因?yàn)閜op動(dòng)畫沒有完成的造成的。與之相關(guān)聯(lián)的章[我是傳送門點(diǎn)我進(jìn)入](http://blog.lessfun.com/blog/2015/09/09/uiviewcontroller-push-pop-trap/)
[附上下載地址](http://code.cocoachina.com/view/134516)demo里面有增加一些東西文章中沒有寫出來
[自己日常用到的一些組件](https://github.com/MuYanQin/QQKit)
#如有錯(cuò)誤請(qǐng)?zhí)岢鑫視?huì)改正,勿噴!持續(xù)更新。。