RunLoop

  

什么是RunLoop?

从字面意思看,

运行循环

跑圈
 
RunLoop基本作用:
保持程序的持续运行
处理App中的各种事件(比如触摸事件、定时器事件、Selector事件)
节省CPU资源,提高程序性能:该做事时做事,该休息时休息
 
iOS中有2套API来访问和使用RunLoop
Foundation
NSRunLoop
 
Core Foundation
CFRunLoopRef

NSRunLoop和CFRunLoopRef都代表着RunLoop对象

NSRunLoop是基于CFRunLoopRef的一层OC包装,所以要了解RunLoop内部结构,需要多研究CFRunLoopRef层面的API(Core Foundation层面)

线程与RunLoop:

每条线程都有唯一的一个与之对应的Runloop对象

主线程的RunLoop默认是开启的,子线程需要主动开启.

RunLoop在第一次获取的时候被创建,在线程结束时销毁.

获取RunLoop对象:

Foundation框架:

[NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象

[NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象

Core Foundation框架:

CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象

CFRunLoopGetMain(); // 获得主线程的RunLoop对象

RunLoop 相关类:

Core Foundation中关于RunLoop的5个类
CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef                           
CFRunLoopTimerRef
CFRunLoopObserverRef

CFRunLoopModeRef:

CFRunLoopModeRef代表RunLoop的运行模式

一个 RunLoop 包含若干个 Mode,每个Mode又包含若干个Source/Timer/Observer
 
每次RunLoop启动时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode
 
如果需要切换Mode,只能退出Loop,再重新指定一个Mode进入
 
这样做主要是为了分隔开不同组的Source/Timer/Observer,让其互不影响
 
系统默认注册了5个Mode:
kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用
GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到
kCFRunLoopCommonModes: 这是一个占位用的Mode,不是一种真正的Mode
 
 
CFRunLoopSourceRef是事件源(输入源)
 
以前的分法
Port-Based Sources  
Custom Input Sources
Cocoa Perform Selector Sources  (self performSelector:<#(nonnull SEL)#> withObject:<#(nullable id)#> afterDelay:<#(NSTimeInterval)#> inModes:<#(nonnull NSArray<NSString *> *)#>)  PerformSelector 就是Cocoa Perform Selector Sources.
 
现在的分法
Source0:非基于Port的
Source1:基于Port的  (Source1接收的事件最终是交给Source0去执行的)
 
CFRunLoopTimerRef是基于时间的触发器
基本上说的就是NSTimer
-(void)test
{  
    NSTimer * time = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(timer) userInfo:nil repeats:YES];

    [[NSRunLoop currentRunLoop]addTimer:time forMode:NSDefaultRunLoopMode];

}


-(void)test1
{
    
    //scheduledTimerWithTimeInterval 默认将NSTimer添加到RunLoop中,并且是默认模式
    NSTimer * time = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(timer) userInfo:nil repeats:YES];
    
    //将NSTimer添加到RunLoop中.NSRunLoopCommonModes包含:UITrackingRunLoopMode和kCFRunLoopDefaultMode , 但注意:runloop可以包含多种模式, 但一次只能执行一种模式
    [[NSRunLoop currentRunLoop]addTimer:time forMode:NSRunLoopCommonModes];
    
}


-(void)test2
{
    CADisplayLink * link = [CADisplayLink displayLinkWithTarget:self selector:@selector(timer)];
    
    link.frameInterval = 60;
    
    //CADisplayLink 需要主动添加到RunLoop中.
    [link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
 
}

注意:当设置了Runloop的模式后, 只能在对应的模式下,定时器才能起作用.

CFRunLoopObserverRef:

CFRunLoopObserverRef是观察者,能够监听RunLoop的状态改变

可以监听的时间点有以下几个:
    //创建RunLoop观察者
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopBeforeSources, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        
        NSLog(@"%zd" , activity);
        
    });
    
    //添加监听者
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopCommonModes);
    
    //由于是C语言创建出来的监听者, 需要release
    CFRelease(observer);

我么可以通过给RunLoop添加监听者, 去做一些拦截操作,.

RunLoop处理逻辑-官方版:

将输入源或定时源添加到RunLoop后, runloop就不的循环处理事件源的一些操作, 若没有事件源输入, runloop就进入休眠状态.

RunLoop处理逻辑-官方版

RunLoop处理逻辑-网友整理版

在第一步之前,可以优化一下:判断Runloop模式中是否有事件源. 有就通知Observer ,即将计入Runloop,否则直接退出.
 
 
RunLoop应用:
1.NSTimer:上面已经介绍过了.
 
2.ImageView显示:(加入有一个需要,在滚动view时,加载图片显示图片, 此时滚动View时,界面就会很卡,此时就可以运用RunLoop,延迟显示图片)
 我们可以这么做:
我关联了ImageView
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { [self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"test.png"] afterDelay:3.0 inModes:@[NSDefaultRunLoopMode]]; }

当滚动View时,Runloop处于UITrackingRunLoopMode 模式, 此时performSelector 这个scource 源是Runloop是不会执行的,虽然runloop可以有多种模式,但一次只能在一个模式下运行,执行退出当前模式,才能执行其他模式下的Scource.

3.常驻线程:(就是让一个线程不死, 一直等待响应某些事情, 获取希望能让一条线程,在后台去监测扫描用户的沙盒里的文件,这个时候需要一直使用线程等等).
注意:一定不能设置一个Strong 的成员属性来引用线程, 这是不允许的,虽然能保证线程不被dealloc,但当线程被创建后,执行完任务后线程已经进入消亡状态, 是无法再次开启, 更不能执行其他任务.(通过一个死循环,去操作,让这个线程不死)
 
方法1:(不推荐,消耗性能)
@interface ViewController () @property(nonatomic ,strong)NSThread * thread; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; YWThread * thread = [[YWThread alloc]initWithTarget:self selector:@selector(run) object:nil]; self.thread = thread; [thread start]; } -(void)run { while (1) { //如果没有这个死循环, Runloop创建后就关闭. 这个死循环让当前线程的Runloop不断的开启关闭.(这样也可以让一个线程不死, 一直接收处理Source) [[NSRunLoop currentRunLoop]run]; } NSLog(@"-------------"); } -(void)test1 { NSLog(@"-----test1-----"); } -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { [self performSelector:@selector(test1) onThread:self.thread withObject:nil waitUntilDone:NO]; }
 
方法2: 直接给Runloop添加事件源

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    
    YWThread * thread = [[YWThread alloc]initWithTarget:self selector:@selector(run) object:nil];
    
    [thread start];
}
    

-(void)run
{
    这样就可以让一个线程不死(runLoop一直循环,线程就不会被dealloc)
    [[NSRunLoop currentRunLoop]addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop]run];
    
    NSLog(@"---run-----");
    
}
 
原文地址:https://www.cnblogs.com/yuwei0911/p/5271081.html