将来跳槽用iOS DeveloperiOS接下来要研究的知识点

RunLoop小结

2017-12-27  本文已影响67人  倚楼听风雨wing

一、先来几个问题

1.在viewDidLoad中添加3个循环的timer,这3个timer都会访问并且改变一个可变数组,请问会有什么问题吗?
2.如何获取子线程runLoop,如何让一个runLoop跑起来,runLoop和线程之间的关系
3.如何结束一个runLoop
4.如何用runLoop来做点事,让你的程序高效起来

二、为什么要有RunLoop

1.能够让程序一直处于运行状态,随时能够和用户交互,并且在没有交互的时候休眠,以节省性能
2.调用解耦(Message Queue) 把用户对界面的操作消息放到消息队列中,然后通过运行循环到消息队列中把消息取出来通知对应需要处理的界面操作或者逻辑等。

三、RunLoop in Cocoa

Foundation
  NSRunLoop [不是线程安全]
Core Foundation (跨平台)
  CFRunLoop [线程安全]

NSRunLoop纯粹是对CFRunLoop的一层面向对象的封装,在使用的时候需要考虑线程安全问题
在我们的日常开发中都有哪些是用到了RunLoop的呢,例如:
NSTimer UIEvent Autorelease
NSObject (NSDelayedPerfroming)
CADisplayLink CATransition CAAnimation
dispatch_get_main_queue() 等等...
或者以前的AFNetworking中的NSURLConnection的代理的回调就是在一个置顶线程的RunLoop中处理的

四、RunLoop机制

runLoop.png

1.Souce是RunLoop的数据源抽象类(Protocol),RunLoop定义了两个版本的Souce:

2.AutoreleasePool对对象的释放也是在RunLoop的两次Sleep之间进行Pop和Push将这次循环中产生的Autorelease对象释放
3.RunLoop接收来自两种不同类型源的事件:

4.RunLoop在同一时间只能并且必须在一种特定的Mode下跑,如果需要更换Mode则需要停止当前的loop然后在指定的Mode下开启loop。Mode是iOS滑动顺畅的关键,有以下几种:

更多苹果内部:Mode:http://iphonedevwiki.net/index.php/CFRunLoop
平常我们把timer添加到RunLoop的defaultMode中发现滚动scrollView的时候timer就不工作了就是因为这个时候的mode切换成UITrackingRunLoopMode了。如果想要继续工作的话,只需要把timer添加到commonModes中即可,这个时候不论应用程序在什么mode的情况下都会执行timer。从这里我们也可以看出,如果想要保持用户滑动的流畅性,除了把一些耗时操作放到子线程去做还可以把一些必须在主线程执行的操作放到defaultMode中去执行。例如:

private func test() {
  let img = UIImage(named: "xxx.png")
  perform(#selector(setImg(_:)), with: img) // 默认就是defaultMode
}

@objc private func setImg(_ img: UIImage) {
   imageView.image = img
}

5.事件运行循环顺序

1.通知观察者已经输入了运行循环。
2.通知观察者,任何准备好的计时器即将开火。
3.通知观察者,任何不是基于端口的输入源都将被触发。
4.触发任何可以触发的非基于端口的输入源。
5.如果基于端口的输入源已准备就绪并正在等待触发,请立即处理该事件。转到第9步。
6.通知观察者线程即将睡眠。
7.使线程进入睡眠状态,直到发生以下事件之一:
[7.1]一个事件到达一个基于端口的输入源。
[7.2]计时器启动。
[7.3]为运行循环设置的超时值已过期。
[7.4]运行循环明确地被唤醒。
8.通知观察者线程刚刚醒来。
9.处理未决事件。
[9.1]如果用户定义的定时器触发,则处理定时器事件并重新启动循环。转到第2步。
[9.2]如果输入源被触发,则交付事件。
[9.3]如果运行循环被明确唤醒但尚未超时,请重新启动循环。转到第2步。
10.通知观察者运行循环已经退出。

基于这些指定的顺序苹果给出了特定的api可以检测到runloop的所处的运行状态

/* Run Loop Observer Activities */
public struct CFRunLoopActivity : OptionSet {

    public init(rawValue: CFOptionFlags)
    // 1.进入runloop
    public static var entry: CFRunLoopActivity { get }
    // 2.即将处理定时源
    public static var beforeTimers: CFRunLoopActivity { get }
     // 3.即将处理输入源
    public static var beforeSources: CFRunLoopActivity { get }
     // 4.即将进入睡眠状态
    public static var beforeWaiting: CFRunLoopActivity { get }
     // 5.被唤醒
    public static var afterWaiting: CFRunLoopActivity { get }
     // 6.退出runloop
    public static var exit: CFRunLoopActivity { get }
     // 7.以上所有状态集合
    public static var allActivities: CFRunLoopActivity { get }
}

五、Coding

1.主线程的RunLoop是默认开启的,如果是我们自己创建的子线程,默认是没有开启RunLoop的,只有我们去获取的时候才会以懒加载的形式创建。注意RunLoop需要在线程内部获取,一旦开启线程将不会被释放。这里可能有人会问为什么下面要用while循环的方式不停的调用run方法,这是苹果推荐的一种RunLoop开启方式,是为了方便我们停止RunLoop。如果只是调runloop.run()其实内部也是一个while循环在不停的调用runloop.run(mode: .defaultRunLoopMode, before: Date.distantFuture)

let thread = Thread {
    let runloop = RunLoop.current
    runloop.add(Port(), forMode: .defaultRunLoopMode)
    self.rl = CFRunLoopGetCurrent()
    while (self.isRun) {
        runloop.run(mode: .defaultRunLoopMode, before: Date.distantFuture)
    }
    
    runloop.run()
}
thread.start()

结束一个正在运行的RunLoop

CFRunLoopStop(rl)
thread?.cancel()
isRun = false

2.给RunLoop添加观察者

let rl = CFRunLoopGetMain()
let ob = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, CFRunLoopActivity.allActivities.rawValue, true, 0) { (observer, activity) in
    if activity == .allActivities {
        print("allActivities")
    } else if activity == .beforeTimers {
        print("beforeTimers")
    } else if activity == .beforeSources {
        print("beforeSources")
    } else if activity == .beforeWaiting {
        print("beforeWaiting")
    } else if activity == .afterWaiting {
        print("afterWaiting")
    } else if activity == .entry {
        print("entry")
    } else if activity == .exit {
        print("exit")
    }
}
CFRunLoopAddObserver(rl, ob, .defaultMode)
observer = ob

一般来说我们只需要观察beforeWaiting在一次RunLoop结束的时候做点事情就可以了
3.移除RunLoop中的observer,这里多说一点,在swiftCore Foundating中的内容也不用我们手动释放了,系统帮我们处理了,所以我们不用主动释放observer

 CFRunLoopRemoveObserver(CFRunLoopGetMain(), observer, .defaultMode)

完整代码:

import UIKit

class ViewController: UIViewController {

    private var observer: CFRunLoopObserver?
    private var thread: WYThread?
    private var rl: CFRunLoop!
    private var isRun = true
    
    private func startRunLoop() {
        thread = WYThread(block: { [unowned self] in
            let runloop = RunLoop.current
            runloop.add(Port(), forMode: .defaultRunLoopMode)
            self.rl = CFRunLoopGetCurrent()
            while (self.isRun) {
                runloop.run(mode: .defaultRunLoopMode, before: Date.distantFuture)
            }
            
            runloop.run()
        })
        
        thread?.name = "wy_test"
        thread?.start()
    }
    
    private func stopRunLoop() {
        CFRunLoopStop(rl)
        thread?.cancel()
        isRun = false
    }
    
    private func addRunLoopObserver() {
        let rl = CFRunLoopGetMain()
        let ob = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, CFRunLoopActivity.allActivities.rawValue, true, 0) { (observer, activity) in
            if activity == .allActivities {
                print("allActivities")
            } else if activity == .beforeTimers {
                print("beforeTimers")
            } else if activity == .beforeSources {
                print("beforeSources")
            } else if activity == .beforeWaiting {
                print("beforeWaiting")
            } else if activity == .afterWaiting {
                print("afterWaiting")
            } else if activity == .entry {
                print("entry")
            } else if activity == .exit {
                print("exit")
            }
        }
        CFRunLoopAddObserver(rl, ob, .defaultMode)
        observer = ob
    }

    private func removeRunLoopObserver() {
        CFRunLoopRemoveObserver(CFRunLoopGetMain(), observer, .defaultMode)
    }
}

上一篇 下一篇

猜你喜欢

热点阅读