从自己刚开始做项目时,一直都是用AFNetworking进行网络请求。每次涉及网络请求的时候都手写一次,写的整个人都不好不好了。特别AFNetworking升级3.0的时候真想剁了自己的手。从那以后只要是第三方的我基本都是自己做一层封装。废话不多说下面来聊聊下载类的封装。
所谓下载管理类:无非就是,在对AFNetworking封装便用的时候,在进行一次对下载工具的封装。这样的好处有以下几点:
- 同一个网址的数据请求只会进行一次。 例如用户对TableView的疯狂下拉刷新 数据源处理不好可能会崩溃。
- 控制器消失的时候取消当前数据请求。 AFNetworking进行数据请求,如果不手动取消请求的话在控制器销毁的时候此次任务还是存在的。
当请求完成的时候走回调Block这就有崩溃的危险存在。当初这里理解了,当AF请求没有完成就pop的话,由于AF引用当前界面,所以当前界面不会释放。直至完成才会释放故不存在crash一说。返回只是节省用户流量罢了。
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
{
//没网络的情况时直接返回
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
{
//没网络的情况时直接返回
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 - 统一处理下载返回的数据
- (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 主动退出怎么才能不显示失败的提示 -999就是取消此次下载
if (error.code != -999) {
//非主动取消链接
[[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 网络判断
-(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;/* 网络指示器的状态: 有网络 : 开 没有网络: 关 */
});
return isNetworkEnable;
}
QQNetManager.h
中的方法
@interface QQNetManager : NSObject
{
NSMutableDictionary *_dataDic;///<纪录下载的url
NSMutableArray *_VCS; ///<纪录当前控制器有哪些下载
UIAlertView * _alert;///<防治alert重复出现
}
+ (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;///<插入对象
- (void)deleteQQConnection:(QQsession *)hc;//<删除对象
//- (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请求
- @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开始下载前在数组来查找有没有此Url对应的键值
有 表示此下载存在返回
无 表示无此下载 开始下载/
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请求
- @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;
}
//为了防止单个网络请求多次请求 例如刷新 - (void)insertQQConnection:(QQsession *)hc
{
[_dataDic setObject:hc forKey:hc.urlStr];
} - (void)deleteQQConnection:(QQsession )hc
{
[_dataDic removeObjectForKey:hc.urlStr];
}
/*
某个线程A调用lock方法。这样,NSLock将被上锁。可以执行“关键部分”,完成后,调用unlock方法。
如果,在线程A 调用unlock方法之前,另一个线程B调用了同一锁对象的lock方法。那么,线程B只有等待。直到线程A调用了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];
}
//控制器消失的时候取消下载 - (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];
}
/
有的三方滑动视图 或者自己的写的segment都会用到addchildviewcontroller:
但是点击返回直接返回到上一个界面 segment的父视图是自己写的另一个界面 而三方的滑动视图是别人的
*/
warning 这里主要还是根据入库的控制器来写不是一定的 根据个人需要
// //针对滑动的三方
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 =@"网络连接异常,请检查网络!";
break;
}
case 1:
{
msg = @"服务故障,正在抢修!";
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;///栈顶的控制器
}
@end
@implementation QQNavigationController
- (void)initialize{
}
-
(void)viewDidLoad {
[super viewDidLoad];
//添加系统自带的手势返回
self.delegate = self;
if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
self.interactivePopGestureRecognizer.delegate = self;
}self.view.backgroundColor = [UIColor whiteColor];
//设置透明度 相差64像素
self.navigationBar.translucent = YES;
//设置颜色
self.navigationBar.barTintColor = RGB(0, 122, 255);
//处理ScrollViewInsets的自动下沉
self.automaticallyAdjustsScrollViewInsets = NO;[self.navigationBar setTitleTextAttributes:[NSDictionary dictionaryWithObjectsAndKeys:[UIColor whiteColor], NSForegroundColorAttributeName, [UIFont systemFontOfSize:20], NSFontAttributeName, nil]];
if( ([[[UIDevice currentDevice] systemVersion] doubleValue]>=7.0)){
//如应用中出现seachbar的跳动,视图位置出现问题,有可能是这里引起的
self.edgesForExtendedLayout = UIRectEdgeNone;//视图控制器,四条边不指定self.extendedLayoutIncludesOpaqueBars = NO;//不透明的操作栏 //设置view的位置 self.modalPresentationCapturesStatusBarAppearance = NO;
// [[UINavigationBar appearance] setBackgroundImage:[UIImage new]
// forBarPosition:UIBarPositionTop
// barMetrics:UIBarMetricsDefault];
//设置navi透明需要translucent = yes,设置图片,barMetrics(更改类型的道不同的效果)
}else{
// [self.navigationBar setBackgroundImage:[UIImage new]
// forBarMetrics:UIBarMetricsDefault];
}
//nav下面的横线消失
// self.navigationBar.shadowImage = [UIImage new];
}
//配合修改状态栏颜色
-
(UIViewController *)childViewControllerForStatusBarStyle{
return self.topViewController;
} -
(void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
//防止在push的过程中触发返回的时间导致崩溃
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 这里可以设置返回手势的开关
//推送的视图将要出现时将侧滑返回设置为真
- (void)navigationController:(UINavigationController *)navigationController
didShowViewController:(UIViewController *)viewController
animated:(BOOL)animated
{
if (navigationController.viewControllers.count == 1){
navigationController.interactivePopGestureRecognizer.enabled = NO;
}else{
navigationController.interactivePopGestureRecognizer.enabled = YES;
}
//获取栈顶的Vc
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) {
//手势返回 成功取消
if (![context isCancelled]) {
QMLog(@"%@",viewController);//这里打印的是栈顶的VC 所以要tempVC中间过渡
[[QQNetManager defaultManager] deleteConnectionVC:tempVC];
}
}];
}
// 为了解决与scroll的手势冲突 - (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;
}
}
文件中的注释已经很详细了,在这里就不多做赘述了。
有个要注意的是`AFNetworking` 手动取消下载也会执行下载失败的回调,打印`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`来判断是不是取消下载执行的失败回调。
##2016.11.14日更新内容
之前的
```UIViewController * VC = [self popViewControllerAnimated:YES];
//这里打印的是刚刚出栈的VC
QMLog(@"%@",VC);
[[QQNetManager defaultManager] deleteConnectionVC:VC]```
会有一个问题 就是当我A界面push进B界面,然后迅速(就是很快很快)pop回B界面,虽然返回成功但是`popViewControllerAnimated:YES` 的返回值是`nil`之前在`- (void)deleteConnectionVC:(UIViewController *)vc
`里面没有做空的判断因此会崩溃。不过此时`viewControllers`里面是有B界面的。所以现在用` [[QQNetManager defaultManager] deleteConnectionVC:self.viewControllers.lastObject];
`来取值
##2017.03.18日更新内容
`popViewControllerAnimated:YES `返回为nil,大概是因为pop动画没有完成的造成的。与之相关联的章[我是传送门点我进入](http://blog.lessfun.com/blog/2015/09/09/uiviewcontroller-push-pop-trap/)
[附上下载地址](http://code.cocoachina.com/view/134516)demo里面有增加一些东西文章中没有写出来
[自己日常用到的一些组件](https://github.com/MuYanQin/QQKit)
#如有错误请提出我会改正,勿喷!持续更新。。
网友评论