This is the 7th day of my participation in the Gwen Challenge in November. Check out the details: The Last Gwen Challenge in 2021.

DSL

Domain Specific Language (DSL) is translated into Chinese as “Domain Specific Language”. A DSL, by definition, is also a programming language, but it is primarily used to deal with problems in a specific domain. So where did we first get DSL in iOS? I think it was in CocoaPods, but at the time it was like he knew me and I didn’t know him (see this article for the CocoaPods DSL). I first got to know him in the navigation library. I would like to retrieve the following text:

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.

From here I saw the word DSL to understand him, but also to learn from him. Wwdc2021Write a DSL in Swift Using Result Builders introduces how to write A DSL using the Result Builder in Swift and explains DSLS.

NSAttributedString DSL

Last we already wrote in building Layout DSL, so this article we try to construct NSMutableAttributedString DSL. First we write an NSAttributedString extension to encapsulate:

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
    }
    
    .....
}
Copy the code

// Here we find that other attributes, such as background and underline, are encapsulated with the same code, so we extract and encapsulate them as follows:

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
    }
}
Copy the code

So when we use it, we write:

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

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

So we’ve implemented a single NSAttributedString and we’ve implemented a chain call. But if multiple NSAttributedStrings are merged:

Att1. Append (att2) // AppendCopy the code

So we can do merge encapsulation here, but we will use a Swift feature: Function Builder, which greatly enhances the ability of the Swift language to build built-in DSLS. See this article for an introduction to Function Builder. Here are two of them:

  • static func buildBlock(_ components: Component...) -> ComponentTo combine a variable number of results into one result.
  • static func buildOptional(_ component: Component?) -> Component

Construct the optional intermediate result into a new intermediate result. Use to support if expressions that do not contain else closures.

@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())
    }
}

Copy the code

Swift has upgraded Result Builders, see Result Builders. In the meantime, for changes to the Swift version update, see this article

So we can merge multiple NSAttributedStrings:

NSAttributedString {
            att1
            att2
}
Copy the code

Of course, we can also add an extension to the string to omit a sentence like this:

let att1 = NSMutableAttributedString(string: "Hello ")
Copy the code
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)
    }
}
Copy the code

We can write like this:

NSAttributedString {
  "Hello ".foregroundColor(.blue)
  "World".foregroundColor(.blue)
}
Copy the code

This concludes our introduction to DSLS.