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

资讯详情

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

Dance 实现原理揭秘:关联对象与 DanceFactory 单例架构深入解析

Dance 实现原理揭秘:关联对象与 DanceFactory 单例架构深入解析 Dance 实现原理揭秘关联对象与 DanceFactory 单例架构深入解析【免费下载链接】DanceA radical elegant animation library for iOS.项目地址: https://gitcode.com/gh_mirrors/dance1/DanceDance 是一款专为 iOS 打造的开源动画库全部核心代码浓缩在单个Dance.swift文件中。它建立在 iOS 10 的UIViewPropertyAnimator之上让开发者只需一句view.dance.animate { ... }就能对任意UIView创建、暂停、反转、拖拽进度动画。本文将从源码层面深入揭秘 Dance 的实现原理重点剖析两大架构支柱关联对象Associated Objects与DanceFactory 单例架构帮助你真正读懂这套小而美的设计。 Dance 是什么基于 UIViewPropertyAnimator 的 iOS 动画库Dance 的自我定位是 A radical elegant animation library for iOS它把 iOS 10 引入的UIViewPropertyAnimator封装成一套极其顺手的链式 API。你可以在任何能拿到 view 引用的地方随时对它的动画做四件事⏸.pause()暂停动画▶️.start()启动或继续动画.reverse()反向播放动画.setProgress(to:)像拖进度条一样搓动画最巧妙的是这套能力不需要你继承任何自定义视图类任何UIView、UILabel、UIButton拿来就能用。这背后正是关联对象在发挥作用。 关联对象原理如何给任意 UIView 挂载动画能力先抛出一个 Swift 开发中的经典难题我们无法给UIView的扩展直接添加存储属性。扩展只能添加计算属性但计算属性没有地方存放真正的数据。Dance 需要为每个 view 记住它的动画对象是谁该怎么办Dance 的答案藏在Dance.swift的UIView扩展里它借用了 Objective-C 运行时的关联对象Associated Objects机制extension UIView { fileprivate struct DanceAssociatedKey { static var dance dance_key } public var dance: Dance { get { if let danceInstance objc_getAssociatedObject(self, DanceAssociatedKey.dance) as? Dance { return danceInstance } else { let newDanceInstance Dance(dancingView: self) objc_setAssociatedObject(self, DanceAssociatedKey.dance, newDanceInstance, .OBJC_ASSOCIATION_RETAIN) return newDanceInstance } } } } 为什么 Swift 扩展不能直接添加存储属性Swift 的扩展语法只允许新增方法、计算属性和嵌套类型不允许新增存储属性。这是语言层面的限制目的是防止扩展悄悄改变类型的内存布局。而UIView是 Objective-C 的类底层天然支持动态附加属性于是objc_getAssociatedObject/objc_setAssociatedObject就成了给现有类外挂状态的标准姿势。 关键细节懒加载 单例保证注意这个 getter 的写法每次访问view.dance时先尝试从关联对象中取出已有的Dance实例取不到才创建新的并用.OBJC_ASSOCIATION_RETAIN策略挂到 view 上。这意味着一个 view 一生只对应一个Dance实例天然去重首次访问时才创建属于标准的懒加载view 持有Dance而Dance内部用weak var dancingView弱引用回 view完美避开循环引用view 释放后关联对象随之清理不会内存泄漏。 DanceFactory 单例架构全库动画的统一调度中心如果说关联对象解决了动画对象挂在哪那么DanceFactory单例则解决了动画引擎管在哪。整个库的动画能力最终都汇聚到这个类里统一调度。 为什么选择 fileprivate 单例在Dance.swift中DanceFactory被声明为fileprivate类并持有静态实例fileprivate class DanceFactory { static let instance DanceFactory() var tagCount: Int 0 var animators [Int: UIViewPropertyAnimator]() // ... }这样设计有三大好处封装性fileprivate意味着只有Dance.swift内部能访问外部开发者完全接触不到这个发动机只能通过view.dance这个干净的入口操作动画避免 API 面过宽全局唯一static let instance由 Swift 保证线程安全的懒初始化整个进程只有一份所有 view 的动画状态都集中在一处管理便于调试集中管理意味着出错时能在单一入口统一兜底这也是后面要讲的宽容错误处理的基础。 tag 标签与动画字典一对一的映射机制DanceFactory内部维护了两样核心数据一个不断自增的tagCount和一个以 tag 为 key 的动画字典animators [Int: UIViewPropertyAnimator]。每次调用view.dance.animate(...)时流程是这样的self.tag DanceFactory.instance.tagCount DanceFactory.instance.tagCount 1 // 若旧动画还在先礼貌地结束它 if self.hasAnimation { DanceFactory.instance.finishAnimation(tag: self.tag, at: .current) } // 为这个 view 创建新的 UIViewPropertyAnimator 并登记 DanceFactory.instance.createNewAnimator(tag: self.tag, duration: duration, curve: curve) { animation(self.dancingView) } return dancingView.dance也就是说每个Dance对象持有一个唯一的tag而DanceFactory用这个 tag 在字典里找到对应的UIViewPropertyAnimator。整套协作关系可以概括成一张职责表角色职责UIView被动画的对象通过关联对象挂载DanceDance面向开发者的控制手柄持有 tag 与弱引用 viewDanceFactory单例调度中心持有 tag → Animator 映射字典UIViewPropertyAnimator真正执行动画的引擎iOS 10 内存自动清理的细节细心的读者会发现createNewAnimator在创建动画器时会给它注册一个 completion 回调动画一结束就把自己从animators字典里移除。这样动画完成 → 字典瘦身 → tag 失效形成闭环既保证了hasAnimation状态判断的准确性也避免了字典无限膨胀。 Dance 类面向视图的动画控制手柄Dance是一个public类是整个库对外的门面。它内部只存了两个状态weak var dancingView被动画的 view和public var tag唯一标识。它提供的 API 几乎 1:1 映射了UIViewPropertyAnimator的能力但全部以视图视角重新包装progress读取/设置动画进度0 到 1state查询动画状态.inactive/.active⚡isRunning/isReversed查询或反转运行状态✅hasAnimation判断当前 view 是否有动画在册更重要的是每个方法都返回dancingView.dance从而支持链式调用。比如示例项目DanceExample/ViewController.swift里的写法circle.dance.animate(duration: 2.0, curve: .easeInOut) { $0.transform CGAffineTransform(scaleX: 1.5, y: 1.5) $0.center self.view.center $0.backgroundColor .blue }.addCompletion { _ in self.view.backgroundColor .green }.start(after: 5.0)一句链式调用就完成了创建动画 → 添加完成回调 → 延迟启动三个步骤代码既优雅又易读这正是 Dance 的设计哲学——在UIViewPropertyAnimator的威力与易用性之间取得平衡。 一次动画的完整生命周期把上面的机制串起来一个 view 动画的完整生命周期是这样的创建调用.animate(...)Dance申请新 tagDanceFactory创建UIViewPropertyAnimator并登记进字典此时若旧动画仍在会先把它结束在当前状态启动调用.start()DanceFactory按 tag 找到动画器并执行控制任意时刻可.pause()暂停、.reverse()倒放、通过progress或.setProgress(to:)精确拖拽进度——注意拖拽时若动画器还没运行Dance 会先 start 再 pause以便fractionComplete能生效结束动画自然播完或调用.finish(at: .start/.current/.end)提前收尾此时触发所有 completion 回调并把该 tag 从字典中移除。 宽容的错误处理设计宁可打印绝不崩溃Dance 官方用 forgiving宽容的形容自己的错误处理。比如你手滑对没有动画的 view 调用了.start()它不会抛异常、不会崩溃而是走handle(error:forViewWithTag:)打印一条友好提示** Dance Error: view with dance.tag tag does not have an active animation! **这种日志兜底的策略对新手极其友好动画代码写错了App 照常运行你只要顺着dance.tag就能定位到是哪一次动画调用出了问题。对比许多动辄 fatal error 的库这套设计大大降低了上手门槛。 总结这套架构设计巧在哪里回顾整个实现Dance 的架构可以用三个词概括零侵入用关联对象给UIView挂载能力不改动系统类不强制继承任何视图即插即用单点调度DanceFactory单例集中管理所有动画器配合 tag 映射字典实现精确控制与自动清理面向开发者Dance类提供链式、宽容、可调试的 API把底层的复杂度全部关进Dance.swift的黑盒里。如果你正在研究 iOS 动画或者想学习关联对象 单例 字典映射这套经典组合拳打开项目的Dance.swift逐行阅读相信会有不少收获——整个库只有这一个核心源文件通读一遍的成本极低收益却很高。【免费下载链接】DanceA radical elegant animation library for iOS.项目地址: https://gitcode.com/gh_mirrors/dance1/Dance创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表