尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

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

UIView.cheetah是怎么实现的:关联对象与自定义属性动画扩展源码剖析 UIView.cheetah是怎么实现的关联对象与自定义属性动画扩展源码剖析【免费下载链接】CheetahEasy animation library on iOS with Swift2项目地址: https://gitcode.com/gh_mirrors/chee/CheetahCheetah 是一个基于 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/UIViewCheetahAnimation.swiftvar 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 的选择很有讲究键是全局变量的地址cheetahAssociationKey指向一个全局字符串变量的内存地址进程内天然唯一不会与其他库撞键键定义在 extension 文件作用域而非 Cheetah 类内部保证了键的生命周期与库一致弱引用 view 强引用 Cheetah的组合见 Cheetah/Cheetah.swiftCheetah内部持有weak var view: UIView?即view 强持 CheetahCheetah 弱指回 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.swiftCheetahGroup组一组并行执行的属性组与组之间串行。wait()方法Cheetah/Cheetah.swift就是压入新组从而实现move → wait() → rotate这种先后顺序。几个值得注意的设计细节duration(_:)会同时写给最后一个属性和当前组Cheetah/Cheetah.swift所以先写.duration(0.5)再写多个属性后续属性会继承这个时长缓动函数全部以闭包形式提供public typealias Easing (_ t:CGFloat, _ b:CGFloat, _ c:CGFloat) - CGFloatCheetah/Easings.swift30 多个内置曲线大多通过贝塞尔求解器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用displayLink.timestamp差值算出本帧dt遍历所有注册中的 Cheetah逐个调用proceed(dt)推进时间轴某个 Cheetah 完成后把它的关联对象置为 nilobjc_setAssociatedObject(view, cheetahAssociationKey, nil, ...)即自动从 view 上卸载自己避免陈旧的动画状态残留当注册表为空时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.swiftsize / frame / alpha / backgroundColor图层类Cheetah/CheetahLayerProperties.swiftposition / rotation / scale / 边框圆角约束类Cheetah/CheetahConstraintProperties.swift七、总结一套可复用的 iOS 扩展设计范式 把UIView.cheetah的实现拆开看其实是一份非常精炼的设计范式清单关联对象解决Swift 扩展无存储问题键用全局变量地址动画结束自动卸载弱引用回指切断循环引用让 view 的生命周期天然约束动画的生命周期链式 API 组/属性双层模型用wait()一个方法同时获得并行与串行能力CADisplayLink 集中管理单例管理器共享一个 DisplayLink空载即停prepare / calc / update 三段式属性模板让任意属性可动画成为低成本的扩展点。如果你正在做 iOS 动画、或者想给系统类外挂状态这套不到千行的源码主目录 Cheetah/值得完整读一遍——它几乎浓缩了 Runtime 扩展与帧驱动动画的全部关键技巧。【免费下载链接】CheetahEasy animation library on iOS with Swift2项目地址: https://gitcode.com/gh_mirrors/chee/Cheetah创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表