Avec Swift 5.1, il est désormais possible d’utiliser un outil : les Function Builders. C’est un outil pour pouvoir créer des DSL custom comme en SwiftUI (qui l’utilise très largement…).
Cette nouvelle fonctionnalité est basé sur le « Design Pattern » appelé Monteur (Builder Patern) :
Le monteur (builder) est un patron de conception utilisé pour la création d’une variété d’objets complexes à partir d’un objet source. L’objet source peut consister en une variété de parties contribuant individuellement à la création de chaque objet complet grâce à un ensemble d’appels à l’interface commune de la classe abstraite Monteur.
Pour créer un « Function Builder », tout commence par l’introduction d’un nouveau mot-clé : @_functionBuilder ou bien @functionBuilder.
Ensuite, il faut définir une ou plusieurs surcharges de la function « buildBlock ».
Exemple avec SwiftUI (ne compile pas) :
@functionBuilder
struct ViewBuilder {
static func buildBlock() -> EmptyView {
return EmptyView()
}
static func buildBlock<V: View>(_ view: V) -> some View {
return view
}
static func buildBlock<A: View, B: View>(
_ viewA: A,
_ viewB: B
) -> some View {
return TupleView((viewA, viewB))
}
}
Mais il est aussi possible d’utiliser les variadics quand on ne sait combien de paramètres seront passés en arguments.
Exemple avec SwiftUI (ne compile pas) :
@functionBuilder
struct ViewBuilder {
static func buildBlock(_ views: View...) -> CombinedView {
return CombinedView(views: views)
}
}
Ce qui donne pour SwiftUI en réalité :
struct VStack<Content: View>: View {
init(@ViewBuilder builder: () -> Content) {
// A function builder closure can be called just like
// any other, and the resulting expression can then be
// used to, for instance, construct a container view.
let content = builder()
...
}
}
struct HeaderView: View {
let image: UIImage
let title: String
let subtitle: String
var body: some View {
VStack {
Image(uiImage: image)
Text(title)
Text(subtitle)
}
}
}
Au lieu de :
struct HeaderView: View {
let image: UIImage
let title: String
let subtitle: String
var body: some View {
var builder = VStackBuilder()
builder.add(Image(uiImage: image))
builder.add(Text(title))
builder.add(Text(subtitle))
return builder.build()
}
}
Voici un exemple pour créer un « Function Builder »pour agréger plusieurs NSAttributedString (ce qui avouons-le est très pénible la plupart du temps…)
import UIKit
@_functionBuilder
class NSAttributedStringBuilder {
static func buildBlock(_ components: NSAttributedString...) -> NSAttributedString {
let result = NSMutableAttributedString(string: "")
return components.reduce(into: result) { (result, current) in result.append(current) }
}
}
extension NSAttributedString {
class func composing(@NSAttributedStringBuilder _ parts: () -> NSAttributedString) -> NSAttributedString {
return parts()
}
}
let result = NSAttributedString.composing {
NSAttributedString(string: "Hello",
attributes: [.font: UIFont.systemFont(ofSize: 24),
.foregroundColor: UIColor.red])
NSAttributedString(string: " world!",
attributes: [.font: UIFont.systemFont(ofSize: 20),
.foregroundColor: UIColor.orange])
}
Sources