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