DSL ,领域专用语言, Domain Specific Languagegit
一门编程语言,图灵完备,功能有,性能也有。譬如 Swiftgithub
DSL 基于一门语言,专门解决某一个问题。适合声明式,规则明确的场景编程
该问题上,语法简练,处理方便。譬如 SnapKitbash
Swift 有类型推导功能 type refer、协议化编程 POP、操做符重载等优点,开发其 DSL 比较方便。闭包
原生布局,使用 LayoutAnchor
app
label.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
// label 的顶部,距离 button 的底部, 20 pt
label.topAnchor.constraint(
equalTo: button.bottomAnchor,
constant: 20
),
// label 的左边,对齐 button 的左边
label.leadingAnchor.constraint(
equalTo: button.leadingAnchor
),
// label 的宽度,不超过 button 的宽度 - 40 pt
label.widthAnchor.constraint(
lessThanOrEqualTo: view.widthAnchor,
constant: -40
)
])
复制代码
使用本文造的 DSL 后, 布局代码少了不少,符号更加直观less
// put , 有放置的意思
label.put.layout {
$0.top == button.put.bottom + 20
$0.leading == button.put.leading
$0.width <= view.put.width - 40
}
复制代码
LayoutAnchor
须要创建功能协议 LayoutAnchor
, 把 iOS 系统有 6 个布局方法,抽离合并成 3 个。dom
创建功能协议 LayoutAnchor
,把繁琐的细节,屏蔽掉编程语言
protocol LayoutAnchor {
func constraint(equalTo anchor: Self,
constant: CGFloat) -> NSLayoutConstraint
func constraint(greaterThanOrEqualTo anchor: Self,
constant: CGFloat) -> NSLayoutConstraint
func constraint(lessThanOrEqualTo anchor: Self,
constant: CGFloat) -> NSLayoutConstraint
}
extension NSLayoutAnchor: LayoutAnchor {}
复制代码
LayoutProxy
, 在原生布局方法上,包裹一层。这样调用语法少一点先拿到属性,布局
class LayoutProxy {
lazy var leading = property(with: view.leadingAnchor)
lazy var trailing = property(with: view.trailingAnchor)
lazy var top = property(with: view.topAnchor)
lazy var bottom = property(with: view.bottomAnchor)
lazy var width = property(with: view.widthAnchor)
lazy var height = property(with: view.heightAnchor)
private let view: UIView
fileprivate init(view: UIView) {
self.view = view
}
private func property<A: LayoutAnchor>(with anchor: A) -> LayoutProperty<A> {
return LayoutProperty(anchor: anchor)
}
}
复制代码
再调用布局方法
封装一层,把原生的方法名,给改了
LayoutProperty
, 他包了个遵照 LayoutAnchor 的属性 anchor. 这样能够不用直接操做 NSLayoutAnchor ,直接给 NSLayoutAnchor 增长方法,优雅一些struct LayoutProperty<Anchor: LayoutAnchor> {
fileprivate let anchor: Anchor
}
extension LayoutProperty {
func equal(to otherAnchor: Anchor, offsetBy constant: CGFloat = 0) {
anchor.constraint(equalTo: otherAnchor,
constant: constant).isActive = true
}
func greaterThanOrEqual(to otherAnchor: Anchor,
offsetBy constant: CGFloat = 0) {
anchor.constraint(greaterThanOrEqualTo: otherAnchor,
constant: constant).isActive = true
}
func lessThanOrEqual(to otherAnchor: Anchor,
offsetBy constant: CGFloat = 0) {
anchor.constraint(lessThanOrEqualTo: otherAnchor,
constant: constant).isActive = true
}
}
复制代码
调用语法,略微精炼
label.translatesAutoresizingMaskIntoConstraints = false
let proxy = LayoutProxy(view: label)
proxy.top.equal(to: button.bottomAnchor, offsetBy: 20)
proxy.leading.equal(to: button.leadingAnchor)
proxy.width.lessThanOrEqual(to: view.widthAnchor, offsetBy: -40)
复制代码
上下文环境, 说明了这里是干什么的。方便理解
手动创建布局对象,let proxy = LayoutProxy(view: label)
,再具体布局
薄板代码 boiler plate,仍是多了一些。每次都要重复这个套路,不怎么优雅。
上下文环境,譬如 SnapKit
.
看见 .snp{}
, 就知道这里面是干什么的。在这里,只会布局相关,不会干其余
给 UIView
添加扩展方法,配置 UIView
后,执行 LayoutProxy
的闭包
extension UIView {
func layout(using closure: (LayoutProxy) -> Void) {
translatesAutoresizingMaskIntoConstraints = false
closure(LayoutProxy(view: self))
}
}
复制代码
看起来像动画调用 UIView.animate
label.layout {
$0.top.equal(to: button.bottomAnchor, offsetBy: 20)
$0.leading.equal(to: button.leadingAnchor)
$0.width.lessThanOrEqual(to: view.widthAnchor, offsetBy: -40)
}
复制代码
将第 2 步的调用方法,用操做符号替换
加和减,把约束和偏移,结合成元组 tuple
// 加
func +<A: LayoutAnchor>(lhs: A, rhs: CGFloat) -> (A, CGFloat) {
return (lhs, rhs)
}
// 减
func -<A: LayoutAnchor>(lhs: A, rhs: CGFloat) -> (A, CGFloat) {
return (lhs, -rhs)
}
复制代码
3 种状况 X 2 种条件
// 等于, 使用 == ,看成 =
// 右边参数,含偏移
func ==<A: LayoutAnchor>(lhs: LayoutProperty<A>,
rhs: (A, CGFloat)) {
lhs.equal(to: rhs.0, offsetBy: rhs.1)
}
// 等于, 使用 == ,看成 =
func ==<A: LayoutAnchor>(lhs: LayoutProperty<A>, rhs: A) {
lhs.equal(to: rhs)
}
// 不小于,
// 右边参数,含偏移
func >=<A: LayoutAnchor>(lhs: LayoutProperty<A>,
rhs: (A, CGFloat)) {
lhs.greaterThanOrEqual(to: rhs.0, offsetBy: rhs.1)
}
// 不小于
func >=<A: LayoutAnchor>(lhs: LayoutProperty<A>, rhs: A) {
lhs.greaterThanOrEqual(to: rhs)
}
// 不大于,
// 右边参数,含偏移
func <=<A: LayoutAnchor>(lhs: LayoutProperty<A>,
rhs: (A, CGFloat)) {
lhs.lessThanOrEqual(to: rhs.0, offsetBy: rhs.1)
}
// 不大于
func <=<A: LayoutAnchor>(lhs: LayoutProperty<A>, rhs: A) {
lhs.lessThanOrEqual(to: rhs)
}
复制代码
label.layout {
$0.top == button.bottomAnchor + 20
$0.leading == button.leadingAnchor
$0.width <= view.widthAnchor - 40
}
复制代码
命名空间能够长这个样子,NamespaceWrapper(val: view)
public protocol TypeWrapper{
associatedtype WrappedType
var wrapped: WrappedType { get }
init(val: WrappedType)
}
public struct NamespaceWrapper<T>: TypeWrapper{
public let wrapped: T
public init(val: T) {
self.wrapped = val
}
}
复制代码
extension TypeWrapper where WrappedType: UIView {
func layout(using closure: (LayoutProxy) -> Void) {
wrapped.translatesAutoresizingMaskIntoConstraints = false
closure(LayoutProxy(view: wrapped))
}
var bottom: NSLayoutYAxisAnchor{
wrapped.bottomAnchor
}
var leading: NSLayoutXAxisAnchor{
wrapped.leadingAnchor
}
var width: NSLayoutDimension{
wrapped.widthAnchor
}
var centerX: NSLayoutXAxisAnchor{
wrapped.centerXAnchor
}
var centerY: NSLayoutYAxisAnchor{
wrapped.centerYAnchor
}
}
复制代码
调用效果长这样,日常见不到的
NamespaceWrapper(val: label).layout {
$0.top == NamespaceWrapper(val: button).bottom + 20
// ...
}
复制代码
NamespaceWrapper(val: view)
变成咱们常见的 view.put
( 视图布局有放置的含义,这里用 put )
弄一胶水协议 NamespaceWrap
完成这个转换,UIView 遵照这个协议。
public protocol NamespaceWrap{
associatedtype WrapperType
var put: WrapperType { get }
}
public extension NamespaceWrap{
var put: NamespaceWrapper<Self> {
return NamespaceWrapper(val: self)
}
}
extension UIView: NamespaceWrap{ }
复制代码
label.put.layout {
$0.top == button.put.bottom + 20
$0.leading == button.put.leading
$0.width <= view.put.width - 40
}
复制代码