SwiftUI学习->语法糖

1,082 阅读4分钟

说明

简单的语法是一个声明式 UI 框架的基石,让 SwiftUI 能够像写 HTML 一样简单的关键就是以下这些 Swift 新语法特性

SwiftUI Syntax

  • ResultBuilder
  • ViewBuilder
  • Trailing Closure
  • Generic/Opaque Type
  • Inline

State Syntax

  • PropertyWrapper
  • KeyPath
  • DynamicMemberLookup

SwiftUI Syntax

ResultBuilder

Result Builder是一个自定义的类型,使用声明式语法描述绘制过程的方式来创建嵌套数据,比如链表和树。

protocol Drawable {
    func draw() -> String
}

struct Line: Drawable {
    func draw() -> String {
        return elements.map { $0.draw() }.joined(separator: "-")
    }
    var elements: [Drawable]
}

struct Texts: Drawable {
    func draw() -> String {
        return content
    }
    init(_ content: String) {
        self.content = content
    }
    var content: String
}

@resultBuilder
struct DrawingBuilder {
    static func buildBlock(_ components: Drawable...) -> Drawable {
        return Line(elements: components)
    }
}

struct ContentView : View{
    //@resultBuilder方式
    var body: some View{
        Text("\(makeGreeting().draw())")
    }
    
    func draw(@DrawingBuilder content: () -> Drawable ) -> Drawable {
        return content()
    }
    
    func makeGreeting() -> Drawable {
        let greeting = draw(content: {
            Texts("Hello")
            Texts("!")
            }
        )
        return greeting
    }
}

ViewBuilder

允许闭包中提供多个子视图,结合ViewBuilder和便利构造函数使代码更优美。可以在项目中通过ViewBuilder注解和便利构造函数把许多具体相同特点的View封装起来,并且分离逻辑代码和视图,提升代码的可复用性,并增强可读性。

struct childView<Content: View>: View {
    let content: Content
    
    init(@ViewBuilder content: () -> Content) {
        self.content = content()
    }
    
    var body: some View {
        content
            .background(Color.red)
            .cornerRadius(5)
    }
}

struct ContentView: View {
    var body: some View {
        childView(content: {
            Text("Hello")
        })
    }
}

Trailing Closure

尾随闭包是闭包的一种简写方式。当一个闭包作为另一个函数的最后一个参数时,可以使用尾随闭包,省略参数名。换言之,把闭包作为函数的最后一个参数时,可以使用尾随闭包,比较方便。

Generic/Opaque Type

Generic 让调用者决定参数或者返回值的类型
Opaque Type 是让方法本身决定返回的类型

protocol Animal {
    associatedtype NameType // 动物名字类型占位符,即:协议实现泛型的方式(我们可能不清楚这个动物的名字的类型是啥,所以这里需要定义一个泛型,让具体实现的动物类中自己决定)
    var name: NameType { get set } // 动物的名字属性,其类型为NameType泛型
    
    func eat(food: String)
}

struct Dog: Animal{
    
    var name: String
    
    func eat(food: String) {
        print("\(name) 吃了 \(food)")
    }
}

struct Cat: Animal{
    
    var name: String
    
    func eat(food: String) {
        print("\(name) 吃了 \(food)")
    }
}

struct Zoo{
    
    func dog() -> some Animal {
        return Dog(name: "旺财")
    }
    
    func cat() -> some Animal {
        return Cat(name: "小花")
    }
}


func runTest() -> Void{
    
    let zoo = Zoo()
    
    let dog = zoo.dog()
    dog.eat(food: "骨头")
    
    let cat = zoo.cat()
    cat.eat(food: "鱼")
}

Inline

函数内联 是一种编译器优化技术,它通过使用方法的内容替换直接调用该方法,就相当于假装该方法并不存在一样,这种做法在很大程度上优化了性能

@inline(never) 声明这个函数never编译成inline的形式

@inline(__always) 声明这个函数总是编译成inline的形式

inline函数可以用 闭包 的形式实现

    @inline(__always) func log(){
        print("hello")
    }
    @inline(never) func log(){
        print("hello")
    }

State Syntax

PropertyWrapper

它的作用对象属性,其主旨就是:通过property Wrapper机制,对一些类似的属性的实现代码做同一封装

@propertyWrapper // 先告诉编译器 下面这个UserDefault是一个属性包裹器
struct UserDefault<T> {
    //这里的属性key 和 defaultValue 还有init方法都是实际业务中的业务代码
    //我们不需要过多关注
    let key: String
    let defaultValue: T
    
    init(_ key: String, defaultValue: T) {
        self.key = key
        self.defaultValue = defaultValue
    }
    //  wrappedValue是@propertyWrapper必须要实现的属性
    // 当操作我们要包裹的属性时  其具体set get方法实际上走的都是wrappedValue 的set get 方法。
    var wrappedValue: T {
        get {
            return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
        }
        set {
            UserDefaults.standard.set(newValue, forKey: key)
        }
    }
}

//封装一个UserDefault配置文件
struct UserDefaultsConfig {
    //实际写法就是在UserDefault包裹器的初始化方法前加了个@
    @UserDefault("name_Key", defaultValue: false)
    static var nameKey: Bool
    
    @UserDefault("password_Key", defaultValue: "unknown")
    static var passwordKey: String
}
//调用
func done(){
        ///具体的业务代码。
        UserDefaultsConfig.nameKey = true
        UserDefaultsConfig.passwordKey = "123"
    }

KeyPath

Swift 中可以使用 KeyPath 来获取属性值,语法为\Type.property.property.....,能使用 KVC 的方法 value(forKey:) 、value(forKeyPath:) 和 KVO 的方法 addObserver(_:forKeyPath:options:context:)

class Animal: NSObject {
    @objc var name: String
    
    init(name: String) {
        self.name = name
    }
}
//使用
func done(){
     let llama = Animal(name: "Llama")
     let nameAccessor = \Animal.name
     let nameCountAccessor = \Animal.name.count
         
     _ = llama[keyPath: nameAccessor]
     // "Llama"
     _ = llama[keyPath: nameCountAccessor]
     //5
}

DynamicMemberLookup

动态成员查找, 简单理解其目的就是动态来获取属性的值

@dynamicMemberLookup
    struct Worker {
        // 必须要实现的方法。返回值类型可自定义
            subscript(dynamicMember member: String) -> String {
                let properties = ["name": "LiaoWorking", "city": "Shanghai"]
                return properties[member, default: ""]
            }
    }
    
//使用
func done(){
     let worker = Worker()
     let _ = worker.name    // liaoworking
}