ios xcode 定时器_GitHub - ZhenKaiJia/Timer: iOS常用计时器包括NSTimer、CADisplayLink、GCD定时器。主要介绍三种定时器的使用,以及全局定时器...

本文介绍了iOS中三种常见的定时器:NSTimer、CADisplayLink和GCD定时器的使用方法,包括初始化、避免循环引用以及添加到RunLoop。同时,文章还讨论了它们的优缺点,例如CADisplayLink和NSTimer依赖RunLoop可能导致时间不准,而GCD定时器更准时。最后,提供了一个全局定时器的封装实现,包括接口设计和多线程安全性。
摘要由CSDN通过智能技术生成

一、介绍

iOS常用计时器包括NSTimer、CADisplayLink、GCD定时器。本次主要介绍三种定时器的使用,以及全局定时器的封装。过程主要涉及知识点定时器、多线程、锁、消息转发。

二、使用

1、Timer

第一种 这种会导致循环引用,因为self强引用timer,timer强引用target,会导致定时器释放失败。

self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(test), userInfo: nil, repeats: true)

第二种 这个方法可以解决循环引用问题,但需要注意timer要加到runloop中才能正常使用。

self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: {[weak self] (timer) in

self?.test()

})

第三种 这个方法可以解决循环引用问题,但需要注意timer要加到runloop中才能正常使用,没有runloop只会执行一次。且需要手动唤醒定时器

self.timer = Timer.init(timeInterval: 1, repeats: true, block: {[weak self] (timer) in

guard let self = self else { return }

self.test()

RunLoop.main.add(timer, forMode: RunLoop.Mode.common)

})

self.timer?.fire()

2、CADisplayLink

初始化方法

self.link = CADisplayLink.init(target: self, selector: #selector(test))

self.link?.preferredFramesPerSecond = 1

self.link?.add(to: RunLoop.current, forMode: .default)

使用CADisplayLink注意会出现和Timer相同的问题。1、循环引用 2、依赖runloop才能正常运行

3、GCD定时器

// 创建定时器

let queue = DispatchQueue.global()

self.gcdTimer = DispatchSource.makeTimerSource(queue: queue)

// self.gcdTimer?.schedule(wallDeadline: DispatchWallTime.now(), repeating: 1)

self.gcdTimer?.schedule(deadline: DispatchTime.now(), repeating: 1)

self.gcdTimer?.setEventHandler { [weak self] in

self?.test()

}

self.gcdTimer?.resume()

使用GCD定时器需要有一个成员变量持有gcdTimer,否则会立刻释放

三、对比结论

Timer和CADisplayLink使用会依赖runloop运行,而runloop同时会承担其他任务导致及时不准时。且使用不当容易引起内存泄漏

GCD直接和系统内核挂钩,且不依赖runloop,使用GCD定时器会更加准时

四、全局定时器封装

接口参数设计

全局定时器可能有多个运行,需要唯一标识

设置定时器的开始时间

设置定时器的间隔时间

设置定时器是否重复执行

设置定时器是否异步执行

回调

class TimerManager: NSObject {

/// 每一个key都对应唯一的一个定时器, 固用字典存储

private var timers = NSMutableDictionary()

/// 因为涉及到多线程同时读写,为了避免出现错误,执行数据变更时需要加锁操作

private let semaphore = DispatchSemaphore.init(value: 1)

static var instance: TimerManager {

struct Static {

static let instance: TimerManager = TimerManager()

}

return Static.instance

}

/// 倒计时消息转发

/// - Parameters:

/// - timerKey: 倒计时key,需要保证唯一

/// - targat: 消息转发

/// - selector: 方法名

/// - start: 开始时间

/// - interval: 间隔时间

/// - repeats: 是否重复

/// - async: 是否异步

func schedule(timerKey: String, targat: NSObject, selector: Selector, start: DispatchTime = .now(), interval: TimeInterval = 1, repeats: Bool = true, async: Bool = true) {

self.schedule(timerKey: timerKey, start: start, interval: interval, repeats: repeats, async: async) { [weak targat] in

/// 检查targat是否能响应selector

if targat?.responds(to: selector) ?? false {

targat?.perform(selector)

}

}

}

/// 倒计时block

/// - Parameters:

/// - timerKey: 倒计时key,需要保证唯一

/// - start: 开始时间

/// - interval: 间隔时间

/// - repeats: 是否重复

/// - async: 是否异步

/// - eventHandle: 回调

func schedule(timerKey: String, start: DispatchTime = .now(), interval: TimeInterval = 1, repeats: Bool = true, async: Bool = true, eventHandle: @escaping (() -> Void)) {

guard !timerKey.isEmpty || start.rawValue <= 0 || interval <= 0 else {

return

}

let timerQueue = async ? DispatchQueue.global() : DispatchQueue.main

let timer = DispatchSource.makeTimerSource(queue: timerQueue)

semaphore.wait()

timers[timerKey] = timer

semaphore.signal()

timer.schedule(deadline: start, repeating: interval)

timer.setEventHandler { [weak self] in

eventHandle()

if !repeats {

self?.cancelTask(timerKey: timerKey)

}

}

timer.resume()

}

/// 取消定时器

/// - Parameter timerKey: 定时器标识

func cancelTask(timerKey: String) {

guard !timerKey.isEmpty else {

return

}

guard let timer = timers[timerKey] as? DispatchSourceTimer else {

return

}

timer.cancel()

semaphore.wait()

timers.removeObject(forKey: timerKey)

semaphore.signal()

}

}

外部调用

/// 消息转发

TimerManager.instance.schedule(timerKey: self.theClassName, targat: self, selector: #selector(test))

/// block回调

TimerManager.instance.schedule(timerKey: self.theClassName) { [weak self] in

self?.test()

}

全局定时器内部没有考虑程序退到后台在回来时间间隔问题,解决方案如下

1、监听通知,对比推出前和进入后的时间差,对数据源进行修改

2、每次处理时间数据的时候保存NSDate(),在下次执行数据处理之前先对比之前保存的NSDate和当前的时间差,然后在进行数据处理。代码如下

@objc func test() {

/// 如果currentCount是逐渐减少的话需要在这里判断 如果小于0则执行销毁定时器的方法

// if currentCount <= 0 {

// TimerManager.instance.cancelTask(timerKey: self.theClassName)

// }

if CacheManager.instance.hasCachedValue(with: self.theClassName) {

if let cacheDate = CacheManager.instance.valueWithCache(key: self.theClassName) as? NSDate {

if cacheDate.timeIntervalSinceNow < -2 {

///因为时间差是负数 如果是倒计时 就加上时间差 反之则减去时间差

self.currentCount -= Int(cacheDate.timeIntervalSinceNow) + 1

}

}

}

CacheManager.instance.cacheData(NSDate(), withKey: self.theClassName)

currentCount += 1

print(currentCount)

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值