ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

如何在Traits中扩展自定义Trait:从继承基类到ObjectMapper映射的5个步骤

如何在Traits中扩展自定义Trait:从继承基类到ObjectMapper映射的5个步骤

【免费下载链接】Traits

Modify your native iOS app in real time.

项目地址:https://gitcode.com/gh_mirrors/tr/Traits
点击查看免费下载

Traits 是一个让 iOS 原生 App 无需重新编译即可实时修改设计与行为的开源库。本文面向新手,完整拆解在 Traits 中扩展自定义 Trait 的 5 个步骤:继承 Trait 基类、声明目标类型、实现apply方法、用 ObjectMapper 完成 JSON 映射、编译或代码注入生效,帮你快速打造自己的实时样式修改能力。

Traits 实时改样式的效果演示

先看看它能做到什么:修改一份 JSON 配置文件里的参数,运行中的 App 会立刻更新界面上的颜色、圆角、字体、约束等属性,不用回到 Xcode 重新编译。

除了调整内置属性,Trait 还支持修改 UI 之外的逻辑。而扩展机制的妙处在于:你新写的 Trait 甚至可以通过代码注入(code injection)热加载,连 App 都不用重启。

扩展自定义Trait前:读懂Trait基类

一切自定义都从基类Trait开始,定义在 Trait.swift:

成员作用
restrictedTypes声明这个 Trait 支持哪些目标类型(如[UIView.self])
apply(to:remove:)把效果应用到目标对象上,并写入一个「恢复闭包」
mapping(map:)基于 ObjectMapper 的 JSON 字段映射
Error内置三种错误:unsupportedValue、incorrectTarget、requiresOverride

基类的 apply 默认抛出requiresOverride,提醒你子类必须重写;框架在加载配置时会先用 verifyTypeRequirements 校验目标类型,类型不匹配时自动抛出incorrectTarget并打印日志,不用你手动防御。

步骤1:继承Trait基类,创建自定义Trait子类

新建一个 Swift 文件,声明一个继承Trait的类,并按需声明数据属性。以「修改视图透明度」为例:

import Foundation import ObjectMapper public final class Alpha: Trait { private(set) var alpha: CGFloat? }

不需要任何注册操作:框架在启动时会通过 ObjC 运行时的objc_getClassList扫描所有继承自Trait的类,自动建立「类名 → 工厂」的映射(见 getTraitFactories)。也就是说,只要类名唯一,写出来就能用。

步骤2:用restrictedTypes声明支持的目标类型

重写类属性restrictedTypes,返回一个类型数组,表示这个 Trait 能作用在哪些对象上:

override class var restrictedTypes: [AnyClass]? { return [UIView.self] }

三种写法的含义:

  • 返回[UIView.self]:只支持UIView及其子类;
  • 返回nil:支持任意类型(参考 Inherit.swift);
  • 返回空数组[]:表示不支持任何类型(基类默认值,仅用于占位)。

参考官方实现:CornerRadius只支持UIView(CornerRadius.swift),Font只支持UILabel(Font.swift)。

步骤3:实现apply方法,返回一键恢复闭包

apply(to:remove:)是 Trait 的核心:把参数写到目标对象上,同时把「撤销方法」塞进remove闭包。框架在切换配置时会调用这些闭包,把界面恢复到改动前的样子:

override func apply(to target: Trait.Target, remove: inout RemoveClosure) throws { let target = target as! UIView remove = { [weak target, old = target.alpha] in target?.alpha = old } target.alpha = alpha ?? 1 }

三个关键细节:

  1. 先保存旧值再修改,闭包里引用旧值实现可逆恢复;
  2. 用[weak target]避免闭包持有对象形成循环引用;
  3. 参数不合法时直接throw,例如Font中遇到不存在的字体名会抛出unsupportedValue并附上可用字体列表(Font.swift)。

官方最完整的示例是Constraint:修改约束常量并支持还原,见 Constraint.swift。

步骤4:用ObjectMapper映射JSON参数

为了让配置能从 JSON 文件反序列化成对象,需要实现两个成员(基类要求init?(map:)必须在类内声明,无法放在扩展里,见 Trait.swift):

public required init?(map: Map) { super.init(map: map) } override func mapping(map: Map) { super.mapping(map: map) alpha <- map["alpha"] }
  • super.mapping(map: map)会自动写入"type"字段(值为类名),框架靠它在解析 JSON 时区分 Trait 类型;
  • alpha <- map["alpha"]用 ObjectMapper 的<-运算符完成双向绑定;
  • 需要类型转换时(比如把"#FF0000"字符串转成UIColor),可以挂一个自定义 Transform,项目自带了 ColorTransform.swift 供参考:
fontColor <- (map["color"], ColorTransform())

配置文件的结构如下,traits数组按顺序应用:

{ "myView": [ { "type": "Alpha", "alpha": 0.5 } ] }

加载入口在 TraitsProvider.reloadSpec,JSON 里写了未知类型时会打印Unknown Trait type for key ...便于排查。

步骤5:编译生效,或代码注入实时更新

最后一步是最爽的:常规流程是编译运行后修改配置 JSON 即可看到效果;而项目还支持配合 Injection 工具做代码注入——直接保存.swift文件,新 Trait 类会被重新扫描注册,运行中的 App 即时获得新能力,全程无需重启。

💡 注入时框架会调用 Trait.injected() 刷新工厂列表,所以新类「写完即生效」。

常见问题与测试验证

❓ 忘记重写 apply 会怎样?框架捕获到requiresOverride后会在日志打印apply function requiresOverride,界面不受影响,属于安全失败。

❓ 如何验证映射和恢复逻辑正确?参考现有测试:用verifySerialization做「序列化 → 反序列化」往返校验,再调用apply后执行恢复闭包,断言属性回到初始值。示例见 FontSpec.swift。

❓ 多属性 Trait 怎么写?照抄Font的结构即可:多个private(set)属性 +mapping里逐字段绑定,见 Font.swift。

相关文件路径速查

文件说明
Traits/Classes/Traits/Trait.swiftTrait 基类、错误定义与自动注册机制
Traits/Classes/Traits/Constraint/Constraint.swift官方推荐的最小完整示例
Traits/Classes/TraitsProvider/TraitsProvider.swift配置加载与 Trait 应用调度
Traits/Classes/Helpers/ColorTransform.swift自定义 ObjectMapper Transform 示例
Example/Generated.TraitIdentifiers.swift类型安全的 identifier 生成示例
Example/Tests/Specs/Traits/FontSpec.swiftTrait 单元测试写法参考

掌握以上 5 个步骤,你就可以在 Traits 中为任何NSObject子类添加自己的实时可控属性了——写一个类、映射几个字段,剩下的交给框架。

【免费下载链接】Traits

Modify your native iOS app in real time.

项目地址:https://gitcode.com/gh_mirrors/tr/Traits
点击查看免费下载

相关推荐

上一篇:36亿参数双语文档理解王者:Kakao Kanana-1.5-v-3b-instruct实测
下一篇:Ice 菜单栏管理指南:免费开源的 Mac 图标收纳工具,3 个分区 10 分钟搞定

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进