两端对照
14 组主题,并排比较 Telegram-iOS 与 TelegramSwift 对同一问题的解法。
两个客户端由不同团队维护,但面对的问题相同:长列表、倒序聊天、富文本、动画贴纸、系统菜单和键盘。每组对照先写共同问题,再分别列出 iOS 与 macOS 的做法和对应条目,最后写出差异。用左侧列表或方向键切换主题,右上角可以只看一端。
视图基础:节点与图层
iOS 用 AsyncDisplayKit 节点把绘制移到后台。每次失效都递增 sentinel,旧的后台任务因此作废。layer-backed 节点用弱代理,防止 CALayer.delegate 悬空。
- #05-06异步绘制用原子 sentinel 取消旧帧,并在主线程原子回填结果
每次异步显示递增
_displaySentinel。后台 block 捕获当时值。新的失效请求使旧 block 的检查失败。完成 block 只在未取消时设置layer.contents。异步任务加入父层事务,事务在主线程同步回填。 - #05-12layer-backed 节点用弱代理修复
CALayer.delegate悬空指针layer-backed 节点不把自身直接交给
CALayer.delegate。它创建弱目标代理,并用 associated object 强持有代理。layer 比 node 活得更久时,代理把回调安全地吞掉。 - #03-12在
action(forKey:)中同时禁用隐式动画和追踪挂载自定义
CAAction的run为空。图层对任何 action key 都返回该对象。它在kCAOnOrderIn和kCAOnOrderOut时维护层级状态并触发回调。
TGUIKit 不做异步绘制。所有 View 默认 layer-backed,统一翻转坐标,并关闭隐式动画。变换类属性改由 updateLayer 显式同步。层级进出用空 CAAction 通知。
- #12-05精选 12-5在基础 View 上统一翻转坐标并关闭隐式动画
所有
View默认 layer-backed。flip默认值为true。初始化关闭图层隐式动画,关闭自动子视图缩放,并按需重绘 layer。 - #12-04精选 12-4以条件
updateLayer模拟 UIKit 变换属性View把锚点、变换和子层变换包装为存储属性。属性变更时,它主动调用updateLayer()。它只在至少一个属性被使用时请求 AppKit 的updateLayer路径。 - #12-06精选 12-6用空 CAAction 兼做层级进入通知
SimpleShapeLayer拦截 CA 的onOrderIn和onOrderOut动作查询。它把这两个动作转换为层级生命周期回调。无论事件是什么,它都返回无操作CAAction。初始化时设置 Retina 比例。
AppKit 特有难点
以下问题只在 macOS 端出现,iOS 端没有对应条目。
- 翻转坐标。 TGUIKit 统一
flip = true,并在updateLayer中同步,避免多处坐标系分歧。见 #12-05。 NSTableView行几何。 代码禁用自动行高,手工调用noteHeightOfRow,并自己计算粘性标题的推挤。见 #13-21 和 #13-06。- 非激活窗口的 hover。
NSTrackingArea使用.activeInActiveApp和.assumeInside,窗口失焦时仍更新悬停状态。见 #12-10。 - 窗口键盘分发。 代码自建优先级链与全局处理器,覆盖默认 responder 链。见 #12-03。
- 显式 Core Animation 管理。
SimpleLayer的空 action 关闭隐式动画,关键转场手动补动画。见 #12-06。