美文网首页
iOS多线程Swift GCD 三:Dispatch Sourc

iOS多线程Swift GCD 三:Dispatch Sourc

作者: Trigger_o | 来源:发表于2021-04-20 20:43 被阅读0次

    这部分内容和OC的GCD几乎没区别;但是先比较OC的一大堆宏定义(DISPATCH_TIME_NOW,NSEC_PER_SEC等等),swift明显更易读一些.
    Dispatch Source,调度资源,用于协调处理底层系统事件,在收到指定系统信号的时候,在指定的队列执行任务.
    Dispatch Semaphore,用于线程间通信.

    一:Dispatch Source

    主要有这么几种source:
    Timer Source 定时器
    File System Source 文件状态监听
    Process Source 监听进程状态(ProcessEvent
    Memory Pressure Source 内存警告
    Signal Source 监听UNIX信号
    Mach Port Source 监听Mach Port信号,主要与runloop的应用相关
    Custom Source 自定义数据监听

    GCD也根据这几种source,提供了对应的一些API.

    1.DispatchSourceTimer

    DispatchSourceTimer是个协议,它不需要创建类来遵循,而是通过makeTimerSource方法来返回一个对象.

    class func makeTimerSource(flags: DispatchSource.TimerFlags = [], queue: DispatchQueue? = nil) -> DispatchSourceTimer
    

    flags一般使用[]或者.strict;queue往那个队列添加任务,设置为nil时会添加到一个全局队列;

    func schedule(deadline: DispatchTime, repeating interval: DispatchTimeInterval= .never, leeway: DispatchTimeInterval = .nanoseconds(0))
    

    设置定时器的定时策略:
    deadline是定时器执行第一次前的等待时间,也就是从resume()到第一次执行之间的时间;
    repeating是每次重复的间隔时间
    leeway也是一个延迟,它不作用与第一次执行,表示每次执行可以允许的延迟,用于提升性能,一般不用设置,如果设置了允许的值,则偶尔会出现一点点的偏差


            print("开始:\(Date.init().timeIntervalSince1970)")
            timer = DispatchSource.makeTimerSource(flags: .strict, queue: .main)
            timer?.schedule(deadline: .now() + 3, repeating: 2, leeway: .milliseconds(500))
            timer?.setEventHandler {
                print("\(Date.init().timeIntervalSince1970)")
            }
            timer?.resume()
    

    timer需要被持有,如果作为局部变量会被释放,不能执行
    设置与不设置leeway的区别看下面两次输出


    leeway设置为500毫秒
    不设置leeway
    func start(){
            timer = DispatchSource.makeTimerSource(flags: .strict, queue: .main)
            timer?.schedule(deadline: .now() + 2, repeating: .never)
            timer?.setEventHandler {
                print("\(Date.init().timeIntervalSince1970)")
            }
            timer?.resume()
    }
            //取消
    func end(){
            timer?.cancel()
    }
    

    把repeating设置为.never,就会只执行一次,设置deadline来控制延时,这种设置可随时取消执行的延时操作的方法很方便,相较于Dispatchafter需要额外写一些东西来控制,而timer直接cancel()就行,需要的时候再次调用start就行了;


            print("start:\(Date.init().timeIntervalSince1970)")
            timer = DispatchSource.makeTimerSource(flags: .strict, queue: .global())
            timer?.schedule(deadline: .now() + 2, repeating: .never)
            timer?.setEventHandler {
                print("time:\(Date.init().timeIntervalSince1970) -- \(Thread.current)")
            }
           //timer?.setEventHandler(handler: DispatchWorkItem.init(qos: .userInteractive, flags: [], block: {
           //     print("time:\(Date.init().timeIntervalSince1970) -- \(Thread.current)")
           // }))
            
            for i in 0 ..< 1000000{
                print(i)
            }
    

    上面这样一个例子,如果全局队列,那么可以在start的2秒后输出time;如果改成.main,会在七八秒甚至更久之后才能输出,因为主队列不会开启新线程,只能等当前的函数走完,才执行timer的handler,即便创建workitem自定义优先级也不行;
    因此makeTimerSource时使用主队列需要谨慎,如果后面有耗时操作,那延时就不准了,或者说耗时操作也不要放在主线程.

    在handler里,要么使用weak,要么记得在合适的时候调用cancel,否则会引起循环强引用.

    2. DispatchSourceMemoryPressure

    class func makeMemoryPressureSource(eventMask: DispatchSource.MemoryPressureEvent, queue: DispatchQueue? = nil) -> DispatchSourceMemoryPressure
    

    和timer一样,也是一个协议,通过初始化方法获取对象
    MemoryPressureEvent是内存变换消息,有四种选项:
    .all是所有状态都监听
    .normal是监听回复正常的消息
    .warning是监听内存警告的消息
    .critical是监听内存进入紧急状态的消息
    和DispatchSource.TimerFlags一样,它也定义了一大堆的比较方法.


            let m = DispatchSource.makeMemoryPressureSource(eventMask: .all, queue: .global())
            m.setEventHandler {
                print("内存状态变换")
            }
            m.activate()
    

    只是写这段代码不会有log出现,因为内存没有什么波澜.

    3. DispatchSourceProtocol

    这个协议规定了Dispatch Source的一些行为.前言说的那几种source都遵循这个协议.

    activate()
    

    source初始化时是不活跃的,需要调用activate()来进入工作状态;
    也可以使用resume()来开始


    resume()
    

    恢复工作状态,和下面的方法一一对应,存在计数关系;
    当处在工作状态时,再调用会crash;


    suspend()
    

    暂停source的监听,它和resume()一一对应的,会有一个计数关系,调用几次suspend就得调用resume才能恢复;
    当处在暂停状态时,再调用会crash;
    repeating设置为.never的timer,只会执行一次,使用suspend()和resume()也没有意义


    cancel()
    

    取消source;
    另外activate(),activate(),suspend()状态的source都不能直接释放,会crash,需要先cancel()


    func setEventHandler(qos: DispatchQoS = .unspecified, flags: DispatchWorkItemFlags = [], handler: Self.DispatchSourceHandler?)
    func setEventHandler(handler: DispatchWorkItem)
    

    设置event就是添加workItem的两种方式

    func setCancelHandler(handler: DispatchWorkItem)
    func setCancelHandler(qos: DispatchQoS = .unspecified, flags: DispatchWorkItemFlags = [], handler: Self.DispatchSourceHandler?)
    

    设置调用cancel()的回调任务

    func setRegistrationHandler(handler: DispatchWorkItem)
    func setRegistrationHandler(qos: DispatchQoS = .unspecified, flags: DispatchWorkItemFlags = [], handler: Self.DispatchSourceHandler?)
    

    设置完成初始化source的回调任务,对于timer来说,会走在第一次执行之前

    二:Dispatch Semaphore

    通过wait和signal来进行线程间的数据同步.

            let sem = DispatchSemaphore.init(value: 0)
            for i in 0 ..< 100{
                DispatchQueue.global().async {
                    print("\(i) + \(Thread.current)")
                    let count = sem.signal()
                    print(count)
                }
               sem.wait()
           }
    

    调用signal()信号量为+1,并且返回当前的信号量;
    调用wait()信号量-1,大概信号量为0时,阻塞当前线程,等待信号量>0的时刻;


    image.png

    wait()可以设置时长,如sem.wait(timeout: .now() + 1),和workItem的效果一样,设置一个最迟的等待时间,是如果时间到了任务还没完成,也不再等待,直接返回.
    如果时间到之前任务已经完成,则也是立即返回,
    返回结果是枚举值DispatchTimeoutResult,有两个值.success和.timeout对应两种情况.

    相关文章

      网友评论

          本文标题:iOS多线程Swift GCD 三:Dispatch Sourc

          本文链接:https://www.haomeiwen.com/subject/ywmukltx.html