zoukankan      html  css  js  c++  java
  • iOS学习——RUNLOOP、NSTimer

      每一个app的启动,开启主线程的同时,也开启了一个Runloop死循环,runloop会不断询问是否有新的任务给线程执行。runloop最常用的三块,就是网络事件,事件响应与NSTimer。网络事件现在基本上都用已经封装好的框架,但是最初用NSURLConnection进行网络请求的时候,会出现异步回调永远没法回来的情况,原因就是子线程运行完了,不会再次执行回调,对于这种情况就是让子线程上的runloop跑起来。

      

    - (void)demoTimer {
        [NSThread detachNewThreadWithBlock:^{
           [NSTimer scheduledTimerWithTimeInterval:1 repeats:NO block:^(NSTimer * _Nonnull timer) {
              NSLog(@"timer执行中%s  %@",__func__,[NSThread currentThread]);
    
          }];
            NSLog(@"线程走了%s  %@",__func__,[NSThread currentThread]);
    
        }];
        
        
    }

      现在做了一个类似网络请求的步骤,在子线程创建开启一个nstimer,并执行回调block,然后运行的结果是这样的

    2017-11-12 10:12:21.938 hhh[30280:461580] 线程走了__27-[ViewController demoTimer]_block_invoke  <NSThread: 0x608000265380>{number = 3, name = (null)}

      执行了NSTimer,然后线程就运行完任务直接消失,并没有执行回调方法,因此这时候就需要用到NSRunloop开启循环。

    - (void)demoTimer {
        [NSThread detachNewThreadWithBlock:^{
            [NSTimer scheduledTimerWithTimeInterval:1 repeats:NO block:^(NSTimer * _Nonnull timer) {
                NSLog(@"timer执行中%s  %@",__func__,[NSThread currentThread]);
                
            }];
                    [[NSRunLoop currentRunLoop] run];
    
            NSLog(@"线程走了%s  %@",__func__,[NSThread currentThread]);
            
        }];
    
    }
    2017-11-12 11:05:49.779 hhh[31453:486745] timer执行中__27-[ViewController demoTimer]_block_invoke_2  <NSThread: 0x608000076d80>{number = 3, name = (null)}
    2017-11-12 11:05:49.779 hhh[31453:486745] 线程走了__27-[ViewController demoTimer]_block_invoke  <NSThread: 0x608000076d80>{number = 3, name = (null)}

      不过这样同样会有一个问题,就是thread循环没有关闭,让线程一直没有被kill,因此,我们可以用

                [NSThread exit];

      退出当前线程,也可以才有有限制条件循环开启来保证循环不被一直开启

      

    - (void)demoTimer {
        
        __block BOOL isFinshed = NO;
        [NSThread detachNewThreadWithBlock:^{
            [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
                NSLog(@"timer执行中%s  %@",__func__,[NSThread currentThread]);
                isFinshed = YES;
            }];
            while (!isFinshed) {
                [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1f]];
    
            }
    
            NSLog(@"线程走了%s  %@",__func__,[NSThread currentThread]);
            
        }];
    
    }

      

      现在项目中能够主动使用得上runloop的,基本上就是开启NSTimer的时候。

    - (void)demoTimer1 {
        NSTimer *timer = [NSTimer timerWithTimeInterval:1.0f repeats:YES block:^(NSTimer * _Nonnull timer) {
            NSLog(@"%s  %@",__func__,[NSThread currentThread]);
        }];
        [[NSRunLoop currentRunloop] addTimer:timer forMode:NSDefaultRunLoopMode];
    }

      当创建一个timer对象的时候,需要将其加入runloop中才能运行起来,而runloop则按优先级可以分为两种模式,一种NSDefaultRunLoopMode,默认模式;一种NSRunLoopCommonModes,包含程序UI处理的模式,是UI模式和默认模式的一种混合模式。当使用默认模式执行timer时,触发界面上的UI事件,就会发现timer停止计时,因为这时候当前runloop会切换到具有更高优先级的UI模式。因此,如果要想UI响应与timer不冲突,那么就不得不使用NSRunLoopCommonModes

        [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

      不过有一点是需要注意的,就是timer内尽量执行轻量级代码,不要执行耗时耗性能操作,不然当结合scrollView等滚动界面使用的时候,仍然会出现卡顿的情况。

      而事件响应就不用过多赘述了,每次点击事件等事件发生,都是由当前线程上runloop接受并推动线程去执行。

      其实对于runloop还有分段执行这一操作,类似与多线程,但是工作原理大不一样,只不过由于现在多线程、并发等技术的实现,而很少需要用到。当一次runloop循环需要执行太多操作的时候,界面就会出现卡顿的情况,与nstimer执行耗时操作加入runloop中,滚动界面的效果类似。例如:当使用tableView加载图片,如果使用本地高清大图,一次加载过多的时候,滑动起来就会有强烈卡顿感,就是因为一次runloop需要执行大量图片渲染所造成的(类似的情景还有使用瀑布流加载本地大图,特别是苹果图片一般都比较大);这种情况下,可以是用多次runloop分段渲染,每次渲染一张图片(UI的改变只能在主线程,无法开启多线程渲染)。

    #import "ViewController.h"
    #import <CoreFoundation/CoreFoundation.h>
    
    typedef void(^runloopBlock)();
    
    #define IDENTIFIER @"ZLIos"
    #define MAINWIDTH [UIScreen mainScreen].bounds.size.width
    #define MAXIMGCOUNT 50 //设定界面能加载的最大图片数
    @interface ViewController ()<UITableViewDelegate,UITableViewDataSource>
    @property (nonatomic ,strong) UITableView * tableView;
    @property (nonatomic ,strong) NSMutableArray * tasks;
    
    @end
    
    @implementation ViewController
    
    - (NSMutableArray *)tasks {
        if (!_tasks) {
            _tasks = [NSMutableArray array];
        }
        return _tasks;
    }
    
    #pragma mark initView
    - (void)initTableView {
        
        UITableView *tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStyleGrouped];
        tableView.delegate = self;
        tableView.dataSource = self;
        [self.view addSubview:tableView];
        self.tableView = tableView;
    }
    
    - (void)initTimerForRunloop {
        
        //每次计时都是一次runloop,设定定时器的时间,就可以设定runloop循环的速度
        [NSTimer scheduledTimerWithTimeInterval:0.01f target:self selector:@selector(runTimerMethod) userInfo:nil repeats:YES];
    }
    //timer执行事件
    - (void)runTimerMethod {
        
    }
    #pragma mark VIEWLOAD
    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view, typically from a nib.
        
        [self initTimerForRunloop];
        [self initTableView];
        
        [self craeteRunloopForOberServer];
    }
    
    #pragma TableViewDelegate
    - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
        return 120.f;
    }
    
    - (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
        return 0.01f;
    }
    - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
        return 0.01f;
    }
    #pragma mark TableViewDataSource
    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
        return 100;
    }
    - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
        return 1;
    }
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:IDENTIFIER];
        if (!cell) {
            cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:IDENTIFIER];
        }
        
        //清理视图,释放内存,防止复用问题
        for (UIView *view in cell.contentView.subviews) {
            [view removeFromSuperview];
        }
        
        //将任务加入block中,放入任务数组,等待runloop循环执行
        __weak ViewController *vc = self;
        [self addBlockToArr:^{
            [vc cellAddImageViewOne:cell];
        }];
        [self addBlockToArr:^{
            [vc cellAddImageViewTwo:cell];
        }];
        [self addBlockToArr:^{
            [vc cellAddImageViewThree:cell];
        }];
        [self addBlockToArr:^{
            [vc cellAddImageViewFour:cell];
        }];
        return cell;
    }
    
    #pragma mark 加载图片
    - (void)addBlockToArr:(runloopBlock)block {
      
     
        [self.tasks addObject:block];
        if (self.tasks.count > MAXIMGCOUNT) {
            [self.tasks removeObjectAtIndex:0];
        }
    }
    
    - (void)cellAddImageViewOne:(UITableViewCell *)cell {
        UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(5, 10, (MAINWIDTH - 5 * 5) / 4, 100)];
        NSString *imgPath = [[NSBundle mainBundle] pathForResource:@"timg" ofType:@"jpeg"];
        UIImage *image = [UIImage imageWithContentsOfFile:imgPath];//图片会被多次调用,节省内存
        imageView.contentMode = UIViewContentModeScaleAspectFit;
    
        imageView.image = image;
        
        [cell.contentView addSubview:imageView];
    }
    - (void)cellAddImageViewTwo:(UITableViewCell *)cell {
        UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(5 + (MAINWIDTH - 5 * 4) / 4 * 1, 10, (MAINWIDTH - 5 * 5) / 4, 100)];
        NSString *imgPath = [[NSBundle mainBundle] pathForResource:@"timg" ofType:@"jpeg"];
        UIImage *image = [UIImage imageWithContentsOfFile:imgPath];//图片会被多次调用,节省内存
        imageView.contentMode = UIViewContentModeScaleAspectFit;
        imageView.image = image;
        
        [cell.contentView addSubview:imageView];
    }
    - (void)cellAddImageViewThree:(UITableViewCell *)cell {
        UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(5 + (MAINWIDTH - 5 * 4) / 4 * 2, 10, (MAINWIDTH - 5 * 5) / 4, 100)];
        NSString *imgPath = [[NSBundle mainBundle] pathForResource:@"timg" ofType:@"jpeg"];
        UIImage *image = [UIImage imageWithContentsOfFile:imgPath];//图片会被多次调用,节省内存
        imageView.contentMode = UIViewContentModeScaleAspectFit;
    
        imageView.image = image;
        
        [cell.contentView addSubview:imageView];
    }
    - (void)cellAddImageViewFour:(UITableViewCell *)cell {
        UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(5 + (MAINWIDTH - 5 * 4) / 4 * 3, 10, (MAINWIDTH - 5 * 5) / 4, 100)];
        NSString *imgPath = [[NSBundle mainBundle] pathForResource:@"timg" ofType:@"jpeg"];
        UIImage *image = [UIImage imageWithContentsOfFile:imgPath];//图片会被多次调用,节省内存
        imageView.contentMode = UIViewContentModeScaleAspectFit;
    
        imageView.image = image;
        
        [cell.contentView addSubview:imageView];
    }
    
    #pragma mark 开启多次runloop渲染图片
    //回调
    static void callBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){
        ViewController *vc = (__bridge ViewController *)info;
        if (vc.tasks.count == 0) {
            return;
        }
        runloopBlock task = vc.tasks.firstObject;
        task();
        [vc.tasks removeObjectAtIndex:0];
        
    }
    
    - (void)craeteRunloopForOberServer {
        //获取当前runloop
        CFRunLoopRef runloop = CFRunLoopGetCurrent();
    //    得到上下文
        CFRunLoopObserverContext context = {
            0,
            (__bridge void *)self,//C取用OC对象,需要桥接
            &CFRetain,
            &CFRelease,
            NULL
        };
        
        //创建观察者 kCFRunLoopBeforeWaiting 执行完任务,进入等待之前
        CFRunLoopObserverRef observerRef = CFRunLoopObserverCreate(NULL,kCFRunLoopBeforeWaiting , YES, 0, &callBack, &context);
        
        //建立监听,kCFRunLoopDefaultMode 当滑动的时候进入ui模式,runloop不会再次循环渲染
        //kCFRunLoopCommonModes让界面在滑动的同时渲染图片
        CFRunLoopAddObserver(runloop, observerRef, kCFRunLoopCommonModes);
        
        CFRelease(observerRef);
        
    }
    
    
    - (void)didReceiveMemoryWarning {
        [super didReceiveMemoryWarning];
        // Dispose of any resources that can be recreated.
    }
    
    @end

      这里,runloop的使用采用了CFRunloop这个库,用c语言实现对runloop的状态监听。这里C语言的内容其实可以总结为这几步:1.获取到当前的runloop 2.创建context,即上下文 3.创建观察者 4.加入监听 5.释放C语言对象(create后需要释放,C语言未加入ARC),这样就实现了在主线程上对大量图片渲染,并且避免卡顿情况。

      

     

  • 相关阅读:
    mysql基础以优化
    Mysql建立索引基础
    Mysql(1)
    SVN学习总结
    Github
    Java Eclipse断点调试
    Java设计模式图文详解
    代理模式
    Java——动态代理技术
    Spring基本概念理解
  • 原文地址:https://www.cnblogs.com/zhulilove/p/7819685.html
Copyright © 2011-2022 走看看