-
- 1.1. Wrong key ⚠️
- 1.2. Wrong type ⚠️
- 1.3. Keys overlapping ⚠️
- 1.4. A safer way
- 1.5. What is #function?
- 1.6. Test 🔨
- 1.7. Conclusion
1. Swift中类型安全的UserDefaults
作为一名iOS开发者,你可能总是使用UserDefaults来保存和检索本地数据。
// This is how you save a value
UserDefaults.standard.set("123", forKey: "userID")
// This is how you read it
let userID = UserDefaults.standard.string(forKey: "userID")
然而,在一个大项目中与UserDefaults交互是有风险的,可能会发生以下错误:
- Wrong key
- Wrong type
- Keys overlapping(重叠)
1.1. Wrong key ⚠️
我们可以用一个键保存一个值,然后尝试用另一个键读取它。
UserDefaults.standard.set("123", forKey: "userID")
...
let userID = UserDefaults.standard.string(forKey: "UserID")
因为键是大小写敏感的**userID不同于userID **。这似乎是一个很容易避免的错误,但在一个有很多键的大项目中,这是可能发生的。
1.2. Wrong type ⚠️
每次我们与UserDefaults交互时,我们必须记住我们保存和加载的值的类型。
下面的示例包含一个错误,因为我们将userID保存为String,但随后试图将其检索为Int。
UserDefaults.standard.set("123", forKey: "userID")
...
let userID = UserDefaults.standard.int(forKey: "userID")
同样,这似乎很容易在代码中发现,但在大型项目中,这是可能发生的。
1.3. Keys overlapping ⚠️
我们可能会忘记在项目的另一部分中为某个值使用了一个给定的键,而决定为另一个值使用相同的键。
let currentUserID = "123"
UserDefaults.standard.set(currentUserID, forKey: "userID")
// and then in another file of our project 👇
let messageRecipientUserID = "456"
UserDefaults.standard.set(messageRecipientUserID, forKey: "userID")
1.4. A safer way
我们可以改进UserDefaults来解决上面讨论的三个问题吗?
这是一个我们可以用Computed Properties解决的问题。
除了存储属性之外,类、结构和枚举还可以定义计算属性,计算属性实际上并不存储值。相反,它们提供了一个getter和一个可选setter来间接地检索和设置其他属性和值. — The Swift Programming Language
Let’s add the following extension to our project
extension UserDefaults {
var userID: String? {
get {
return string(forKey: #function)
}
set {
set(newValue, forKey: #function)
}
}
}
userID计算属性不会在UserDefaults的当前实例中存储任何值。相反,它提供了一个getter(从本地存储检索值)和一个setter(将值写入本地存储)。
1.5. What is #function?
It is a special keyword and is automatically replaced with the name of the function that contains it.
E.g., in our example, it is replaced with the String userID.
这将确保我们用来保存(和检索)UserDefaults值的键是我们定义的属性的名称。
1.6. Test 🔨
Let’s run a quick test
UserDefaults.standard.userID = "123"
if let userID = UserDefaults.standard.userID {
print(userID)
}
> 123
Cool right? 🎉
如果我们为每个键/值定义一个计算属性,我们希望将其存储到UserDefaults中,那么就自动解决了上面讨论的三个问题。
- Wrong key: ✅ fixed 编译器不允许我们使用未定义的属性。
- Wrong type: ✅ fixed 因为每个计算属性都有一个类型。
- Keys overlapping: ✅ fixed 因为现在键是计算属性的名称,Swift编译器将确保UserDefaults中定义的每个属性都是唯一的。
1.7. Conclusion
这种方法还有最后一个好处:封装。我们现在隐藏(到我们的扩展)一些关于如何保存值的细节。任何调用者都可以简单地使用此属性,而不必担心底层细节。