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
}
複製代碼