You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

UIView.animate的completion闭包是否在主线程执行?

UIView.animate的completion闭包线程问题排查

问题背景

我实现了一个简单的popupView弹窗,最初的基础动画逻辑如下:

// Present
if popup.superView != currentView { currentView.addSubview(popup) }
UIView.animate(withDuration: 0.3, animations: {
    popup.alpha = 1
})

// Dismiss
UIView.animate(withDuration: 0.3, animations: {
    popup.alpha = 0
}, completion: {
    popup.removeFromSuperview()
})

实际运行时发现,若弹出(present)和关闭(dismiss)操作同时触发,新的弹出操作会被关闭逻辑的completion回调打断失效。因此新增animationCount计数变量优化逻辑,代码如下:

var animationCount: Int = 0

// Present
if popup.superView != currentView { currentView.addSubview(popup) }
animationCount += 1
UIView.animate(withDuration: 0.3, animations: {
    popup.alpha = 1
}, completion: {
    animationCount -= 1
// 注:原代码此处存在笔误,闭包结束应为}而非)
)

// Dismiss
animationCount += 1
UIView.animate(withDuration: 0.3, animations: {
    popup.alpha = 0
}, completion: {
    animationCount -= 1
    if animationCount == 0 {
        popup.removeFromSuperview()
    }
})

优化后逻辑运行表现符合预期,但存在一个疑问:在UIView.animate的completion闭包中读写animationCount变量是否会产生数据竞争?核心判断依据是:如果completion闭包运行在主线程,那么animationCount就不存在数据竞争问题,也不会出现弹窗无法从父视图移除的异常。

解答

结论非常明确:UIView.animate的animations闭包、completion闭包始终在主线程执行,你当前的计数方案不存在数据竞争问题。

  • UIKit本身是线程不安全的框架,苹果官方要求所有UI相关操作必须在主线程执行,UIView动画作为UI核心能力,系统会自动将动画逻辑、完成回调全部调度到主线程运行,哪怕你在子线程调用UIView.animate方法,回调也不会跑在子线程。
  • 你的计数逻辑本身是成立的:只要保证所有触发present/dismiss时对animationCount的+1操作、completion回调里的-1和移除视图判断都在主线程执行,就不会出现计数错乱、弹窗漏移除的问题。

修正后可直接使用的代码

你贴的优化代码存在两处语法/适配问题:一是present逻辑的completion闭包结尾笔误把}写成了),二是较新iOS版本的completion闭包会返回动画完成的布尔值参数,修正后代码如下:

var animationCount: Int = 0

// Present
if popup.superview != currentView {
    currentView.addSubview(popup)
}
animationCount += 1
UIView.animate(withDuration: 0.3, animations: {
    popup.alpha = 1
}, completion: { _ in
    animationCount -= 1
})

// Dismiss
animationCount += 1
UIView.animate(withDuration: 0.3, animations: {
    popup.alpha = 0
}, completion: { _ in
    animationCount -= 1
    if animationCount == 0 {
        popup.removeFromSuperview()
    }
})

额外提示:日常开发中所有触发弹窗弹出、关闭的操作都应该手动放在主线程调用,这是UIKit的基础开发规范,也能彻底避免极端时序下的逻辑异常。


内容的提问来源于stack exchange,提问作者sangyoonlee019

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 16:57:18