说好的一周三次的翻译未能如期实现,借口可以列出一大堆,我也不再这里赘述。最难能可贵的就是,我还没有彻底放弃翻译这件事,它一直藏在我的心里深处,时刻铭记。
Coding中不断思考,重复设计不仅耗时而且令人生厌。
笼统地讲,设计者在设计编辑软件中设计好,然后提交到平台给开发着去code。改善设计同样也需要大量的处理步骤。
如果我们希望在我们的apps中有更丰富的主题,这样的要求就更难了。我们如何使用我们的编译器来处理这个问题呢?
让我们来看看一个可以处理这个问题的库是怎么做到的。
功能
首先,我们要考虑到我们需要的所有功能。
及时反馈 -我们做出更改时不用重新的编译,就应该立刻看到更改所造成的影响。
能够更新使用中的应用-我敢说没有比这个更酷的了
- 支持代码和Xib
- 他不仅仅可以更新UI,甚至可以对内部的数据稍加调整。
- 集成简单 - 使用这个库,影响将是最小的。
- 希望可以测试Everything,生活又美好了。
- 在Xcode8使用时可以代码注入,瞬时的写入库,无需重新编译。
设计
尽管我喜欢用swift编写app,但还是借用了不少OC中runtime的能力,但是这是值得的。
我经常认为突变是邪恶的源泉,尽管这是正确的。
如果我想在一个正在运行的应用中执行瞬时的更改,
同时对基础的代码造成最低的影响我们就需要便面不必要的状态改变。
在Trait中有这些观点,它允许我们去延伸已经存在的事物,为它增加更多的功能。让我们来模仿一下。
Trait
如果库的使用者有一些自定义的类,我们可以以一种可恢复的方式更改它们。
在这个库中,Trait包含了一些单一的功能,可以通过一个实体改变它的状态,他还有一个功能就是撤销这些改变。
所以我们使用撤销功能是就可以在结束时回到最初的状态。
let sourceView = view.copy
let reverse =Trait.apply(view)
reverse(view)
sourceView == view
这种模式不仅可以轻易的更改运行中的应用而且还可以控制它的副作用,而不是任由其随意的更改。
持久化
使用这些功能非常有利于对代码的理解。
然而,可能在执行的时候存在一些不确定的错误,因为Trait
是一个基于元数据的定制功能。
让我们看看功能列表,我们看到的最为特别的能力及就是在使用时更新,所以我们需要持久化这样我们就可以通过网络设置。
因为JSON是行业标准我们可以使用Object—Mapper,所以我们的要求指令支持2种方式转换(连续的或者是并发的)。
Outside of prototype scope, I would highly recommend using something easier to read/modify by humans e.g. YAML, JSON is better for machine than humans
我们将持久换这些功能配置,这样即使在不同的Traits中也会同样的标准。例如:DropShadow。
open override func mapping(map: Map) { super.mapping(map: map)
color <- (map["color"], ColorTransform())
offset <- (map["offset"], SizeTransform())
opacity <- map["opacity"]
}
实时重载
借用我们的数据转化为JSON格式,我们可以支持本地或者远程重载。
首先,我们需要一个后台进程去监测改变,在这里使用KZFileWatchers监测本地或者远程。
下一步,我们需要一种方法将并发的Traits注入到正在运行的App中,这就是TraitsProvider
.
TraitsProvider
的功能很简单:
* 找到已存在的tarits
* 使用它的监察功能一处所有的副作用返回到出事状态。
* 使用新的traits,存储哪些监察者为将来使用。
traits.forEach {
var removeClosure = {}
$0.apply(to: target, remove: &removeClosure)
var stack = target.traitsReversal stack.append(RemoveClosureWrapper(block: removeClosure))
target.traitsReversal = stack}
I have had to introduce inout param for remove closure because when migrating to Swift3 I discovered compiler bug with my previous approach of just returning closure
识别traits 降低影响
每个视图都有不同的traits,所以我们需要辨别哪个Traits要应用到哪个实体呢?简而言之,我们可以引入关联对象标识符。
public extension NSObject {
var traitSpec: String? {
get { return objc_getAssociatedObject(self, &Keys.specKey) as? String }
set {
if let key = newValue { TraitsProvider.loadSpecForKey(key, target: self)
}
objc_setAssociatedObject(self, &Keys.specKey, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC)
}
}
}
public extension UIView {
/// Defines an identifier for trait specification. @IBInspectable override var traitSpec: String? { get { return super.traitSpec } set { super.traitSpec = newValue } }}
设置好标识符,询问TraitsProvider
去加载Trait的规格。
这功能可同样在IB和代码中使用。
支持修改运行库自身
我们希望写完一个新的Trait就能马上获得支持无需重新编译工程。
第一步:代码注入。第二部:设计我们的系统这样不需要我们手动注册Traits。
我们可以使用runtime直接找出所有继承自Trait
的类。
typealias Factory = (_ map: Map) -> Trait?
static func getTraitFactories() -> [String: Factory] { let classes = classList()
var ret = [String: Factory]()
for cls in classes {
var current: AnyClass? = cls
repeat {
current = class_getSuperclass(current)
} while (current != nil && current != Trait.self)
if current == nil { continue }
if let typed = cls as? Trait.Type { ret[String(describing: cls)] = typed.init
}
}
return ret
}
然后我们只需要自动冲洗注入已经存在的Traits
:
/// This function will be called on code injection, thus allowing us to load new Trait classes as they appear.
class func injected() {
Trait.factories = Trait.getTraitFactories()
}
不仅仅用在UI上
这种模式不只用在UI上,借用runtime的能力,我们可以用到app的其他地方。我们该如何安全的使用它呢。
我们想避免一些可以使用DropShadow 处理数据模型,那么我们如何定义支持哪种类型的Trait
呢?
我们可以支持所有的Trait列表中的类:
var restrictedTypes: [AnyClass]? { return [UIView.self] }
在应用Trait
之前,TraitsProvider
会核实类型要求,确保每个apply
只会运用在支持的targets中。
追加:避免字符串的使用
字符串是丑陋的,除了在代码中我们也应该在IB中避免使用它们。我们可以写一个简单的脚本浏览所有的SB/xib,创建一个相应类型的索引。然后添加为构建阶段,这样我们就可以使用更加安全的API。
以上就是我在Demo中使用到的。
结论
源码、文档、test都在GitHub上。
使用这个库唯一做的事就是指定为你的对象标识符,这样就可以调整运行我们的后台程序了。
我们设计的架构允许再不重新编译工程的情况下快速的创建新的Traits,或者远程修改实体中的Traits(通过JSON数据)或者通过本地代码实时修改。
在应用中的Views中或者IB中配置它们都是一样的,这个库就是这么的帅。
出处
翻译原文How hard would it be to adjust your iOS app in real-time?