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

p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo }
span.s1 { }

    [[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),这样就实现了在主线程上对大量图片渲染,并且避免卡顿情况。

  

p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #703daa }
span.s1 { }
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo }
span.s1 { }
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #703daa }
span.s1 { }

时间: 2024-11-12 23:57:11

iOS学习——RUNLOOP、NSTimer的相关文章

iOS中RunLoop机制浅探

iOS中RunLoop机制浅探 一.浅识RunLoop RunLoop这个家伙在iOS开发中,我们一直在用,却从未注意过他,甚至都不从见过他的面孔,那个这个神秘的家伙究竟是做什么的?首先,我们先来观察一下我们的程序运行机制. 无论是面向对象的语言或是面向过程的语言,代码的执行终究是面向过程的.线程也一样,一个线程从开始代码执行,到结束代码销毁.就像HELLO WORLD程序,打印出字符串后程序就结束了,那么,我们的app是如何实现如下这样的机制的呢:app从运行开始一直处于待命状态,接收到类似点

iOS 中的 NSTimer

iOS 中的 NSTimer NSTimer fire 我们先用 NSTimer 来做个简单的计时器,每隔5秒钟在控制台输出 Fire .比较想当然的做法是这样的: @interface DetailViewController () @property (nonatomic, weak) NSTimer *timer; @end @implementation DetailViewController - (IBAction)fireButtonPressed:(id)sender { _ti

iOS学习笔记-精华整理

iOS学习笔记总结整理 一.内存管理情况 1- autorelease,当用户的代码在持续运行时,自动释放池是不会被销毁的,这段时间内用户可以安全地使用自动释放的对象.当用户的代码运行告一段 落,开始等待用户的操作,自动释放池就会被释放掉(调用dealloc),池中的对象都会收到一个release,有可能会因此被销毁. 2-成员属性:     readonly:不指定readonly,默认合成getter和setter方法.外界毫不关心的成员,则不要设置任何属性,这样封装能增加代码的独立性和安全

#在蓝懿iOS学习的日子#

#在蓝懿iOS学习的日子#昨天我们学习了让uiimaag,学习了如何的插入图片,学习如何让它上下左右移动,其实就是加入按钮buttonon来控制图片的方向,还学习让图片在一定的范围内来回的移动,最后还制作了一个简易的游戏,我们都称之为简易的反射器,在下方设立一个按钮,发射一个图片,打击上方左右移动的image,为胜利,并以打中一次,来进行计分,难点就是两个图片的碰撞,设立的放h是这样的: //设置luobo与tu1障碍物的碰撞 //frame是指图片的矩形,进行碰撞 if (CGRectInte

IOS学习之蓝牙4.0

转载请注明出处 作者:小马 IOS学习也一段时间了,该上点干货了.前段时间研究了一下IOS蓝牙通讯相关的东西,把研究的一个成果给大家分享一下. 一 项目背景 简单介绍一下做的东西,设备是一个金融刷卡器,通过蓝牙与iphone手机通讯.手机端的app通过发送不同的指令(通过蓝牙)控制刷卡器执行一些动作,比如读磁条卡,读金融ic卡等.上几张图容易理解一些:              看了上面几张图,你应该大概了解这是个什么东东了. 二 IOS 蓝牙介绍 蓝牙协议本身经历了从1.0到4.0的升级演变,

iOS: 学习笔记, performSelectorOnMainThread

- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait performSelectorOnMainThread:withObject:waitUntilDone: 基于默认模式调用主线程中接收器的方法 Invokes a method of the receiver on the main thread using the default mode. 参数 Par

iOS学习笔记之UITableViewController&amp;UITableView

iOS学习笔记之UITableViewController&UITableView 写在前面 上个月末到现在一直都在忙实验室的事情,与导师讨论之后,发现目前在实验室完成的工作还不足以写成毕业论文,因此需要继续思考新的算法.这是一件挺痛苦的事情,特别是在很难找到与自己研究方向相关的文献的时候.也许网格序列水印这个课题本身的研究意义就是有待考证的.尽管如此,还是要努力的思考下去.由于实验室的原因,iOS的学习进度明显受到影响,加之整理文档本身是一件耗费时间和精力的事情,因此才这么久没有写笔记了. M

iOS 学习资料整理

视频教程(英文) 视频 简介 Developing iOS 7 Apps for iPhone and iPad 斯坦福开放教程之一, 课程主要讲解了一些 iOS 开发工具和 API 以及 iOS SDK 的使用, 属于 iOS 基础视频 iPad and iPhone Application Development 该课程的讲师 Paul Hegarty 是斯坦福大学软件工程学教授, 视频内容讲解得深入, 权威, 深受好评 Advanced iPhone Development - Fall

【资源】IOS学习资料 - 逆天整理 - 精华无密版【最新】【精华】

 入门看视频,提高看书籍,飘升做项目.老练研开源,高手读外文,大牛讲低调~  01.IOS基础 01.iOS开发快速入门教程 http://pan.baidu.com/s/1kT3ScOf 链接: http://pan.baidu.com/s/1kTKheAF 密码: yycm 02.苹果开发零基础入门教程 http://pan.baidu.com/s/1dDfHL77 链接: http://pan.baidu.com/s/1o6iNkIu 密码: nn3a 03.黑马IOS2期基础 http: