DynamicProperty和propertyWrapper介紹和深入理解

本文分兩部分,第一部分是介紹常用的屬性包裝器,第二步部分是自定義屬性包裝器 + 動態(tài)屬性分析

一任斋、SwiftUI常用的屬性包裝器:

@AppStorage: 全局生效(除App層級),全局發(fā)送更新通知耻涛,直接操作UserDefaults生效;可存儲配置(輕量)數(shù)據(jù);
@SceneStorage: 作用域位為所有SwiftUI視圖废酷,可在界面內(nèi)存儲輕量數(shù)據(jù),界面注銷(非app關(guān)閉)則數(shù)據(jù)清除抹缕;
@State: 作用域位為SwiftUI視圖模塊內(nèi)澈蟆,僅支持值類型;
@ObservedObject:作用域位為SwiftUI視圖模塊內(nèi)卓研,支持class對象趴俘,作為小范圍內(nèi)的初始數(shù)據(jù)源,視圖刷新會銷毀重建鉴分;
@StateObject: 同@ObservedObject哮幢,但是屬于靜態(tài)變量,視圖刷新不會銷毀志珍;
@EnvironmentObject: 自定義環(huán)境對象橙垢,使用時需注入給具體視圖,可減少初始化伦糯,方便切換不同數(shù)據(jù)源等;
@Environment: 系統(tǒng)環(huán)境變量柜某,不需要注入(若手動增加變量嗽元,則仍需注入給具體視圖)
@FocusedValue: 用于輸入框的綁定/讀取輸入內(nèi)容

由于結(jié)構(gòu)體內(nèi)的屬性不可變,所以當(dāng)想創(chuàng)建可變屬性時喂击,需要使用mutating關(guān)鍵字剂癌,但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 }
}
// 創(chuàng)建包裝屬性
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協(xié)議且RawValue為Int或String的數(shù)據(jù)類型刽肠。
@SceneStorage
@SceneStorage("selected") var index = 0
  • 同@AppStorage十分類似溃肪,不過其作用域僅限于當(dāng)前Scene
  • Scene退出時若app未退出,則數(shù)據(jù)失效
  • app退出時數(shù)據(jù)會持久化
@ObservedObject
class Person:ObservableObject{
    @Published var name = "張三"
}
@ObservedObject var p = Person()
  • ObservableObject 協(xié)議要求實現(xiàn)類型是 class音五,它需要實現(xiàn)一個屬性:objectWillChange = ObservableObjectPublisher()惫撰,使用@Published可以自動實現(xiàn)。
  • 在數(shù)據(jù)將要發(fā)生改變時放仗,會向外進(jìn)行“廣播”
  • 只是作為View的數(shù)據(jù)依賴润绎,包裝器被動態(tài)屬性池持有,但是包裝器內(nèi)的動態(tài)屬性不被持有诞挨,View更新視圖時視圖狀態(tài)(值)重新獲取,ObservedObject對應(yīng)的動態(tài)屬性可能會被銷毀重建呢蛤,
@StateObject
  • StateObject行為類似ObservedObject對象
  • 動態(tài)屬性為只讀屬性惶傻,只能修改動態(tài)屬性的子屬性
  • 針對引用類型設(shè)計,當(dāng)View更新時其障,實例不會被銷毀银室,和視圖的動態(tài)屬性池綁定,

...

寫了幾個小時文章励翼,發(fā)布的時候內(nèi)容不見了蜈敢,吐了,不想寫了汽抚,直接貼代碼看吧Wハ痢!造烁!

注意:
避免非必要的包裝器聲明:只要聲明了否过,就算不使用午笛,其發(fā)送的更新通知,會導(dǎo)致View發(fā)生不必要的更新苗桂。

二药磺、DynamicProperty運(yùn)作原理

  1. @propertyWrapper:聲明,聲明了包裝值和投影值煤伟,
    • 需要包裝類
    • 需要包裝值
  2. DynamicProperty:動態(tài)屬性協(xié)議癌佩,包裝器和動態(tài)屬性并不一樣,但大多是關(guān)聯(lián)在一起的便锨,
    • update(可省略)函數(shù)围辙,更新發(fā)布器的被訂閱值,
    • _makeProperty: 該函數(shù)的默認(rèn)實現(xiàn)只在包裝器內(nèi)生效鸿秆,包裝器有確定的包裝值即動態(tài)值酌畜,該函數(shù)將動態(tài)屬性加入到視圖的動態(tài)屬性池并與視圖狀態(tài)關(guān)聯(lián),因而更新動態(tài)值可以更新視圖卿叽;
      一般propertyWrapper用于修飾繼承DynamicProperty(動態(tài)屬性協(xié)議)的struct桥胞,

DynamicProperty協(xié)議:

public protocol DynamicProperty {
// 關(guān)聯(lián)動態(tài)屬性和視圖
  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:動態(tài)屬性協(xié)議贩虾,包裝器和動態(tài)屬性并不一樣,但大多是關(guān)聯(lián)在一起的沥阱,
  • update(可省略)函數(shù)缎罢,更新發(fā)布器的被訂閱值,
  • _makeProperty考杉,該函數(shù)的默認(rèn)實現(xiàn)只在包裝器內(nèi)生效策精,包裝器有確定的包裝值即動態(tài)值,該函數(shù)將動態(tài)屬性加入到視圖的動態(tài)屬性池并與視圖狀態(tài)關(guān)聯(lián)崇棠,因而更新動態(tài)值可以更新視圖咽袜;
動態(tài)屬性包裝器:

從包裝屬性到更新視圖的流程:

  1. View初始化
  2. 數(shù)據(jù)依賴實例化: 包裝器/動態(tài)屬性初始化:
  3. 獲取視圖狀態(tài)
  4. 更新動態(tài)屬性,關(guān)聯(lián)視圖狀態(tài)
  • 調(diào)用當(dāng)前struct的動態(tài)屬性對應(yīng)的_makeProperty函數(shù)枕稀;
  • 若動態(tài)屬性a內(nèi)也有其他動態(tài)屬性b询刹,則調(diào)用屬性b的_makeProperty函數(shù),
  1. 構(gòu)建視圖
  2. ObservableObject:被訂閱的發(fā)布器萎坷;修改動態(tài)屬性值凹联,
  3. objectWillChange.send() 發(fā)送 值即將變更通知,
  4. struct-View-DynamicPropertyBuffer:動態(tài)屬性池接收到 值即將變更通知哆档,視圖狀態(tài) 收到 視圖狀態(tài)值 即將變更蔽挠,
  5. struct-DynamicProperty: 調(diào)用DynamicProperty-update()主動更新動態(tài)值,若有需要的話虐呻,
  6. struct-View(body): 動態(tài)屬性值發(fā)生變更象泵,視圖狀態(tài)值變更寞秃,
  7. 視圖(狀態(tài))更新
模擬 @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(結(jié)構(gòu)體) + 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 中存取數(shù)據(jù),并手動發(fā)送 值變更通知
  • RecordManager:使用鍵值對納用發(fā)布器忽孽,不同包裝器只要key值一樣绑改,就會使用同一個發(fā)布器(RecordValue)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市兄一,隨后出現(xiàn)的幾起案子厘线,更是在濱河造成了極大的恐慌,老刑警劉巖出革,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件造壮,死亡現(xiàn)場離奇詭異,居然都是意外死亡骂束,警方通過查閱死者的電腦和手機(jī)耳璧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來展箱,“玉大人旨枯,你說我怎么就攤上這事』斐郏” “怎么了攀隔?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長栖榨。 經(jīng)常有香客問我昆汹,道長,這世上最難降的妖魔是什么婴栽? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任筹煮,我火速辦了婚禮,結(jié)果婚禮上居夹,老公的妹妹穿的比我還像新娘。我一直安慰自己本冲,他們只是感情好准脂,可當(dāng)我...
    茶點故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著檬洞,像睡著了一般狸膏。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上添怔,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天湾戳,我揣著相機(jī)與錄音贤旷,去河邊找鬼。 笑死砾脑,一個胖子當(dāng)著我的面吹牛幼驶,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播韧衣,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼盅藻,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了畅铭?” 一聲冷哼從身側(cè)響起氏淑,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎硕噩,沒想到半個月后假残,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡炉擅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年辉懒,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片坑资。...
    茶點故事閱讀 40,427評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡耗帕,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出袱贮,到底是詐尸還是另有隱情仿便,我是刑警寧澤,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布攒巍,位于F島的核電站嗽仪,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏柒莉。R本人自食惡果不足惜闻坚,卻給世界環(huán)境...
    茶點故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望兢孝。 院中可真熱鬧窿凤,春花似錦、人聲如沸跨蟹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽窗轩。三九已至夯秃,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背仓洼。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工介陶, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人色建。 一個月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓哺呜,卻偏偏與公主長得像,于是被迫代替她去往敵國和親镀岛。 傳聞我的和親對象是個殘疾皇子弦牡,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,440評論 2 359

推薦閱讀更多精彩內(nèi)容