学习 DSL

1,039 阅读3分钟

「这是我参与11月更文挑战的第7天,活动详情查看:2021最后一次更文挑战」。

DSL

DSL(Domain Specific Language) 翻译成中文就是:“领域特定语言”。首先从定义就可以看出,DSL 也是一种编程语言,只不过它主要是用来处理某个特定领域的问题。那么我们在学习iOS最先接触DSL是在哪里呢?我觉得是在CocoaPods里,但是那时候的状态就像他认识了我,而我不认识他(关于CocoaPods dsl 可以看这篇文章)。当我开始认识他的时候是在Masonry这个布局库中。在Masonry的介绍里面有这样一段话:

Masonry is a light-weight layout framework which wraps AutoLayout with a nicer syntax. Masonry has its own layout DSL which provides a chainable way of describing your NSLayoutConstraints which results in layout code that is more concise and readable. Masonry supports iOS and Mac OS X.

从这里我看见了DSL一词去了解他,也去学习了他。并且在wwdc2021Write a DSL in Swift using result builders介绍了通过在 Swift使用结果构建器编写DSL以及讲解了DSL,建议大家看一下。

NSAttributedString DSL

上篇我们已经写了在构建Layout DSL,那么这篇我们尝试构NSMutableAttributedString DSL。首先我们写一个NSAttributedString扩展来封装:

public extension NSAttributedString {
    /// Sets the color of this text
    @discardableResult
    func foregroundColor(_ color: UIColor) -> NSAttributedString {
        let mutableAtt = NSMutableAttributedString(string: self.string, attributes: self.attributes(at: 0, effectiveRange: nil))
        mutableAtt.addAttributes(attributes, range: NSMakeRange(0, (self.string as NSString).length))
        return mutableAtt
    }
    
    .....
}

//这里我们发现其他的属性backgroundunderline等等都是用同样的代码封装,所以我们进行提取封装成如下:

public extension NSAttributedString {
    /// Sets the color of this text
    @discardableResult
    func foregroundColor(_ color: UIColor) -> NSAttributedString {
        self.addStyle([.foregroundColor : color])
    }
    
    .....
    
    func addStyle(_ attributes: [NSAttributedString.Key:Any]) -> NSAttributedString {
       let mutable = NSMutableAttributedString(string: self.string, attributes: self.attributes(at: 0, effectiveRange: nil))
        mutable.addAttributes(attributes, range: NSMakeRange(0, (self.string as NSString).length))
        return mutable
    }
}

这样我们在使用的时候这样写:

let att1 = NSMutableAttributedString(string: "Hello ")
att1.foregroundColor(.blue).background(.orange)

let att2 = NSAttributedString(string: "World")
att2.foregroundColor(.blue).background(.orange)

这样我们实现了单个 NSAttributedString实现了链式调用。但是如果多个NSAttributedString 合并:

att1.append(att2) //后面不停的append

所以这里我们可以进行合并封装,不过这里要用到Swift 一个特性:Function Builder ,他会很大程度增强了 Swift 语言构建内置 DSL 的能力。具体关于Function Builder的介绍,可以看这篇文章。这里介绍下面用到的俩个:

  • static func buildBlock(_ components: Component...) -> Component 将可变数量的结果合并为一结果。

  • static func buildOptional(_ component: Component?) -> Component 将可选的中间结果构造成新的中间结果。用于支持不包含 else 闭包的 if 表达式。

@resultBuilder
public struct AttributedStringBuilder {
    @discardableResult
    public static func buildBlock(_ components: NSAttributedString...) -> NSAttributedString {
        let string = NSMutableAttributedString()
        components.forEach { string.append($0) }
        return string
    }
    
    @discardableResult
    public static func buildOptional(_ component: NSAttributedString?) -> NSAttributedString {
       guard let component = component else { return component }
       return component
    }
}

public extension NSAttributedString {
    @discardableResult
    convenience init(@AttributedStringBuilder _ content: () -> NSAttributedString) {
        self.init(attributedString: content())
    }
}

这里多说一句在swift 对于Result Builders进行了升级,具体可以看result builders。同时对于swift 版本升级的变化,可以看这篇文章

这样我们就能将多个NSAttributedString 合并了:

NSAttributedString {
            att1
            att2
}

当然,我们还可以给字符串添加扩展,来省略这样一句话:

let att1 = NSMutableAttributedString(string: "Hello ")
public extension String {

    /// Sets the color of this text
    func foregroundColor(_ color: UIColor) -> NSAttributedString {
        NSAttributedString(string: self, attributes: [.foregroundColor : color])
    }
  
    .......
    
    /// Returns this string as an `NSAttributedString`
    var attributed: NSAttributedString {
        NSAttributedString(string: self)
    }
}

我们就可以这样书写了:

NSAttributedString {
  "Hello ".foregroundColor(.blue)
  "World".foregroundColor(.blue)
}

到此关于DSL的介绍就结束了。