SwiftUI

DynamicProperty和propertyWrapper介

2022-09-16  本文已影响0人  stonly916

本文分两部分,第一部分是介绍常用的属性包装器,第二步部分是自定义属性包装器 + 动态属性分析

一、SwiftUI常用的属性包装器:

@AppStorage: 全局生效(除App层级),全局发送更新通知,直接操作UserDefaults生效;可存储配置(轻量)数据;
@SceneStorage: 作用域位为所有SwiftUI视图,可在界面内存储轻量数据,界面注销(非app关闭)则数据清除;
@State: 作用域位为SwiftUI视图模块内,仅支持值类型;
@ObservedObject:作用域位为SwiftUI视图模块内,支持class对象,作为小范围内的初始数据源,视图刷新会销毁重建;
@StateObject: 同@ObservedObject,但是属于静态变量,视图刷新不会销毁;
@EnvironmentObject: 自定义环境对象,使用时需注入给具体视图,可减少初始化,方便切换不同数据源等;
@Environment: 系统环境变量,不需要注入(若手动增加变量,则仍需注入给具体视图)
@FocusedValue: 用于输入框的绑定/读取输入内容

由于结构体内的属性不可变,所以当想创建可变属性时,需要使用mutating关键字,但swift不允许我们编写mutating var body: some View,那怎么改变视图呢,这里就需要属性包装器了

@AppStorage:
@frozen @propertyWrapper public struct AppStorage<Value> : DynamicProperty {
// 包装属性
    public var wrappedValue: Value { get nonmutating set }
// 投影属性 可使用 $ 加在属性前来使用
    public var projectedValue: Binding<Value> { get }
}
// 创建包装属性
let wrapped_age = AppStorage(wrappedValue: 12, "age");
@AppStorage("age") var age = "12"
@SceneStorage
@SceneStorage("selected") var index = 0
@ObservedObject
class Person:ObservableObject{
    @Published var name = "张三"
}
@ObservedObject var p = Person()
@StateObject

...

写了几个小时文章,发布的时候内容不见了,吐了,不想写了,直接贴代码看吧!!!

注意:
避免非必要的包装器声明:只要声明了,就算不使用,其发送的更新通知,会导致View发生不必要的更新。

二、DynamicProperty运作原理

  1. @propertyWrapper:声明,声明了包装值和投影值,
    • 需要包装类
    • 需要包装值
  2. DynamicProperty:动态属性协议,包装器和动态属性并不一样,但大多是关联在一起的,
    • update(可省略)函数,更新发布器的被订阅值,
    • _makeProperty: 该函数的默认实现只在包装器内生效,包装器有确定的包装值即动态值,该函数将动态属性加入到视图的动态属性池并与视图状态关联,因而更新动态值可以更新视图;
      一般propertyWrapper用于修饰继承DynamicProperty(动态属性协议)的struct,

DynamicProperty协议:

public protocol DynamicProperty {
// 关联动态属性和视图
  static func _makeProperty<V>(in buffer: inout _DynamicPropertyBuffer, container: _GraphValue<V>, fieldOffset: Int, inputs: inout _GraphInputs)
// 属性行为
  static var _propertyBehaviors: UInt32 { get }
// 更新属性值
  mutating func update()
}
  1. @propertyWrapper:声明,声明了包装值和投影值,
  1. DynamicProperty:动态属性协议,包装器和动态属性并不一样,但大多是关联在一起的,
动态属性包装器:

从包装属性到更新视图的流程:

  1. View初始化
  2. 数据依赖实例化: 包装器/动态属性初始化:
  3. 获取视图状态
  4. 更新动态属性,关联视图状态
  1. 构建视图
  2. ObservableObject:被订阅的发布器;修改动态属性值,
  3. objectWillChange.send() 发送 值即将变更通知,
  4. struct-View-DynamicPropertyBuffer:动态属性池接收到 值即将变更通知,视图状态 收到 视图状态值 即将变更,
  5. struct-DynamicProperty: 调用DynamicProperty-update()主动更新动态值,若有需要的话,
  6. struct-View(body): 动态属性值发生变更,视图状态值变更,
  7. 视图(状态)更新
模拟 @AppStorage 属性包装器:
@propertyWrapper
struct MyUserDefault<Value: Equatable>: DynamicProperty {
    private var manager: RecordManager = .shared // 8
    private let defaultValue: Value // 16
//    @ObservedObject private var record: RecordValues2<Value> // 16
    @StateObject private var record: RecordValue<Value> // // 16(结构体) + 8(对象)
    var wrappedValue:Value {
        get {
            return record.wrappedValue ?? defaultValue
        }
        nonmutating set {
            record.wrappedValue = newValue
        }
    }
    
    var projectedValue: Binding<Value> {
        Binding {
            wrappedValue
        } set: {
            wrappedValue = $0
        }
    }
    
    init(wrappedValue value: Value,_ key: String) {
        defaultValue = value
        let rec = manager.pressRecord(key: key) as! RecordValue<Value>
        // @StateObject包装的属性是只读属性,无法正常赋值
        self._record = StateObject(wrappedValue: rec)
        // @ObservedObject包装可以直接赋值
//        record = rec
    }
}

public class RecordValue<Value: Equatable>: ObservableObject {
    let info: UserDefaults = .standard
    let key: String

    var wrappedValue: Value? {
        get {
            // 存储于UserDefaults中,轻量缓存
            return info.object(forKey: key) as? Value
        }
        set {
            guard wrappedValue != newValue else { return }
            
            objectWillChange.send()
            // 存储于UserDefaults中,轻量缓存
            self.info.set(newValue, forKey: key)
            
        }
    }

    init(key: String) {
        self.key = key
    }
}

class RecordManager {
    var info = [String: AnyObject]()
    static let shared = RecordManager()
    
    func pressRecord<T>(key: String) -> RecordValue<T> {
        var obj = info[key]
        if obj == nil {
            obj = RecordValue<T>(key: key)
            info[key] = obj
        }
        return obj as! RecordValue<T>
    }
}
上一篇下一篇

猜你喜欢

热点阅读