news 2026/8/22 15:35:17

UIView.cheetah是怎么实现的:关联对象与自定义属性动画扩展源码剖析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
UIView.cheetah是怎么实现的:关联对象与自定义属性动画扩展源码剖析

UIView.cheetah是怎么实现的:关联对象与自定义属性动画扩展源码剖析

【免费下载链接】CheetahEasy animation library on iOS with Swift2项目地址: https://gitcode.com/gh_mirrors/chee/Cheetah

Cheetah 是一个基于 Swift 的 iOS 轻量级动画库,它通过一句view.cheetah.move(100, 0).run()就能驱动任意 UIView 属性做链式动画。今天我们就以UIView.cheetah这个入口为线索,剖析它背后的两个核心机制:Objective-C 关联对象(Associated Objects)与自定义属性动画系统,看懂它如何在不继承、不修改 UIView 源码的前提下"挂"上动画能力。

一、Cheetah 是什么:一句话带动画

🦁 Cheetah(猎豹)的核心理念写在官方说明里:它不依赖 Core Animation 的隐式动画,而是用一个简单的CADisplayLink 运行循环逐帧修改属性值,因此"任何属性"都可以被动画化——包括 NSLayoutConstraint 的 constant 这种系统动画根本碰不到的东西。

主要特性一览:

特性说明
时长与延迟每个属性可独立设置 duration / delay
绝对/相对模式position绝对定位,move相对位移
并行/串行执行同一组属性并行,wait()切组串行
缓动函数内置 Sine、Quad、Cubic、Elastic、Bounce 等 30+ 曲线
弹簧动力学基于 tension / friction 参数的 Spring 动画

源码入口是 Cheetah.podspec,库本体全部集中在Cheetah/目录下,总共不到 15 个文件,非常适合通读。

二、UIView.cheetah 扩展属性:一行魔法代码

UIView.cheetah的全部实现只有 20 行,位于 Cheetah/UIView+CheetahAnimation.swift:

var cheetahAssociationKey = "cheetahAssociation" public extension UIView { public var cheetah: Cheetah { get { if let instance = objc_getAssociatedObject(self, &cheetahAssociationKey) as? Cheetah { return instance } let instance = Cheetah(view: self) objc_setAssociatedObject(self, &cheetahAssociationKey, instance, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) return instance } } }

这段代码做了一件很巧的事:

  • 首次访问:字典里查不到 Cheetah 实例,就新建一个并通过objc_setAssociatedObject绑到当前 UIView 上,记忆策略选的是.OBJC_ASSOCIATION_RETAIN_NONATOMIC(强引用、非原子);
  • 后续访问objc_getAssociatedObject直接取回同一个实例,保证链式调用状态不丢失;
  • 动画结束后:由管理器主动把关联对象置 nil,自动"卸载"自己(后面第五节细说)。

📌 这就是典型的关联对象扩展模式:Swift 的 extension 不能存储属性,于是借用 Objective-C Runtime 给对象"外挂"一个键值对,相当于给 UIView 动态加了一个cheetah实例变量。

三、为什么关联对象能行:Runtime 原理 30 秒理解

💡 每个 OC 对象(UIView 本质上是 OC 类)在 Runtime 中都维护着一张关联对象表。objc_setAssociatedObject实际上是往这张表里插入一条"键(任意指针)→ 值"的映射。

Cheetah 的选择很有讲究:

  1. 键是全局变量的地址&cheetahAssociationKey指向一个全局字符串变量的内存地址,进程内天然唯一,不会与其他库撞键;
  2. 键定义在 extension 文件作用域而非 Cheetah 类内部,保证了键的生命周期与库一致;
  3. 弱引用 view + 强引用 Cheetah的组合(见 Cheetah/Cheetah.swift):Cheetah内部持有weak var view: UIView?,即"view 强持 Cheetah,Cheetah 弱指回 view",不会形成循环引用,view 销毁后 Cheetah 自动释放,动画也会安全终止。

这套"关联对象 + 弱回指"的写法,是 iOS 上给系统类扩展状态的标准方案,你在很多成熟库(如日志、埋点框架)里都能看到它的身影。

四、Cheetah 核心类:链式 API 与属性分组

拿到cheetah属性后返回的是 Cheetah/Cheetah.swift 里的核心类,它把所有动画 API 设计成返回 self 的链式方法

// move / position / rotate / scale / alpha / backgroundColor ... open func move(_ x: CGFloat, _ y: CGFloat) -> Cheetah open func rotation(_ angle: Double) -> Cheetah open func alpha(_ alpha: CGFloat) -> Cheetah open func constraint(_ constraint: NSLayoutConstraint!, constant: CGFloat) -> Cheetah

内部数据模型是两层结构:

  • CheetahProperty(属性):一次动画的最小单位,记录 from/to、duration、delay、easing 等,定义在 Cheetah/CheetahProperties.swift;
  • CheetahGroup(组):一组并行执行的属性,组与组之间串行。wait()方法(Cheetah/Cheetah.swift)就是"压入新组",从而实现move → wait() → rotate这种先后顺序。

几个值得注意的设计细节:

  • duration(_:)同时写给最后一个属性和当前组(Cheetah/Cheetah.swift),所以先写.duration(0.5)再写多个属性,后续属性会"继承"这个时长;
  • 缓动函数全部以闭包形式提供:public typealias Easing = (_ t:CGFloat, _ b:CGFloat, _ c:CGFloat) -> CGFloat(Cheetah/Easings.swift),30 多个内置曲线大多通过贝塞尔求解器Easings.cubicBezier生成(Cheetah/Easings.swift),你也能自己传任意闭包进来;
  • 弹簧动画由独立的 Cheetah/Spring.swift 实现,通过spring(tension:friction:)挂载到属性上(Cheetah/Cheetah.swift)。

五、动画如何逐帧驱动:CADisplayLink 单例管理器

链式调用只是"配置",真正跑起来靠run()→ 单例 Cheetah/CheetahManager.swift 的帧循环:

run() → Manager.add(self) → 创建 CADisplayLink → 每帧回调 update()

update(_:)每帧做的事(Cheetah/CheetahManager.swift):

  1. displayLink.timestamp差值算出本帧dt
  2. 遍历所有注册中的 Cheetah,逐个调用proceed(dt)推进时间轴;
  3. 某个 Cheetah 完成后,把它的关联对象置为 nilobjc_setAssociatedObject(view, &cheetahAssociationKey, nil, ...)),即自动从 view 上"卸载"自己,避免陈旧的动画状态残留;
  4. 当注册表为空时invalidate()掉 DisplayLink,零动画时零开销

🎯 逐帧推进的核心数学很简单:每个CheetahProperty.proceed(dt)累加elapsed,算出进度current = (elapsed - delay) / duration,再经缓动函数插值后写回真实属性(Cheetah/CheetahProperties.swift)。这正是它能动画"任意属性"的原因——每帧直接赋值,不挑属性类型。

旋转、缩放这类变换类属性走的是另一条路:不直接改layer.transform,而是各自算出一个局部变换矩阵,最后由 Cheetah/CheetahGroup.swift 用CATransform3DConcat合并后一次性写回,因此同一组里 rotate + scale 可以完美叠加、互不覆盖。

六、自定义属性动画扩展:照着模板写三个方法

🛠️ Cheetah 最迷人的能力是"动画你自己的属性"。以透明度为例,Cheetah/CheetahViewProperties.swift 的模板极其清晰:

class CheetahViewAlphaProperty: CheetahCGFloatProperty { override func prepare() { super.prepare() from = view.alpha // 1. 记录起点 } override func update() { view.alpha = calculateCGFloat(from: from, to: toCalc) // 2. 插值并写回 } }

继承合适的基类(CGFloat/CGPoint/CGSize/CGRect/UIColor,见 Cheetah/CheetahProperties.swift),实现prepare(抓 from 值)和update(写当前值)即可。更硬核的例子是约束动画:Cheetah/CheetahConstraintProperties.swift 每帧直接改写constraint.constant,这是 UIView.animate 根本做不到的。

内置属性类分布:

  • 视图类:Cheetah/CheetahViewProperties.swift(size / frame / alpha / backgroundColor)
  • 图层类:Cheetah/CheetahLayerProperties.swift(position / rotation / scale / 边框圆角)
  • 约束类:Cheetah/CheetahConstraintProperties.swift

七、总结:一套可复用的 iOS 扩展设计范式

📖 把UIView.cheetah的实现拆开看,其实是一份非常精炼的设计范式清单:

  1. 关联对象解决"Swift 扩展无存储"问题,键用全局变量地址,动画结束自动卸载;
  2. 弱引用回指切断循环引用,让 view 的生命周期天然约束动画的生命周期;
  3. 链式 API + 组/属性双层模型,用wait()一个方法同时获得并行与串行能力;
  4. CADisplayLink 集中管理,单例管理器共享一个 DisplayLink,空载即停;
  5. prepare / calc / update 三段式属性模板,让"任意属性可动画"成为低成本的扩展点。

如果你正在做 iOS 动画、或者想给系统类外挂状态,这套不到千行的源码(主目录 Cheetah/)值得完整读一遍——它几乎浓缩了 Runtime 扩展与帧驱动动画的全部关键技巧。

【免费下载链接】CheetahEasy animation library on iOS with Swift2项目地址: https://gitcode.com/gh_mirrors/chee/Cheetah

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/22 15:34:56

一套键鼠控制三台电脑:Input Leap 跨设备输入共享快速上手指南

一套键鼠控制三台电脑:Input Leap 跨设备输入共享快速上手指南 【免费下载链接】input-leap Open-source KVM software 项目地址: https://gitcode.com/gh_mirrors/in/input-leap 桌面上同时开着 Windows、macOS 和 Linux 三台机器,键盘和鼠标却只…

作者头像 李华
网站建设 2026/8/22 15:28:47

论文大纲逻辑理不清,有哪些靠谱的一键生成论文工具推荐?

每到毕业季,开题报告就成了不少同学的“第一道坎”:选题定不下来、研究背景和意义分不清、文献综述写不出头绪、研究方法和技术路线逻辑混乱,对着空白文档硬撑几周也理不出完整框架。尤其是零基础、在职读研、跨专业的学生,对高校…

作者头像 李华
网站建设 2026/8/22 15:24:30

如何用Viewi构建登录守卫:组件级中间件完整指南

如何用Viewi构建登录守卫:组件级中间件完整指南 【免费下载链接】viewi Unique and efficient front-end framework for PHP 项目地址: https://gitcode.com/gh_mirrors/vi/viewi Viewi 是一款独特的 PHP 前端框架(Unique and efficient front-en…

作者头像 李华