ios开发中的runloop

来源:互联网 发布:win10提速 优化 编辑:程序博客网 时间:2024/05/22 11:31

</pre>一直觉得ios中的runloop特别重要,想要理解一下,然后在开发过程中希望能够用runloop在某些地方写些跟优雅的代码。<p></p><p>在网上看了些资料,自己总结如下吧!</p><p>很早以前,我研究过windows编程,其中就认识到原来应用可以一直处于运行状态是因为里边有一个while死循环,让应用根本没办法从main入口函数中退出!真是人才,也很容易理解这一点吧!而这样的情况放在ios中就是我们接下来要讲的runloop。</p><p>runloop的作用:</p><p>1.保持程序不会退出</p><p style="text-align:justify">2.程序不退出,就可以随时处理app中的各种事件,包括人为的事件,定时器事件,selector事件等等</p><p style="text-align:justify">3.节省cup资源,提高程序的性能,有事情做的时候就唤醒,没事情做的时候就睡眠状态</p><p style="text-align:justify"></p><pre name="code" class="objc">int main(int argc, char * argv[]) {  @autoreleasepool {      return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));  }}
其中UIApplicationMain这个函数就有一个Runloop,这个Runloop就是主线程中的Runloop,所以主线程才一直不会退出,所以应用也不会结束.


Foundation框架中的NSRunLoop与Core Foundation中的CFRunLoop


RunLoop与线程有直接的非常不同寻常的关系,形影不离

1.没一条线程都有一个唯一的与之对应的RunLoop对象

2.主线程的RunLoop是由系统自己创建的,然后子线程的RunLoop对象我们如果要使用就必须自己去创建

3.一个线程结束了,那么这个线程所对应的RunLoop对象就会销毁


下面来分析下RunLoop的结构


一个RunLoop中可以拥有多个不同的Mode,但是RunLoop在同一时间运行只能去使用其中某一个Mode,如果要切换Mode,必须先退出之前的那个Mode,才能进入新的Mode中。

这里所说的Mode,其实就是指RunLoop的不同的运行模式,它有五种运行模式,如下
1.NSDefaultRunLoopMode   主线程的RunLoop就是在这种模式下运行

2.UITrackingRunLoopMode   界面跟踪模式,当用户与界面交互的时候会在这种模式下运行RunLoop

3.NSRunLoopCommonModes  模式占位,上边的两种模式都可以运行RunLoop

4.UIInitializationRunLoopMode  程序启动时处于这个模式下,程序一点启动完毕就不会在这个模式下了

5.GSEventReceiveRunLoopMode  接受系统事件的内部模式


每个CFRunLoopModeRef中又包含多个Source,Timer,Observer,包含的这三个东东,其实就是去触发RunLoop唤醒不死线程的作用(Observer好像不是这个作用,我暂且就这么理解吧!),下边我们来一一讲解:

CFRunLoopTimerRef:

屁话不多说,其实就是个NSTimer

NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];<span style="font-family: Arial, Helvetica, sans-serif;">  </span>
<span style="font-family: Arial, Helvetica, sans-serif;">//将创建的timer加入到DefaultRunLoopMode中,而这个timer在其他模式,比如TrackingRunLoopMode下会停止工作</span>
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
</pre><pre name="code" class="objc">
  NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];  // 拖拽UI界面时出发定时器,在默认模式(NSDefaultRunLoopMode)下不工作  [[NSRunLoop mainRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
  NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];  // NSRunLoopCommonModes仅仅是标记NSTimer在两种模式(UITrackingRunLoopMode/NSDefaultRunLoopMode)下都能运行,但一个RunLoop中同一时间内只能运行一种模式  [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
  // 默认已经添加到主线程中RunLoop(mainRunLoop)中(Mode:NSDefaultRunLoopMode)  [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
</pre>看过别人写的简书的以为作者<a target=_blank target="_blank" class="author-name blue-link" href="http://www.jianshu.com/users/244aa1f48d1c" style="color:rgb(64,148,199); text-decoration:none; margin:0px 5px; font-family:'lucida grande','lucida sans unicode',lucida,helvetica,'Hiragino Sans GB','Microsoft YaHei','WenQuanYi Micro Hei',sans-serif; line-height:20px">YotrolZ </a>写过的一篇文章中讲到:<p></p><p style="text-align:justify">GCD中的定时器跟NSTimer计时器是不一样的,GCD定时器不受Mode的yin'xiang</p><p style="text-align:justify"></p><p style="text-align: justify;"></p><pre name="code" class="objc">/** 定时器对象 */@property (nonatomic, strong)dispatch_source_t timer; // 需要一个强引用
    NSLog(@"开始");    // 获取队并发队列,定时器的回调函数将会在子线程中执行    // dispatch_queue_t queue = dispatch_get_global_queue(0, 0);    // 获取主队列,定时器的回调函数将会在子线程中执行    dispatch_queue_t queue = dispatch_get_main_queue();    self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);    // 该时间表示从现在开始推迟两秒    dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC);    // 设置定时器的开始时间,间隔时间    dispatch_source_set_timer(self.timer, start, 1 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);    dispatch_source_set_event_handler(self.timer, ^{        NSLog(@"------%@", [NSThread currentThread]);    });    dispatch_resume(self.timer);/* 参数说明:// 设置定时器的一些属性    dispatch_source_set_timer(dispatch_source_t source, // 定时器对象                              dispatch_time_t start, // 定时器开始执行的时间                              uint64_t interval, // 定时器的间隔时间                              uint64_t leeway // 定时器的精度                              );*/


CFRunLoopSourceRef

1.Port-based Sources : 内核相关

2.Custom Input Sources : 与自定义Sources相关

3.Cocoa Perform Selector Sources : 与self performSelector方法相关

4.Source0 : 非基于port

5. Source1 : 基于port


CFRunLoopObserverRef:

CFRunLoopObserverRef是RunLoop的观察者,可以通过CFRunLoopObserverRef来监听RunLoop的状态改变.

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {          kCFRunLoopEntry = (1UL << 0),         // 状态值:1,表示进入RunLoop          kCFRunLoopBeforeTimers = (1UL << 1),  // 状态值:2,表示即将处理NSTimer          kCFRunLoopBeforeSources = (1UL << 2), // 状态值:4,表示即将处理Sources          kCFRunLoopBeforeWaiting = (1UL << 5), // 状态值:32,表示即将休眠          kCFRunLoopAfterWaiting = (1UL << 6),  // 状态值:64,表示从休眠中唤醒          kCFRunLoopExit = (1UL << 7),          // 状态值:128,表示退出RunLoop          kCFRunLoopAllActivities = 0x0FFFFFFFU // 表示监听上面所有的状态      };

那么如何监听RunLoop的状态啦?

1.先创建CFRunLoopObserverRef

// 第一个参数用于分配该observer对象的内存// 第二个参数用以设置该observer所要关注的的事件,详见回调函数myRunLoopObserver中注释// 第三个参数用于标识该observer是在第一次进入run loop时执行还是每次进入run loop处理时均执行// 第四个参数用于设置该observer的优先级,一般为0// 第五个参数用于设置该observer的回调函数// 第六个参数observer的运行状态   CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {      NSLog(@"----监听到RunLoop状态发生改变---%zd", activity);      });
2.将观察者添加到对应的RunLoop上

CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
3.C语言中的对象要主动释放

CFRelease(observer);



RunLoop处理逻辑


RunLoop的具体使用

  • 1.图片刷新(假如界面要刷新N多图片(渲染),此时用户拖拽UI控件就会出现卡的效果,我们可以通过RunLoop实现,只在RunLoop默认Mode下下载,也就是拖拽Mode下不刷新图片)

- (void)viewDidLoad {  [super viewDidLoad];  // 只在NSDefaultRunLoopMode下执行(刷新图片)  [self.myImageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"0"] afterDelay:2.0 inModes:@[NSDefaultRunLoopMode]];    }

2.保证一个线程永远不死
  • 方案一:用一个强引用引用住线程(这种方案是不可行的),原因如下:
  • #import "ViewController.h"#import "YCThread.h"@interface ViewController ()/*思路:用一个强引用线程,当点击屏幕的时候再让他启动,结果是不可行!!!!因为,线程执行完内部的任务后就会自动死亡,你如果用一个强引用引用这个线程,即使内存还在,但是该线程也已经处于死亡状态(线程状态),是不能再次启动的,如果再次启动一个死亡状态的线程,就会报错--reason: '*** -[YCThread start]: attempt(视图) to start the thread again'*//** 线程对象 */@property (nonatomic, strong)YCThread *thread; // 强引用@end@implementation ViewController- (void)viewDidLoad {    [super viewDidLoad];    // 创建子线程    self.thread = [[YCThread alloc] initWithTarget:self selector:@selector(run) object:nil];    // 启动子线程    [self.thread start];}- (void)run {    NSLog(@"----------");}- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {    // 点击屏幕再次启动线程    [self.thread start];}@end
    • 方案二:(死循环+RunLoop),不建议此做法,不是太好
    • #import "ViewController.h"@interface ViewController ()/** 线程对象 */@property (nonatomic, strong)NSThread *thread;@end@implementation ViewController- (void)viewDidLoad {    [super viewDidLoad];    // 创建子线程    self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];    [self.thread start];}- (void)run {    NSLog(@"run--%@", [NSThread currentThread]);    // 利用死循环(不建议此做法)    while (1) {        [[NSRunLoop currentRunLoop] run];    }}- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {    [self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:YES modes:@[NSDefaultRunLoopMode]];}- (void)test {    NSLog(@"test--%@", [NSThread currentThread]);}@end
      方案三:(子线程中加入RunLoop+RunLoop源)建议采用此方案
      #import "ViewController.h"/*思路:为了保证线程不死,我们考虑在子线程中加入RunLoop,    但是由于RunLoop中没有没有源,就会自动退出RunLoop,    所以我们要为子线程添加一个RunLoop,    并且为这个RunLoop添加源(保证RunLoop不退出)*/@interface ViewController ()/** 线程对象 */@property (nonatomic, strong)NSThread *thread;@end@implementation ViewController- (void)viewDidLoad {    [super viewDidLoad];    // 创建子线程    self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];    //启动子线程    [self.thread start];}- (void)run {    NSLog(@"run--%@", [NSThread currentThread]); // 子线程    // 给子线程添加一个RunLoop,并且加入源    [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];    // 启动RunLoop    [[NSRunLoop currentRunLoop] run];    NSLog(@"------------"); // RunLoop启动,这句没有执行的机会}- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {    // 在子线程中调用test方法,如果子线程还在就能够调用成功    [self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:YES modes:@[NSDefaultRunLoopMode]];}- (void)test {    NSLog(@"test--%@", [NSThread currentThread]); // 子线程}@end













0 0
原创粉丝点击