iOS-写一个简单的布局DSL

1,011 阅读1分钟

DSL 是什么?

DSL 其实是 Domain Specific Language 的缩写,中文翻译为领域特定语言(下简称 DSL);而与 DSL 相对的就是 GPL,这里的 GPL 并不是我们知道的开源许可证,而是 General Purpose Language 的简称,即通用编程语言,也就是我们非常熟悉的 Objective-C、Java、Python 以及 C 语言等等。

A specialized computer language designed for a specific task.

为了解决某一类任务而专门设计的计算机语言。

  • 总的来说:DSL 基于一门语言,专门解决某一个问题。适合声明式,规则明确的场景,类似SnapKit

使用LayoutAnchor来布局

   self.view .addSubview(label)
   label.text = "测试"
   label.backgroundColor = UIColor.orange;
        
    NSLayoutConstraint.activate([
       label.topAnchor.constraint(
       equalTo: view.bottomAnchor,
       constant: 20
     ),
    label.leadingAnchor.constraint(
       equalTo: view.leadingAnchor,
       constant: 40
     ),
    label.widthAnchor.constraint(
       lessThanOrEqualTo: view.widthAnchor,
       constant: -20
     )
   ])

使用DSL后,代码就成了

label.layout {

     $0.top.equal(to: view.topAnchor, offsetBy: 90)
     $0.leading.equal(to: view.leadingAnchor,offsetBy: 100)
     $0.width.lessThanOrEqual(to: view.widthAnchor, offsetBy: -40)
}

布局代码少了很多,符号更加直观了,拥有新的语法糖了


如何构造一个简单的布局DSL

  1. 建立功能协议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 {}

2.建立一个上层类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)
    }
}
  1. 增加一个结构体LayoutProperty,LayoutProperty包了个遵守LayoutAnchor的属性anchor
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
    }
}

  1. 采用闭包,建立上下文环境,封装代码
   extension UIView {
    func layout(using closure: (LayoutProxy) -> Void) {
        translatesAutoresizingMaskIntoConstraints = false
        closure(LayoutProxy(view: self))
    }
}

大功告成

  label.layout{ make in
     make.top.equal(to: view.topAnchor, offsetBy: 90)
     make.leading.equal(to: view.leadingAnchor,offsetBy: 100)
     make.width.lessThanOrEqual(to: view.widthAnchor, offsetBy: -40)
   }

你也可以直接写成

  label.layout {
     $0.top.equal(to: view.topAnchor, offsetBy: 90)
     $0.leading.equal(to: view.leadingAnchor,offsetBy: 100)
     $0.width.lessThanOrEqual(to: view.widthAnchor, offsetBy: -40)
   }