DSL, Domain Specific Language, Domain Specific Language
A programming language, Turing-complete, with both functionality and performance. For example, Swift,
DSLS are based on a language and are designed to solve a particular problem. Ideal for declarative, rule-specific scenarios
On this issue, the grammar is concise and easy to handle. Such as SnapKit
DSL, simple to write, improve development efficiency. Establish context domains that hide a lot of implementation details. This code is less, not long. In general, the compile time of your code increases
Swift has the advantages of type derivation function Type refer, protocol-based programming POP and operator overloading, which makes it convenient to develop ITS DSL.
The namespace comes last
This article uses a view layout as an example:
Native layout, using LayoutAnchor
label.translatesAutoresizingMaskIntoConstraints = falseNSLayoutConstraint. Activate ([/ / at the top of the label, from the bottom of the button, 20 pt label. TopAnchor. The constraint (equalTo: Button. BottomAnchor, constant: 20), / / to the left of the label, align the left side of the button label. LeadingAnchor. The constraint (equalTo: Button. LeadingAnchor), / / the width of the label, no more than the width of the button - 40 pt label. WidthAnchor. The constraint (lessThanOrEqualTo: view.widthAnchor, constant: -40 ) ])Copy the code
With the DSL built in this article, there is much less layout code and more intuitive notation
// put {label.put. Layout {$0.top == button.put.bottom + 20
$0.leading == button.put.leading
$0.width <= view.put.width - 40
}
Copy the code
The first step is to encapsulate native layout functionality,LayoutAnchor
The functional protocol LayoutAnchor needs to be established to extract and merge 6 layout methods of iOS system into 3.
NSLayoutAnchor is a generic class. Each specific constraint anchor, along with the specific NSLayoutAnchor class, has its own associated protocol. The implementation details are complicated.
Establish the functional protocol LayoutAnchor to screen out the tedious details
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 {}
Copy the code
Create an upper classLayoutProxy
In the native layout method, wrap one layer. This calls the syntax less
Get the attributes first,
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)
}
}
Copy the code
Then call the layout method
Encapsulate a layer, change the native method name
Add a structureLayoutProperty
, which packages a property anchor that complies with LayoutAnchor. In this way, NSLayoutAnchor can be directly added to NSLayoutAnchor without directly operating NSLayoutAnchor, which is more elegant
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}}Copy the code
Results after the first step:
Call syntax, slightly refined
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)
Copy the code
Step 2: Use closures to set up the layout context and encapsulate the code that the layout calls
The context tells us what’s going on here. Easy to understand
Manually create a layout object, let proxy = LayoutProxy(View: Label), and then specific layout
Boiler plate, still a little bit more. It’s not very elegant to repeat the same routine every time.
Use Swift’s closure to set up the execution context, which is a bit more DSL
Context, such as SnapKit.
If you see.snp{}, you know what’s going on. Here, only the layout will be relevant, will not do other
Add extension methods to UIView, configure UIView, and execute the closure of LayoutProxy
extension UIView {
func layout(using closure: (LayoutProxy) -> Void) {
translatesAutoresizingMaskIntoConstraints = false
closure(LayoutProxy(view: self))
}
}
Copy the code
Effect after Step 2: Compare DSLS
Looks like an animation call to 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)
}
Copy the code
Step 3: Operator overloading to further simplify the syntax
Replace the method called in step 2 with an action symbol
Add and subtract, combine constraints and offsets into a tuple
// add func +<A: LayoutAnchor>(LHS: A, RHS: CGFloat) -> (A, CGFloat) {return(LHS, RHS)} // subtract func -<A: LayoutAnchor>(LHS: A, RHS: CGFloat) -> (A, CGFloat) {return (lhs, -rhs)
}
Copy the code
Whether X is offset in the three cases where the constraint is in effect
There are 3 possibilities X there are 2 possibilities
Func ==<A: LayoutAnchor>(LHS: LayoutProperty<A>, RHS: (A, CGFloat)) {lhs.equal(to: RHS.0, offsetBy: RHS.1)} // equal to, use ==, as = func ==<A: LayoutAnchor>(LHS: LayoutProperty<A>, RHS: A) {LHS. Func >=<A: LayoutAnchor>(LHS: LayoutProperty<A>, RHS: LayoutProperty<A>, (A, CGFloat)) {lhs.greaterThanorequal (to: hs.0, offsetBy: HS.1)} // not less than func >=<A: LayoutAnchor>(LHS: LayoutProperty<A>, RHS: A) {lhs.greaterThanoreQual (to: RHS)} // Not greater than, // Right argument with offset func <=<A: LayoutAnchor>(LHS: A) LayoutProperty<A>, RHS: (A, CGFloat)) {lhs.lessthanoreQual (to: rhs.0, offsetBy: rhS.1)} // No greater than func <=<A: LayoutAnchor>(lhs: LayoutProperty<A>, rhs: A) { lhs.lessThanOrEqual(to: rhs) }Copy the code
Effect after Step 3: DSL
label.layout {
$0.top == button.bottomAnchor + 20
$0.leading == button.leadingAnchor
$0.width <= view.widthAnchor - 40
}
Copy the code
Step 4: Add the namespace
Namespaces, which may seem very large, actually encapsulate one layer
A namespace can look like this: NamespaceWrapper(val: view)
Encapsulation structure,
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
}
}
Copy the code
Add functionality to the structure
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
}
}
Copy the code
Call effect looks like this, usually do not see
NamespaceWrapper(val: label).layout {
$0.top == NamespaceWrapper(val: button).bottom + 20
// ...
}
Copy the code
theNamespaceWrapper(val: view)
To become common to usview.put
(View layout has the meaning of placing, here we use put)
Put a glue protocol called NamespaceWrap to do this transformation, and UIView obeys that protocol.
public protocol NamespaceWrap{
associatedtype WrapperType
var put: WrapperType { get }
}
public extension NamespaceWrap{
var put: NamespaceWrapper<Self> {
return NamespaceWrapper(val: self)
}
}
extension UIView: NamespaceWrap{ }
Copy the code
Effect after Step 4: DSL
label.put.layout {
$0.top == button.put.bottom + 20
$0.leading == button.put.leading
$0.width <= view.put.width - 40
}
Copy the code