Swift学习之propertyWrapper

一、基本介绍

属性包装器给代码之间添加了一层分离层,它用来管理属性如何存储数据以及代码如何定义属性。比如说,如果你有一个提供线程安全检查或者把自身数据存入数据库的属性,你必须在每个属性里写相关代码。当你使用属性包装,你只需要在定义包装时写一遍就好了,然后把管理代码应用到多个属性上。

1、propertyWrapper
@propertyWrapper
struct TwelveOrLess {
    var wrappedValue: Int {
        get { return 1}
    }
}
  • 1、定义一个属性包装器(propertyWrapper),需要用@propertyWrapper关键字声明一下。
  • 2、必须要有wrappedValue属性,至少有一个get方法。
@propertyWrapper
struct TwelveOrLess {
    private var number = 0
    var wrappedValue: Int {
        get { return number }
        set { number = min(newValue, 12) }
    }
}

struct SmallRectangle {
    @TwelveOrLess var height: Int
    @TwelveOrLess var width: Int
}


var rectangle = SmallRectangle()
print(rectangle.height)
// Prints "0"
 
rectangle.height = 10
print(rectangle.height)
// Prints "10"
 
rectangle.height = 24
print(rectangle.height)

这个例子里需要让宽高都不能大于12,假如你自己直接要set,get方法就需要两遍差不多的代码。而使用属性包装,你只需要在定义包装时写一遍就好了,然后把管理代码应用到多个属性上。

truct  SmallRectangle{
    @TwelveOrLess var height: Int
    @TwelveOrLess var width: Int
    func test(){
        print(_height)
        print(height)
    }
}

print(_height) 输出:TwelveOrLess()
print(height)输出:24

  • 使用属性包装器,编译器会为我们自动生成_height、height这两个属性。
    当你给属性应用包装时,编译器会为包装生成提供存储的代码以及通过包装访问属性的代码)
struct SmallRectangle {
    private var _height = TwelveOrLess()
    private var _width = TwelveOrLess()
    var height: Int {
        get { return _height.wrappedValue }
        set { _height.wrappedValue = newValue }
    }
    var width: Int {
        get { return _width.wrappedValue }
        set { _width.wrappedValue = newValue }
    }
}
  • 这里有一个前面 SmallRectangle 的例子,它在 TwelveOrLess 结构体中显式地包装了自己的属性,而不是用 @TwelveOrLess 这个特性。
  • _height 和 _width 属性存储了一个属性包装的实例, TwelveOrLess 。 height 和 width 的 getter 和 setter 包装了 wrappedValue 属性的访问。
2、通过属性包装映射值:

@propertyWrapper
struct SmallNumber {
    private var number = 0
    var projectedValue = false
    var wrappedValue: Int {
        get { return number }
        set {
            if newValue > 12 {
                number = 12
                projectedValue = true
            } else {
                number = newValue
                projectedValue = false
            }
        }
    }
}
struct SomeStructure {
    @SmallNumber var someNumber: Int
}
var someStructure = SomeStructure()
 
someStructure.someNumber = 4
print(someStructure.$someNumber)
// Prints "false"
 
someStructure.someNumber = 55
print(someStructure.$someNumber)
// Prints "true"

  • 添加了projectedValue属性来定义一个映射值。
  • 使用 s.$someNumber 来访问包装的映射值。在保存一个小数字比如四之后, s.$someNumber 的值是 false 。总之,在尝试保存一个过大的数字时映射的值就是 true 了,比如 55.
  • wrappedValue: 属性包装存储的值; someNumber
    projectedValue:映射值,$someNumber;
    属性包装本身:SomeStructure(),_someNumber;
3、SwiftUI常见的propertyWrapper

@State、@Binding、@ObservedObject、@EnvironmentObject、@StateObject

4、模仿@State的一个propertyWrapper
import SwiftUI
struct ContentView: View {

    @Test var h = "20"
    var body: some View {
        VStack {
            
                
            Text("Hello, world!").foregroundColor(.red)
            Button {
                h = "100"
               
            } label: {
               
                Text("点击")
            }

        }
        
    }
}
 class Object{
    var value:String = ""
}
@propertyWrapper
struct Test{
     var object:Object = Object()
     var wrappedValue: String{
            get{
                return object.value
            }
            nonmutating  set{
                
                object.value = newValue
            }
     }
     init(wrappedValue: String) {
     self.object.value = wrappedValue
     }
}

二、基础实践

2.1、本地历史搜索

class History: Equatable,Codable{
    var searchStr = ""
    
    var  timestamp: String?
    // 实现Equatable协议
    static func == (lhs: History, rhs: History) -> Bool {
        // 在这里我们仅基于searchTerm判断相等性,忽略timestamp
        return lhs.searchStr == rhs.searchStr
    }
}

@propertyWrapper
struct HistoryTracking {
    private var storage: String = ""
    private var historykey:String
    private var historys: [History] = [History]()
    var wrappedValue: String{
        set{
            storage = newValue
            
            let newHistory = History()
            newHistory.searchStr =  newValue
            newHistory.timestamp =   "\(Date().timestamp())"
            
            if let index = historys.firstIndex(where: { $0 == newHistory }) {
                historys.remove(at: index)
                historys.insert(newHistory, at: 0)
            } else {
                // 否则,添加新记录
                historys.insert(newHistory, at: 0)
            }
            
            // 保持只保留最近的N条记录,例如10条
            if historys.count > 10 {
                historys.removeLast(historys.count - 10)
            }
            
            if let jsonData = try? JSONEncoder().encode(historys) {
                UserDefaults.standard.set(jsonData, forKey: historykey)
                UserDefaults.standard.synchronize()
            }
            
        }
        get{
            
            return  self.storage
        }
        
    }
    
    //
    mutating func removeAllHistory(key:String){
        UserDefaults.standard.removeObject(forKey: historykey)
        UserDefaults.standard.synchronize()
        self.historys.removeAll()
    }
    
    mutating  func removeHistory(item:History){
        if let indext =   self.historys.firstIndex(of: item){
            self.historys.remove(at: indext)
            if let jsonData = try? JSONEncoder().encode(historys) {
                UserDefaults.standard.set(jsonData, forKey: historykey)
                UserDefaults.standard.synchronize()
            }
            
        }
        
    }
    
    
    
    // 通过projectedValue暴露历史记录
       var projectedValue: [History] {
           return historys
       }
    
    
    
    init(wrappedValue: String,historyKey: String) {
        self.storage = wrappedValue
        self.historykey = historyKey
        if let savedData = UserDefaults.standard.data(forKey: historykey),let historyArray = try? JSONDecoder().decode([History].self, from: savedData) {
            self.historys = historyArray
        }
    }
}






class TestViewController: UIViewController {
    @HistoryTracking(wrappedValue: "", historyKey: "searchTradmarkKey")
    var searchStr:String
    
    //加载历史记录数据
    func loadData() -> [History]{
        let dataArray = $searchStr
        return dataArray
    }
    
    //清楚历史记录
    func removeAllHistory(){
        var tracking:HistoryTracking  =  _searchStr
        tracking.removeAllHistory()
    }
    
    //清楚某条记录
    func removeHistory(history:History){
        var tracking:HistoryTracking  =  _searchStr
        tracking.removeHistory(item: history)
    }
    
    //去搜索
    func goSearch(){
        //搜索新的字符串,并保存到本地记录
        self.searchStr  = "hhh3ddd3"
    }


}

2.2、带有默认值合key的UserDefault

@propertyWrapper
struct UserDefault<T: Codable> {
    private var key: String
    private var defaultValue: T

    var wrappedValue: T {
        get {
            if let data = UserDefaults.standard.data(forKey: key) {
                do {
                    return try JSONDecoder().decode(T.self, from: data)
                } catch {
                    print("Error decoding data: \(error)")
                }
            }
            return defaultValue
        }

        set {
            do {
                let data = try JSONEncoder().encode(newValue)
                UserDefaults.standard.set(data, forKey: key)
                UserDefaults.standard.synchronize()
            } catch {
                print("Error encoding data: \(error)")
            }
        }
    }

    init(_ key: String, defaultValue: T) {
        self.key = key
        self.defaultValue = defaultValue
    }
}


struct AppUserDefaults{
    //是否是第一次
    @UserDefault("isFisrt",defaultValue: true)
    static  var isFirst:Bool
    
    //用户是否同意了用户协议
    @UserDefault("isAgree",defaultValue: false)
    static var isAgree:Bool
    
}

2.3、Codable类型不匹配处理


class Student:Codable {
    @Default var name:String
    @Default var age:Int
    required init(from decoder: any Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        _name = try container.decodeIfPresent(Default<String>.self, forKey: .name) ?? Default(wrappedValue: "")
        _age = try container.decodeIfPresent(Default<Int>.self, forKey: .age) ?? Default(wrappedValue:0)
    }
}

@propertyWrapper
struct Default<T:Codable>:Codable{
    var wrappedValue: T
    init(wrappedValue: T) {
        self.wrappedValue = wrappedValue
    }
    
    init(from decoder: any Decoder) throws {
        let container = try decoder.singleValueContainer()
        if T.self == String.self{
                 if let str  = try? container.decode(Int.self){
                     wrappedValue = "\(str)" as! T
                 }else if let str = try? container.decode(Bool.self){
                     wrappedValue = (str == true ? "0" : "1") as! T
                 }else if let str = try? container.decode(Double.self){
                     wrappedValue  = "\(str)" as! T
                 }else if let str = try? container.decode(Float.self){
                     wrappedValue  = "\(str)" as! T
                 }else{
                     wrappedValue = (try? container.decode(T.self)) ?? "" as! T
                 }
        }else{
            self.wrappedValue =   try! container.decode(T.self) 
        }

     }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,142评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,298评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,068评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,081评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,099评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,071评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,990评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,832评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,274评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,488评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,649评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,378评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,979评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,625评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,643评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,545评论 2 352

推荐阅读更多精彩内容