DynamicProperty和propertyWrapper介绍和深入理解

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

一、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"
  • 用于操作UserDefaults的属性包装器,
  • App层级注册属性无效,
  • 在SwiftUI和UIView视图中都可以生效,
  • 直接修改UserDefaults可以对SwfitUI中绑定生效
  • 目前仅支持:Bool、Int、Double、String、URL、Data(UserDefaults支持更多的类型)。
  • @AppStorage还支持符合RawRepresentable协议且RawValue为Int或String的数据类型。
@SceneStorage
@SceneStorage("selected") var index = 0
  • 同@AppStorage十分类似,不过其作用域仅限于当前Scene
  • Scene退出时若app未退出,则数据失效
  • app退出时数据会持久化
@ObservedObject
class Person:ObservableObject{
    @Published var name = "张三"
}
@ObservedObject var p = Person()
  • ObservableObject 协议要求实现类型是 class,它需要实现一个属性:objectWillChange = ObservableObjectPublisher(),使用@Published可以自动实现。
  • 在数据将要发生改变时,会向外进行“广播”
  • 只是作为View的数据依赖,包装器被动态属性池持有,但是包装器内的动态属性不被持有,View更新视图时视图状态(值)重新获取,ObservedObject对应的动态属性可能会被销毁重建,
@StateObject
  • StateObject行为类似ObservedObject对象
  • 动态属性为只读属性,只能修改动态属性的子属性
  • 针对引用类型设计,当View更新时,实例不会被销毁,和视图的动态属性池绑定,

...

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

注意:
避免非必要的包装器声明:只要声明了,就算不使用,其发送的更新通知,会导致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:动态属性协议,包装器和动态属性并不一样,但大多是关联在一起的,
  • update(可省略)函数,更新发布器的被订阅值,
  • _makeProperty,该函数的默认实现只在包装器内生效,包装器有确定的包装值即动态值,该函数将动态属性加入到视图的动态属性池并与视图状态关联,因而更新动态值可以更新视图;
动态属性包装器:

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

  1. View初始化
  2. 数据依赖实例化: 包装器/动态属性初始化:
  3. 获取视图状态
  4. 更新动态属性,关联视图状态
  • 调用当前struct的动态属性对应的_makeProperty函数;
  • 若动态属性a内也有其他动态属性b,则调用属性b的_makeProperty函数,
  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>
    }
}
  • RecordValue:用于在 UserDefaults 中存取数据,并手动发送 值变更通知
  • RecordManager:使用键值对纳用发布器,不同包装器只要key值一样,就会使用同一个发布器(RecordValue)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容