[英]Change the value that is being set in variable's willSet block
我试图在设置之前对正在设置的数组进行排序,但willSet
的参数是不可变的,并且sort
会改变值。 我怎样才能克服这个限制?
var files:[File]! = [File]() {
willSet(newFiles) {
newFiles.sort { (a:File, b:File) -> Bool in
return a.created_at > b.created_at
}
}
}
为了把这个问题从我自己的项目上下文中提出来,我提出了这个要点:
class Person {
var name:String!
var age:Int!
init(name:String, age:Int) {
self.name = name
self.age = age
}
}
let scott = Person(name: "Scott", age: 28)
let will = Person(name: "Will", age: 27)
let john = Person(name: "John", age: 32)
let noah = Person(name: "Noah", age: 15)
var sample = [scott,will,john,noah]
var people:[Person] = [Person]() {
willSet(newPeople) {
newPeople.sort({ (a:Person, b:Person) -> Bool in
return a.age > b.age
})
}
}
people = sample
people[0]
我收到错误消息,指出newPeople
不是可变的,而sort
正试图改变它。
不可能改变willSet
的值。 如果您实现willSet
观察者,它将新的属性值作为常量参数传递。
didSet
怎么didSet
?
var people:[Person] = [Person]() { didSet { people.sort({ (a:Person, b:Person) -> Bool in return a.age > b.age }) } }
willSet
在值被存储之前被调用。
didSet
在存储新值后立即调用。
您可以在此处阅读有关财产观察者的更多信息https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Properties.html
您还可以编写如下所示的自定义 getter 和 setter。 但是didSet
似乎更方便。
var _people = [Person]() var people: [Person] { get { return _people } set(newPeople) { _people = newPeople.sorted({ (a:Person, b:Person) -> Bool in return a.age > b.age }) } }
在将值类型(包括数组)设置在willSet
内部之前,无法更改它们。 您将需要改为使用计算属性和后备存储,如下所示:
var _people = [Person]()
var people: [Person] {
get {
return _people
}
set(newPeople) {
_people = newPeople.sorted { $0.age > $1.age }
}
}
对于喜欢抽象这种行为的人(尤其是那些习惯于 C# 的自定义属性等功能的人)的另一种解决方案是使用Property Wrapper , 自 Swift 5.1 (Xcode 11.0) 起可用。
首先,创建一个可以对Comparable
元素进行排序的新属性包装结构:
@propertyWrapper
public struct Sorting<V : MutableCollection & RandomAccessCollection>
where V.Element : Comparable
{
var value: V
public init(wrappedValue: V) {
value = wrappedValue
value.sort()
}
public var wrappedValue: V {
get { value }
set {
value = newValue
value.sort()
}
}
}
然后假设您为Person
实现了Comparable
-一致性:
extension Person : Comparable {
static func < (lhs: Person, rhs: Person) -> Bool {
lhs.age < lhs.age
}
static func == (lhs: Person, rhs: Person) -> Bool {
lhs.age == lhs.age
}
}
你可以像这样声明你的属性,它将在init
或设置时自动排序:
struct SomeStructOrClass
{
@Sorting var people: [Person]
}
// … (given `someStructOrClass` is an instance of `SomeStructOrClass`)
someStructOrClass.people = sample
let oldestPerson = someStructOrClass.people.last
警告:在顶级代码中不允许属性包装器(截至撰写本文时,Swift 5.7.1)——它们需要应用于结构、类或枚举中的属性 var。
为了更真实地遵循您的示例代码,您还可以轻松地创建一个ReverseSorting
属性包装器:
@propertyWrapper
public struct ReverseSorting<V : MutableCollection & RandomAccessCollection & BidirectionalCollection>
where V.Element : Comparable
{
// Implementation is almost the same, except you'll want to also call `value.reverse()`:
// value = …
// value.sort()
// value.reverse()
}
然后最年长的人将处于第一个元素:
// …
@Sorting var people: [Person]
// …
someStructOrClass.people = sample
let oldestPerson = someStructOrClass.people[0]
更直接地说,如果您的用例需要通过sort(by:…)
使用比较闭包而不是实现Comparable
一致性,您可以这样做:
@propertyWrapper
public struct SortingBy<V : MutableCollection & RandomAccessCollection>
{
var value: V
private var _areInIncreasingOrder: (V.Element, V.Element) -> Bool
public init(wrappedValue: V, by areInIncreasingOrder: @escaping (V.Element, V.Element) -> Bool) {
_areInIncreasingOrder = areInIncreasingOrder
value = wrappedValue
value.sort(by: _areInIncreasingOrder)
}
public var wrappedValue: V {
get { value }
set {
value = newValue
value.sort(by: _areInIncreasingOrder)
}
}
}
// …
@SortingBy(by: { a, b in a.age > b.age }) var people: [Person] = []
// …
someStructOrClass.people = sample
let oldestPerson = someStructOrClass.people[0]
警告:按照SortingBy
的init
当前工作方式,您需要指定一个初始值 ( []
)。 您可以使用额外的init
来移除此要求(请参阅Swift 文档),但是当您的属性包装器在具体类型上工作时(例如,如果您编写了一个非通用的PersonArraySortingBy
属性包装器),这种方法要复杂得多,而不是通用的 -协议属性包装器。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.