RunLoop

讲讲 RunLoop,项目中有用到吗?

1.控制线程生命周期(线程保活)
2.解决NSTimer在滑动时停止工作的问题
3.监控应用卡顿
4.性能优化

RunLoop内部实现逻辑?

01、通知Observers:进入Loop
02、通知Observers:即将处理Timers
03、通知Observers:即将处理Sources
04、处理Blocks
05、处理Source0(可能会再次处理Blocks)
06、如果存在Source1,就跳转到第8步
07、通知Observers:开始休眠(等待消息唤醒)
08、通知Observers:结束休眠(被某个消息唤醒)
01> 处理Timer
02> 处理GCD Async To Main Queue
03> 处理Source1
09、处理Blocks
10、根据前面的执行结果,决定如何操作
01> 回到第02步
02> 退出Loop
11、通知Observers:退出Loop

RunLoop和线程的关系?

1.每条线程都有一个与之相对应的RunLoop对象
2. RunLoop保存在一个全局的Dictionary里面 线程作为key,RunLoop作为value
3.线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建
4. RunLoop会在线程结束的时候销毁
5.主线程的RunLoop已经自动获取(创建),子线程默认没有开启RunLoop

timer 与 RunLoop 的关系?

- RunLoop里面有一个变量_modes 里面存放着_timers
- 如果timer设置为commonModes 会把timer放在RunLoop的变量_commonModeItems里面
- 在RunLoop的运行逻辑里面进行工作的
RunLoop的结构.png

CFRunLoopMode的结构.png

程序中添加每3秒响应一次的NSTimer,当拖动tableview时timer可能无法响应要怎么解决?

创建 timer 把他添加到RunLoop的commonModes中

RunLoop 是怎么响应用户操作的, 具体流程是什么样的?

- 由Source1把系统事件进行捕捉  把事件包装成事件队列EvenetQueue
- EvenetQueue是由Source0处理的

说说RunLoop的几种状态

 kCFRunLoopEntry
 kCFRunLoopBeforeWaiting
 kCFRunLoopBeforeTimers
 kCFRunLoopBeforeSources
 kCFRunLoopAfterWaiting
 kCFRunLoopExit

RunLoop的mode作用是什么?

1.kCFRunLoopDefaultMode(NSDefaultRunLoopMode):App的默认Mode,通常主线程是在这个Mode下运行
2.UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响

RunLoop

在程序运行过程中循环做一些事情

应用范畴

1.定时器(Timer)、PerformSelector
2.GCD Async Main Queue
3.事件响应、手势识别、界面刷新
4.网络请求
5.AutoreleasePool

RunLoop的基本作用

- 保持程序的持续运行
- 处理App中的各种事件(比如触摸事件、定时器事件等)
- 节省CPU资源,提高程序性能:该做事时做事,该休息时休息

RunLoop和线程的关系

1.每条线程都有一个与之相对应的RunLoop对象
2. RunLoop保存在一个全局的Dictionary里面 线程作为key,RunLoop作为value
3.线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建
4.RunLoop会在线程结束的时候销毁
5.主线程的RunLoop已经自动获取(创建),子线程默认没有开启RunLoop
//Foundation
[NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象
[NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象

//Core Foundation
CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象
CFRunLoopGetMain(); // 获得主线程的RunLoop对象
image.png
image.png

CFRunLoopModeRef

CFRunLoopModeRef 代表RunLoop的运行模式
- 一个RunLoop包含若干个Mode  每个Mode又包含若干个Source0/Source1/Timer/Observer
- RunLoop启动时只能选择其中一个Mode 作为currentMode
- 如果需要切换Mode,只能退出当前Loop,再重新选择一个Mode进入
  不同组的Source0/Source1/Timer/Observer能分隔开来,互不影响
- 如果Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出
常见的Mode
1.kCFRunLoopDefaultMode(NSDefaultRunLoopMode):App的默认Mode,通常主线程是在这个Mode下运行
2.UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响

NSRunLoopCommonModes并不是一个真的模式 他只是一个标记
timer能在commonModes数组中存放的模式下工作

RunLoop的Mode包含的内容

- Source0
  a.触摸事件处理
  b.performSelector:onThread:
- Source1
  a.基于Port的线程间通信
  b.系统事件捕捉
- Timers
  a.NSTimer
  b.performSelector:withObject:afterDelay:
- Observers
  a.用于监听RunLoop的状态
  b.UI刷新(BeforeWaiting)
  c.Autorelease pool(BeforeWaiting)

CFRunLoopObserverRef

监听observer
void observerRunLoopActivicity(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
{
    switch (activity) {
        case kCFRunLoopEntry:
            NSLog(@"kCFRunLoopEntry");
            break;
        case kCFRunLoopBeforeWaiting:
            NSLog(@"kCFRunLoopBeforeWaiting");
            break;
        case kCFRunLoopBeforeTimers:
            NSLog(@"kCFRunLoopBeforeTimers");
            break;
        case kCFRunLoopBeforeSources:
            NSLog(@"kCFRunLoopBeforeSources");
            break;
        case kCFRunLoopAfterWaiting:
            NSLog(@"kCFRunLoopAfterWaiting");
            break;
        case kCFRunLoopExit:
            NSLog(@"kCFRunLoopExit");
            break;
        default:
            break;
    }
}
//创建observer
//CFOptionFlags activities 监听什么状态
//Boolean repeats 是否重复
//CFIndex order  顺序
CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities , YES, 0, observerRunLoopActivicity, NULL);
//kCFRunLoopCommonModes 包括 kCFRunLoopDefaultMode  和 UITrackingRunLoopMode
//添加observer到RunLoop中
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
//释放observer
CFRelease(observer);
第二种添加observer
void observerRunLoopActivicity(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
{
    switch (activity) {
        case kCFRunLoopEntry:
            NSLog(@"kCFRunLoopEntry");
            break;
        case kCFRunLoopBeforeWaiting:
            NSLog(@"kCFRunLoopBeforeWaiting");
            break;
        case kCFRunLoopBeforeTimers:
            NSLog(@"kCFRunLoopBeforeTimers");
            break;
        case kCFRunLoopBeforeSources:
            NSLog(@"kCFRunLoopBeforeSources");
            break;
        case kCFRunLoopAfterWaiting:
            NSLog(@"kCFRunLoopAfterWaiting");
            break;
        case kCFRunLoopExit:
            NSLog(@"kCFRunLoopExit");
            break;
        default:
            break;
    }
}

//创建Observer
CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        
});
//添加observer到RunLoop中
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
//释放observer
CFRelease(observer);
运行逻辑.png

RunLoop休眠的实现原理

实现原理.png

while死循环还是要不断的执行代码 这个时候程序没有处于休眠状态

实际用途

1.控制线程生命周期(线程保活)
2.解决NSTimer在滑动时停止工作的问题
3.监控应用卡顿
4.性能优化
线程保活
class ViewController: UIViewController {

    var thread: MJThread?
    override func viewDidLoad() {
        super.viewDidLoad()
        
        thread = MJThread(target: self, selector: #selector(run), object: nil)
        thread?.start()
    }

    //为了保证线程保活
    @objc func run() {
        
        print(#function + "------------", Thread.current)
        //往RunLoop里面添加Source/Timer/Observer 这样RunLoop才不会退出
        RunLoop.current.add(Port(), forMode: .default)
        RunLoop.current.run()
    }
    
    //线程里面真正要做的事情
    @objc func test() {
        print(#function + "------------", Thread.current)
    }
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        
        perform(#selector(test), on: thread!, with: nil, waitUntilDone: false)
    }

}

RunLoop.current.add(Port(), forMode: .default)
RunLoop.current.run()
会开启一个无限的循环且无法停止 专门用于开启一个永不销毁的线程(RunLoop)

会开启一个无限的循环 无法停止
thread = MJThread(block: {
            RunLoop.current.add(Port(), forMode: .default)
            RunLoop.current.run()
            print("end-------" + Thread.current.description)
        })
 thread?.start()

 override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        
        perform(#selector(test), on: thread!, with: nil, waitUntilDone: false)
    }
    
   //线程里面真正要做的事情
    @objc func test() {
        print(#function + "------------", Thread.current)
    }
    deinit {
        print(#function)
        perform(#selector(stop), on: thread!, with: nil, waitUntilDone: false)
        thread = nil
    }
    
    //用于停止子线程的RunLoop
    @objc func stop() {
        CFRunLoopStop(CFRunLoopGetCurrent())
    }
设置一个BOOl类型变量停止RunLoop
thread = MJThread(block: { [weak self] in
            RunLoop.current.add(Port(), forMode: .default)
             while self?.isStop == false && (self != nil) {
                RunLoop.current.run(mode: .common, before: Date.distantFuture)
            }
            print("end-------" + Thread.current.description)
 })
thread?.start()

//线程里面真正要做的事情
    @objc func test() {
        print(#function + "------------", Thread.current)
    }
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        if thread == nil{
           return
        }
        perform(#selector(test), on: thread!, with: nil, waitUntilDone: false)
    }
    
    deinit {
        print(#function)
        perform(#selector(stop), on: thread!, with: nil, waitUntilDone: true)
        thread = nil
    }
    
    //用于停止子线程的RunLoop
    @objc func stop() {
        isStop = true
        CFRunLoopStop(CFRunLoopGetCurrent())
    }

perform(#selector(stop), on: thread!, with: nil, waitUntilDone: false) 当waitUntilDone 设置为false 控制器的销毁跟子线程执行那些代码是同时进行的 就会发生坏内存访问(控制器的内存没了) 所以,要设置为true (代表子线程的代码执行完毕之后,才会继续往下走)
while self?.isStop == false && (self != nil) { RunLoop.current.run(mode: .common, before: Date.distantFuture) } 因为弱指针有可能会空

class MJThread: Thread {

    deinit {
        print(#function)
    }
}

class MJPermenant: NSObject {

    var thread: MJThread?
    var stoped = false
    typealias MJPermenantThreadTask = (()->())
    override init() {
         
        super.init()
        
        thread = MJThread(block: { [weak self] in
            RunLoop.current.add(Port(), forMode: .common)
            while (self != nil) && self?.stoped == false{
                RunLoop.current.run(mode: .default, before: Date.distantFuture)
            }
        })
       
    }
    //MARK: 开启线程
    func run() {
        thread?.start()
    }
    
    //MARK: 结束线程
    func stop() {
        
        if let innerThread = thread {
            perform(#selector(__stop), on: innerThread, with: nil, waitUntilDone: true)
        }
    }
    
    @objc fileprivate func __stop() {
        stoped = true
        CFRunLoopStop(CFRunLoopGetCurrent())
        thread = nil
    }
    
    func executeTaskWithTarget(task: @escaping MJPermenantThreadTask) {
        
        if let innerThread = thread {
            NSObject.perform(#selector(__executeTask), on: innerThread, with: task, waitUntilDone: false)
        }
    }
    
    @objc fileprivate func __executeTask(task: @escaping MJPermenantThreadTask) {
        task()
    }
    
    deinit {
        stop()
    }
}
封装的线程使用C语言的RunLoop
 // 创建上下文(要初始化一下结构体)
CFRunLoopSourceContext context = {0};
            
// 创建source
CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
            
// 往Runloop中添加source
CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
            
// 销毁source
CFRelease(source);
            
// 启动
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, false);

CFRunLoopRunInMode 的 returnAfterSourceHandled,设置为true,代表执行完source后就会退出当前loop CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, true);

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,755评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,369评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,799评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,910评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,096评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,159评论 3 411
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,917评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,360评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,673评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,814评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,509评论 4 334
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,156评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,882评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,123评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,641评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,728评论 2 351

推荐阅读更多精彩内容

  • 转自bireme,原地址:https://blog.ibireme.com/2015/05/18/runloop/...
    乜_啊_阅读 1,355评论 0 5
  • RunLoop 的概念 一般来讲,一个线程一次只能执行一个任务,执行完成后线程就会退出。如果我们需要一个机制,让线...
    Mirsiter_魏阅读 617评论 0 2
  • Runloop是iOS和OSX开发中非常基础的一个概念,从概念开始学习。 RunLoop的概念 -般说,一个线程一...
    小猫仔阅读 985评论 0 1
  • ======================= 前言 RunLoop 是 iOS 和 OSX 开发中非常基础的一个...
    i憬铭阅读 873评论 0 4
  • 转自http://blog.ibireme.com/2015/05/18/runloop 深入理解RunLoop ...
    飘金阅读 976评论 0 4