Temporizador en iOS

¡Acostúmbrate a escribir juntos! Este es el décimo día de mi participación en el "Nuggets Daily New Plan · April Update Challenge", haz clic para ver los detalles del evento .

Hay tres tipos de temporizadores que usamos comúnmente en iOS: Timer, CADisplayLink, DispatchSourceTimer

Temporizador

Timer es nuestro temporizador más común. Cuando se crea el temporizador (no programado, debe agregarse al runloop manualmente), el temporizador se activará automáticamente después de los t segundos especificados durante la inicialización. Dos de los problemas que a menudo tenemos a su alrededor son problemas de precisión y referencia circular .

Acerca de la precisión

Si el Timer se agrega al runloop principal, es fácil bloquear el subproceso porque el subproceso principal está ocupado con varias operaciones de interfaz de usuario u operaciones complejas, lo que retrasa la ejecución de NSTimer, lo que resulta en una menor precisión.

Si Timer quiere mayor precisión, podemos considerar los siguientes aspectos

  • Elija el modo apropiado

    El temporizador tiene dos modos comunes: defaultRunLoopMode y UITrackingRunLoopMode. Se llamará a UITrackingRunLoopMode cuando se desplace la página, por lo que generalmente elija el modo apropiado durante el desarrollo comercial.

.commonModes es solo una bandera, lo que significa que Timer es válido tanto en defaultRunLoopMode como en UITrackingRunLoopMode

  • Agregar temporizador al niño RunLoop
  DispatchQueue.global().async {
            let timer = Timer(timeInterval: 2, target: self, selector: #selector(self.update), userInfo: nil, repeats: true)
            RunLoop.current.add(timer, forMode: .commonModes)
            CFRunLoopRun()
    }

 @objc func update() {
     print("update")
     if isStop {
           CFRunLoopStop(CFRunLoopGetCurrent())
     }
}
复制代码
Acerca de las referencias circulares

Sabemos que se puede llamar a Timer de dos maneras: target-action y block. Entre ellos, target-action causará una referencia circular y provocará pérdidas de memoria, porque hay una fuerte cadena de referencia entre target, action y runLoop. Solución
1. Utilice el método de devolución de llamada de bloque

 Timer.scheduledTimer(withTimeInterval: 2, repeats: true) { [weak self] in
            guard let self = self else { return }
  }
复制代码

2. Usa la clase NSProxy como un objeto intermedio

CADisplayLink

CADisplayLink muestra contenido en la pantalla con la misma frecuencia que la pantalla se actualiza. También se basa en NSRunLoop para ejecutarse. La frecuencia de actualización de la pantalla de los dispositivos iOS es fija. CADisplayLink generalmente se llama al final de cada actualización. Tiene alta precisión y es más adecuado para la actualización de la pantalla, etc.

 var  displaylink:  CADisplayLink?

func create() {
     let displaylink = CADisplayLink(target: self,
                                    selector: #selector(refresh))
     displaylink.add(to: .current,
                    forMode: .defaultRunLoopMode)
}

@objc func refresh(displaylink: CADisplayLink) {
    // 打印时间戳
    print(displaylink.timestamp)
}

// 停止CADisplayLink
func stop() {
    displaylink?.invalidate()
    displaylink = nil
}
复制代码

DispatchSourceTimer

DispatchSourceTimer tiene una alta precisión porque está a nivel del sistema y no se ve afectado por RunLoop.

Uso básico común

  // 创建源时间
  public class func makeTimerSource(flags: DispatchSource.TimerFlags = [], queue: DispatchQueue? = nil) -> DispatchSourceTimer

  // 设置定时器事件
  public func setEventHandler(qos: DispatchQoS = .unspecified, flags: DispatchWorkItemFlags = [], handler: Self.DispatchSourceHandler?)

  // 设置定时器触发的回调
  /*
  deadline: 表示计时开始时间
  interval: 间隔时间
  leeway : 精准度
  */
   public func schedule(deadline: DispatchTime, interval: DispatchTimeInterval = .never, leeway: DispatchTimeInterval = .nanoseconds(0))
   
  // 销毁
   public func cancel()
  
   // 启动
   public func resume()
   
   // 暂停
   public func suspend()
复制代码
  let internalTimer = DispatchSource.makeTimerSource(queue: queue)
  internalTimer.setEventHandler { [weak self] in
            guard let self = self else { return }
            handler(self)
   }
   if repeats {
            internalTimer.schedule(deadline: .now()+interval, repeating: interval, leeway: leeway)
   } else {
            internalTimer.schedule(deadline: .now()+interval, leeway: leeway)
   }
  internalTimer.resume()
复制代码

Supongo que te gusta

Origin juejin.im/post/7085610285876117517
Recomendado
Clasificación