iOS多线程Swift GCD 三:Dispatch Source、Semaphore

这部分内容和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的区别看下面两次输出


iOS多线程Swift GCD 三:Dispatch Source、Semaphore_第1张图片
leeway设置为500毫秒

iOS多线程Swift GCD 三:Dispatch Source、Semaphore_第2张图片
不设置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的时刻;


iOS多线程Swift GCD 三:Dispatch Source、Semaphore_第3张图片
image.png

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

你可能感兴趣的:(iOS多线程Swift GCD 三:Dispatch Source、Semaphore)