必威-必威-欢迎您

必威,必威官网企业自成立以来,以策略先行,经营致胜,管理为本的商,业推广理念,一步一个脚印发展成为同类企业中经营范围最广,在行业内颇具影响力的企业。

这里是释放,销毁之后全部cell都失去倒计时的功

2019-09-12 19:42 来源:未知

求助,各位大神,有没有什么方法释放掉这个控制器????

图片 1

tableview cell 上播放倒计时

3.其他

后台返回的json中的boolean类型数据。开始以为和OC里的BOOL类型是同一回事,但后来发现怎么都不对于是打断点发现是__NSCFBoolean类型。

NSCFBoolean是NSNumber类簇中的一个私有的类。它是通往CFBooleanRef类型的桥梁,它被用来给Core Foundation的属性列表和集合封装布尔数值。CFBoolean定义了常量kCFBooleanTrue和kCFBooleanFalse。因为CFNumberRef和CFBooleanRef在Core Foundation中属于不同种类,这样是有道理的,它们在NSNumber被以不同的衔接类呈现。

转换成BOOL调用boolValue方法:[nscfBooleanValue boolValue];

更新:模态视图释放不当造成内存泄露
在做倒计时按钮时遇到一个比较诡异的事情。
做登录注册功能时的模态视图用到了导航栏,按流程走一步步填写信息并且push到下一步,当流程走完要dismiss掉整个模态视图。
VC -> present A(嵌套NaviagtionController) -> push B(B的导航栏右按钮是封装的倒计时按钮) -> push C -> push D -> dismiss VC。
尽管在写demo测试时倒计时按钮不会有内存泄漏问题,但因为用到了NSTimer,怕有内存泄漏就还是在按钮类里写了dealloc。dismiss时,按钮的dealloc没有调用。然后给A、B、C、D控制器都写了dealloc,发现控制器的dealloc都调用了。但如果是从B pop回到A,按钮的dealloc又可以调用到。
按钮的dealloc没有调用到而控制器的dealloc调用了,那是按钮的内存泄漏了。找了很久才发现问题不是出在封装的按钮身上。我写了另外一个按钮:继承自UIButton,然后里面只有一个dealloc方法,把它放到C的导航栏上,dismiss时同样也不会调用到dealloc.

@implementation HXBtnTest 
- (void)dealloc{
    NSLog(@"btn销毁了");
}
@end

present A:

AViewController *aVC = [[AViewController alloc]init];
UINavigationController *navi = [[UINavigationController alloc]initWithRootViewController:aVC];
tabBarController presentViewController:navi animated:YES completion:nil];

在D中的dismiss是这样写的:

[self dismissViewControllerAnimated:YES completion:^{
    [[NSNotificationCenter defaultCenter] postNotificationName:HXPushViewControllerNotification object:nil];
}];

基本上一直以来都是这样写代码,没意识过会有问题,上网查似乎又没有人问过类似的问题。。

既然导航栏上的按钮没有被释放,那么久证明还有别的东西在强引用着它。按钮在导航栏上,那么强引用的就是navigationController了,而且情况是,嵌套在nav vc中的视图控制器都释放了而nav vc没有释放。

关于导航栏,准确来说应该是这样的:
navigationcontroller直接控制viewcontrollers集合,然后它包含的navigationbar是整个工程的导航栏,bar有一个用来管理navigationItem的栈。@property(nonatomic, copy) NSArray <UINavigationItem *> *items
navigationItem包含了navigationbar视图的全部元素(如title,tileview,backBarButtonItem等),每个视图控制器的导航项元素由所在视图控制器的navigationItem管理。即设置当前页面的左右barbutton。

因此出现导航栏自定义按钮不能释放的问题有可能是因为navigationcontroller不正常pop造成的。比如当我们写self.navigationViewController popViewController:xxx 时,每pop一个视图控制器,对应的navigationItem 也会pop出栈,其管理的控件也得以释放。

所以这就解释了为什么在D VC中直接写self dismissViewControllerxxx不能释放导航栏按钮。如果你问我,navigationviewcontroller既然没被释放,那么它是被谁持有?我认为是present A的那个控制器。

如何修改:先popToRootViewController再dismiss

//先取得presentingViewController。不先保存的话,popvc之后可能就为空了
UIViewController *temp = self.presentingViewController;
[self.navigationController popToRootViewControllerAnimated:YES];
[temp dismissViewControllerAnimated:YES completion:^{
    [[NSNotificationCenter defaultCenter] postNotificationName:HXPushViewControllerNotification object:nil];
}];

控制器中的代码

  • 首先遍历数据源,取出需要倒计时的数据模型
-(void)enumerateDatasourceCountDown
{
    for(int i = 0; i < self.dataSource.count; ++i)
    {
        DataModel *model = self.dataSource[i];
        if (model.countTime)
        {
            [self countDownModel:model andIndexPath:i];
        }
    }
}
  • 把倒计时的具体剩余时间和与之对应的indexPath保存起来在字典中
-(void)countDownModel:(DataModel *)model andIndexPath:(NSInteger )indexInteger
{
    //哪一行的数据有倒计时
    NSString *indexKey = [NSString stringWithFormat:@"%ld",indexInteger];
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];
    [dict setObject:indexKey forKey:@"indexPath"];
    [dict setObject:@(model.countTime) forKey:indexKey];

    //把模型里的倒计时储存在字典中 以行数index索引为key
    //添加定时器之前先判断这一行的数据是不是已经添加了定时器
    NSNumber *number = self.countDownTimeDict[indexKey];
    NSInteger numberInteger =  [number integerValue];

    //如果在倒计时的字典中取不到 value 说明还没有添加定时器
    if (numberInteger <= 0)
    {
        //添加定时器
        NSTimer *timer  =  [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(numberCutDown:) userInfo:dict repeats:YES];
        [self.timerArr addObject:timer];
        [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

        NSLog(@"第%ld行已经添加了定时器",indexInteger);
    }
    [self.countDownTimeDict addEntriesFromDictionary:dict];
  • 定时器会执行的方法 (在这个方法中修改模型的倒计时,因为模型决定了cell显示什么数据,所以更改模型之后再去刷新这一行就不会出现数据混乱)
-(void)numberCutDown:(NSTimer *)timer
{
    //取出对应倒计时
    NSString * indexInteger = timer.userInfo[@"indexPath"];
    NSInteger index = [indexInteger integerValue];

    DataModel *model = self.dataSource[index];
    //修改模型时间
    model.countTime --;
    //刷新界面
    NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0];
    [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
    if (model.countTime == 0)
    {
        NSLog(@"第%ld行的定时器销毁了",index);
        [timer invalidate];
        timer = nil;
        return;
    }
}

GCD定时器非常耗性能,耗内存,pop这个控制器的时候发现没有释放,在控制器即将消失

封装为自定义UIButton

为了使用的方便,降低耦合性.可以将其封装为一个自定义UIButton类,以后也可以直接在其他项目使用
新建一个继承UIButton的类:NZCountdownButton并将之前代码拷贝
将所有self.captchaBtn改为self
viewDidLoad中代码放在init方法中
详细代码见文章底部的源代码

项目中有用到cell上播放倒计时,遇到很多的坑,在这里总结一些以免下次再遇到

  • 1 第一条跟NStimer无关 只是平时项目中的细节问题,一定要注意block中的循环引用问题,这里有两种方法
  // 第一种创建一个self弱引用
  #define WS(weakSelf)  __weak __typeof(&*self)weakSelf = self;
  //第二种就是如果项目中用到了reactivecocoa这个第三方的话 使用     @weakify(self)
  @Strongify(self)

1.倒计时按钮封装

使用场景:注册1页点击获取验证码按钮,push到注册2页。界面如下“注册2-1页”所示,导航栏右按钮马上进入倒计时状态并不可点击;倒计时结束变成“注册2-2页”所示,可点击并重新发送验证码。在做重置密码功能的时候也用到了类似的逻辑。

图片 2

注册1页

图片 3

注册2-1页

图片 4

注2-2页

倒计时按钮的封装网上一抓一大把代码,也不会很复杂,那就根据自己项目需要封装一个吧!

按钮继承自UIButton,选择NSTimer作为定时器,在子线程中计时,主线程中修改ui。直接上代码:

因为用的是NSTimer,所以要注意强引用引起的内存问题。利用NSTimer分类作为timer的target来解除强引用,之前的一篇文章里面已经写过了所以就不多说了NSTimer的坑

#import "NSTimer+Addition.h"

@implementation NSTimer (Addition)
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval block:(void(^)(NSTimer *timer))block repeats:(BOOL)repeats{
    return [self scheduledTimerWithTimeInterval:interval
                                         target:self
                                       selector:@selector(blockInvoke:)
                                       userInfo:[block copy]
                                        repeats:repeats];
}

+ (void)blockInvoke:(NSTimer *)timer {
    void (^block)() = timer.userInfo;
    if(block) {
        block(timer);
    }
}
@end

倒计时按钮对外暴露的接口:

#import <UIKit/UIKit.h>

typedef void (^networkBlock)(void);//网络操作的block

@interface TimerButton : UIButton
@property (nonatomic ,weak) NSTimer *timer;
@property (nonatomic ,assign)CFRunLoopRef runloop;
//参数1 frame ;参数2 定时器计数次数;参数3 定时器计数间隔 ;参数4 :网络操作block
- (instancetype)initWithFrame:(CGRect)frame timerCount:(int)count timerInerval:(CGFloat)interval networkRequest:(networkBlock)networkBlock;

@end

按钮的具体实现:
1.初始化方法中做的:按钮的一些样式设置、计数次数等参数的赋值、开启timer(因为从注册1页push到下一页,timer就开始倒计时了,所以把timer的开启也放在初始化里做)
2.开启timer:使用gcd子线程中创建timer,因为NSTimer的定时器要添加到runloop才有效,所以要开启子线程runloop且runloop mode要适配。在timer触发时候执行的方法中做按钮UI的更新,如果计时完毕的话就销毁timer并且关闭runloop。
3.当倒计时完毕,按钮恢复可点击状态。点击按钮,发起网络请求获得验证码,并且创建新的timer

#import "TimerButton.h"
#import "NSTimer+Addition.h"
@interface TimerButton()
@property (nonatomic ,copy) networkBlock networkBlock;
@end

@implementation TimerButton
{
    int timerCount;
    int resetCount;
    CGFloat timerInterval;
}
//必须要在vc的dealloc方法中调用btn 的timer销毁方法和runloop的退出方法,保证vc pop的时候btn可以马上销毁

- (instancetype)initWithFrame:(CGRect)frame timerCount:(int)count timerInerval:(CGFloat)interval networkRequest:(networkBlock)networkBlock{
    if (self = [super initWithFrame:frame]) {

        timerCount = count;
        timerInterval = interval;
        self.networkBlock = [networkBlock copy];

        self.enabled = NO;
        [self setTitle:@"重发验证码" forState:UIControlStateNormal];
        [self setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
        [self addTarget:self action:@selector(btnClicked) forControlEvents:UIControlEventTouchUpInside];
        [self timerAction];
    }
    return self;
}

//点击按钮,如果有网络操作就执行网络操作,并且开启新的timer
- (void)btnClicked{
    if (self.networkBlock) {
        self.networkBlock();
    }
    [self timerAction];
}

//开启timer
- (void)timerAction{
    resetCount = timerCount;
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        __weak typeof (self)weakself = self;
        self.timer = [NSTimer scheduledTimerWithTimeInterval:timerInterval block:^(NSTimer *timer) {
            NSLog(@"。。。");
            __strong typeof(weakself) strongself = weakself;
            resetCount --;
            if (resetCount == 0) {
                [strongself.timer invalidate];
                strongself.enabled = YES;
                CFRunLoopStop(CFRunLoopGetCurrent());//这一句照理说其实也可以不写,因为定时器触发唤醒runloop,销毁timer,然后runloop判断还有没有源。因为没有源了,所以runloop会退出。
            }else{
                self.enabled = NO;
                dispatch_async(dispatch_get_main_queue(), ^{
                    [strongself setTitle:[NSString stringWithFormat:@"%ds后重发",resetCount] forState:UIControlStateDisabled];
                    [strongself setTitleColor:[UIColor grayColor] forState:UIControlStateDisabled];
                });
            }
        } repeats:YES];
        [self.timer fire];//马上执行
        self.runloop = CFRunLoopGetCurrent();
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    });
}

使用:
必须要在vc的dealloc方法中调用倒计时按钮的timer销毁方法和runloop的退出方法,保证vc pop的时候btn可以马上销毁。

@property (nonatomic ,weak) TimerButton *btn;

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    TimerButton *btn = [[TimerButton alloc]initWithFrame:CGRectMake(0, 0, 100, 40) timerCount:5 timerInerval:1.0 networkRequest:nil];
    _btn = btn;
    self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]initWithCustomView:btn];
}

- (void)dealloc{
    NSLog(@"vc销毁了");
    [self.btn.timer invalidate];
    CFRunLoopStop(self.btn.runloop);
}

ps:如果不写CFRunLoopStop(self.btn.runloop);,pop viewController时倒计时按钮无法释放(在倒计时按钮的类中写dealloc,pop viewController,按钮的dealloc方法没被调用,但控制器的dealloc方法调用了,所以控制器释放了而按钮没释放)。

为什么会这样请看:NSTimer的坑。主要是iOS10在处理子线程runloop上有所不同。例子中涉及到线程异步的问题,定时器是在子线程RunLoop中注册的,但定时器的移除操作却是在主线程,由于子线程RunLoop处理完一次定时信号后,就会进入休眠状态。在iOS10以前的环境下,定时器被移除后,内核仍然会向对应的Timer Port发送一次信号,所以子线程RunLoop接收到信号后会被唤醒,由于没有定时源需要处理,所以RunLoop会直接跳转到判断阶段,判断阶段会检测当前RunLoopMode是否有事件源需要处理,若没有事件源需要处理,则会退出RunLoop。
但在iOS10环境下,当定时器被移除后,内核不再向对应的Timer Port发送任何信号,所以子线程RunLoop一直处于休眠状态并没有退出,而我们只需要手动唤醒RunLoop(或者直接退出runloop)即可。

总结:

这种方式,是在控制器中每过一秒就修改数据源,然后再重新刷新这一行数据达到倒计时的效果,解决了上述的两个问题。

  • 在控制器中创建timer定时器,一个定时器管理一个cell,如果cell的倒计时结束了,那就停止定时器,而且不会影响到其他cell,也不会浪费性能。
  • 由于修改的是模型,所以避免了数据混乱。
  • 本文 Demo
- countTime{ AFWeakSelf; NSTimeInterval interval = [[NSDate date] timeIntervalSince1970] ; double currentTime = [self.model.promote_end_date doubleValue] - interval; __block float timeout= currentTime; //倒计时时间 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_source_t _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0,queue); dispatch_source_set_timer(_timer,dispatch_walltime,0.1*NSEC_PER_SEC, 0); // 每100毫秒执行 dispatch_source_set_event_handler(_timer, ^{ if(timeout<=0){ //倒计时结束,关闭 dispatch_source_cancel; dispatch_async(dispatch_get_main_queue(), ^{ // 倒计时结束,关闭处理 }); }else{ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ NSTimeInterval interval = [[NSDate date] timeIntervalSince1970] ; double currentTime = [self.model.promote_end_date doubleValue] - interval; int currentHour = currentTime / 3600; int currentMinute = (currentTime - currentHour*3600) / 60; int currentSeconds = currentTime - currentHour*3600 -currentMinute*60; int currentMsec = currentTime*1000 - currentHour*3600*1000 - currentMinute*60*1000 - currentSeconds*1000; weakSelf.hourLabelA.text = [NSString stringWithFormat:@"%d",currentHour/10]; weakSelf.hourLabelB.text = [NSString stringWithFormat:@"%d",(currentHour%10)]; weakSelf.minuteLabelA.text = [NSString stringWithFormat:@"%d",currentMinute/10]; weakSelf.minuteLabelB.text = [NSString stringWithFormat:@"%d",currentMinute%10]; weakSelf.secondLabelA.text = [NSString stringWithFormat:@"%d",currentSeconds/10]; weakSelf.secondLabelB.text = [NSString stringWithFormat:@"%d",currentSeconds%10]; weakSelf.msecLabel.text = [NSString stringWithFormat:@"%d",currentMsec/100%1000]; }); timeout--; } }); dispatch_resume; self.myTimer = _timer;}

图片 5

这两个是成对出现的block外用@weakify block里面用@strongify(今天发现好多控制器请求数据的时候没有严格使用,导致很多控制器没有释放)

  • 2 这里只说我自己遇到的问题及解决办法,基本的介绍就不再说了

    (1)定时器如果要循环的话需要加入到runloop中

    [[NSrunLoop currenRunLoop] addTimer:_timerforMode:NSRunLoopCommonModes]

(2)定时器创建的线程和释放的线程应该在同一个线程,否则的话无法释放有兴趣的同学可以试一下

  //创建
  dispatch_async(dispatch_get_main_queue(), ^{
    if (self.time) {
        [self.time invalidate];
    }
    self.time = [NSTimer ez_scheduledTimerWithTimeInterval:1    block:^{
        NSDate *currentDate =[NSDate date];
        NSCalendar *calendar = [NSCalendar currentCalendar];

        NSCalendarUnit  unit = NSDayCalendarUnit | NSCalendarUnitHour | NSCalendarUnitMinute  | NSCalendarUnitSecond;
        NSDateComponents *commponent = [calendar components:unit fromDate:currentDate toDate:[NSDate dateWithString:weakSelf.endTime ] options:NSCalendarWrapComponents];


        NSDate *dt = [[NSDate dateWithString: weakSelf.endTime] earlierDate:currentDate];
        NSLog(@"-------------------%@",weakSelf.endTime);
        //    self.getPrimeRate.enabled =YES;

        if([dt isEqualToDate:[NSDate dateWithString:weakSelf.endTime ]])
        {
            [weakSelf.time invalidate];
            weakSelf.countDownLabel.text = @"⚡️距离开放认投剩余0天00时00分00秒";

        }else
        {
            weakSelf.countDownLabel.text = [NSString stringWithFormat:@"⚡️距离开放认投剩余%zd天%02zd时%02zd分%02zd秒",commponent.day,commponent.hour,commponent.minute,commponent.second];

        }
    } repeats:YES];
});

这里是释放

dispatch_async(dispatch_get_main_queue(), ^{
             @strongify(self)
             [self.time invalidate];
             self.time = nil;
             NSLog(@"timer停止了");
         });

注 这里我使用的是一个NStimer的分类原文有详细介绍就是创建一个对self弱引用的Nstimer原文

  • 3 由于cell的重用问题 每次控制pop或者dismiss的时候,都不能够释放所以尤其是在和定时器一起用的时候要特别的注意循环引用的问题 ,我就是 遇到了这样的问题感觉很坑的 ,我们要想释放cell就需要知道 控制器的UIviewcontroller的dealoc方法 那么我们怎么才能在cell中得到cell所在的控制器呢 为了降低耦合度 有网友想到了给view添加一个扩展的方法原文在这原文有循序渐进的讲解为什么但是最终没有完全解决我遇到的问题,很不错了

核心代码 :

- (UIViewController*)getViewController
{
    for (UIView* next = [self superview]; next; next = next.superview)
    {
    UIResponder* nextResponder = [next nextResponder];

    if ([nextResponder isKindOfClass:[UIViewController class]])
    {
        return (UIViewController*)nextResponder;
    }
}

return nil; }

其实是使用响应者链得到对应的Controller 这样的话cell就可以得到对应的Controller 然后使用RAC得到控制器销毁的消息发送时刻释放定时器。

核心代码如下 :

- (void)didMoveToSuperview
    {
        UIViewController *controller = [self getViewController];
        //这里需要判断相应的controller是否存在
        if (controller){
            @weakify(self)
            [controller.rac_willDeallocSignal
             subscribeCompleted:^{
                 @strongify(self)
                 [self.countDownTimer invalidate];
                 self.countDownTimer = nil;
             }];
        }
    }

这里释放的时机很重要使用上面的扩展需要Cell被添加到视图树之后才能获取到需要的UIViewController,不然得到会是一个空。那么怎么保证Cell一定被添加到视图树呢。UIView有个方法叫didMoveToSuperview,它会在该视图的父视图改变的时候被调用

  • 4最后的时候发现虽然好了很多打印的时候仍然有一个定时器不能释放 ,试了很多的办法都不行 所以 就在cell 的dealoc方法中又释放了一次就好了具体是什么原因暂时还没发现,希望有知道的大神能够说一下啊,毕竟自己还是一个菜鸟。

    最后又发现了一个好的例文还没仔细看【iOS】TableViewCell上展示倒计时;

    好了先写这么多,有想起来的再补充吧。

2.登录注册模块封装

项目里遇到这样一个需求,有一些功能是需要先登录然后才能使用的。当触发这些功能时,需要先判断用户是否已经登录。
1.未登录已登录情况下,触发不需要登录的功能,直接跳转。
2.未登录情况下,触发需要登录的功能,先进入登录界面,登录成功则跳转,不成功或者取消登录就留在原页面。
3.已登录,触发需要登录的功能,直接跳转。

触发登录的“入口”有可能是按钮,也有可能是其他任何控件,所以单独写了一个LoginManager的类来管理,在需要引导登录的地方调用这个类的方法就能实现相应的引导“行为”。

思路:
1.在AppDelegate中用一个全局变量记录是否已经登录。在开启app时会先进行自动登录,并对这个全局变量进行赋值。

AppDelegate.h
@property (nonatomic ,assign) BOOL isLogin;

2.是否需要检查登录?不用检查登录、要检查登录但已经登录的情况就转跳到要去的功能界面。
3.需要检查登录而未登录,实例化LoginViewController,然后获取topMost presenting viewcontroller,present loginVC。

对外暴露的接口:

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

typedef void (^loginedBlock)(void);

static NSString * const HXPushViewControllerNotification = @"hxPushViewController";
static NSString * const HXDismissViewControllerNotification = @"hxDismissViewController";

@interface LoginManager : NSObject
//参数1:触发登录时 最顶层的视图控制器 ;参数2:是否需要检查登录 ;参数3:已经登录、不需检查登录时要执行的block
+ (BOOL)checkLoginWithTopPresentingViewControllre:(UIViewController *)viewcontroller isCheckLogin:(BOOL)check loginedBlock:(loginedBlock)loginedBlock;

@end

+ (BOOL)checkLoginWithTopPresentingViewControllre:(UIViewController *)viewcontroller isCheckLogin:(BOOL)check loginedBlock:(loginedBlock)loginedBlock;方法参数的含义:
viewcontroller:顶层 presenting viewcontroller
check:是否检查登录
loginedBlock:已经登录、不需检查登录时的操作

具体实现:

#import "LoginManager.h"
#import "AppDelegate.h"
#import "NeedLoginViewController.h"

@interface LoginManager()
@property (nonatomic ,strong)UIViewController *topPresentingViewController;
@property (nonatomic ,copy)loginedBlock loginedBlock;
@end

@implementation LoginManager
static LoginManager *_instance;

+ (instancetype)shareLoginManager{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _instance = [[super allocWithZone:NULL] init];
    });
    return _instance;
}

+ (BOOL)checkLoginWithTopPresentingViewControllre:(UIViewController *)viewcontroller isCheckLogin:(BOOL)check loginedBlock:(loginedBlock)loginedBlock{
    LoginManager *manager = [LoginManager shareLoginManager];
    return [manager checkLoginWithTopPresentingViewControllre:viewcontroller isCheckLogin:check loginedBlock:loginedBlock];
}

- (BOOL)checkLoginWithTopPresentingViewControllre:(UIViewController *)viewcontroller isCheckLogin:(BOOL)check loginedBlock:(loginedBlock)loginedBlock{
    self.topPresentingViewController = viewcontroller;
    self.loginedBlock = [loginedBlock copy];
    //要检查是否已经登录
    if (check) {
        AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
        //已登录
        if (appDelegate.isLogin) {
            if (self.loginedBlock) {
                self.loginedBlock();
            }
            return YES;
        }
        //未登录
        else{
            [self presentLoginPage];
            return NO;
        }
    }
    //不检查登录
    else{
        if (self.loginedBlock) {
            self.loginedBlock();
        }
        return YES;
    }
}

- (void)presentLoginPage{
    //通知添加。先移除再添加.否则在登录界面点取消,再触发登录检查时会再次来到这个方法,导致多次添加通知。
    [[NSNotificationCenter defaultCenter] removeObserver:self name:HXPushViewControllerNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pushVC:) name:HXPushViewControllerNotification object:nil];
    //实例化loginVC 获取顶层VC,present loginVC
    NeedLoginViewController *nLoginVC = [[NeedLoginViewController alloc]init];
    UINavigationController *navi = [[UINavigationController alloc]initWithRootViewController:nLoginVC];
    [self.topPresentingViewController presentViewController:navi animated:YES completion:^{
    }];
}

//一般是登录成功后post HXPushViewControllerNotification
- (void)pushVC:(NSNotification *)notification{
    [[NSNotificationCenter defaultCenter] removeObserver:self name:HXPushViewControllerNotification object:nil];
    self.loginedBlock();
    AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
    appDelegate.isLogin = YES;
}

- (void)dismissVC:(NSNotification *)notification{
    [[NSNotificationCenter defaultCenter] removeObserver:self name:HXDismissViewControllerNotification object:nil];
}

- (void)dealloc{
    [[NSNotificationCenter defaultCenter] removeObserver:self name:HXPushViewControllerNotification object:nil];
}
@end

presentLoginPage方法这里注册了个通知。登录成功后在登录界面post该通知,然后执行相应的通知方法跳转到下一个界面中去,在这里使用的是已经登录、不需检查登录时的loginedBlock。
ps:这个通知先移除,再添加的原因:登录界面点“取消登录”,再触发登录检查时会再次来到这个方法,导致多次添加通知。

使用:
比如我们在点击tabbar的第二个tab时会触发登录检查:

- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController{
    if (viewController.tabBarItem.tag == 1 ) {
        return [LoginManager checkLoginWithTopPresentingViewControllre:tabBarController isCheckLogin:YES loginedBlock:^{
            //已经登录、或者未登录但在present 的登录界面中登录成功就会执行这个block
            tabBarController.selectedIndex = 1;
        }];
    }else{
        return YES;
    }
}

登录界面:
登录成功

- (void)loginBtn{
    ......
    //登录成功!
    [self dismissViewControllerAnimated:YES completion:^{
        [[NSNotificationCenter defaultCenter] postNotificationName:HXPushViewControllerNotification object:nil];
    }];
    ......
}

取消登录:直接dismiss viewcontroller

二. 结合网上的示例自己的解决方案

基于上面的缺陷,我想到了一个方法可以保证性能消耗不那么大,而且数据也不会出现错乱。而且包括数据的请求,缓存本地,上拉加载和上拉刷新时候带来的倒计时问题。具体源代码可以去github上下载,其实都是很简单的实现方法只是可能之前没有人分享出来,如果你发现代码中的 bug,或者你有更好的方法,欢迎批评指正!
  这里写出核心的思想。如果你的项目和我一样有这些需求,那么可以给你作一个参考。

  • timer 的添加和销毁
timer.gif
  • 数据不会重用

mix.gif

TAG标签:
版权声明:本文由必威发布于必威-编程,转载请注明出处:这里是释放,销毁之后全部cell都失去倒计时的功