[英]Change the value that is being set in variable's willSet block
I'm trying to sort the array that is being set before setting it but the argument of willSet
is immutable and sort
mutates the value.我试图在设置之前对正在设置的数组进行排序,但willSet
的参数是不可变的,并且sort
会改变值。 How can I overcome this limit?我怎样才能克服这个限制?
var files:[File]! = [File]() {
willSet(newFiles) {
newFiles.sort { (a:File, b:File) -> Bool in
return a.created_at > b.created_at
}
}
}
To put this question out of my own project context, I made this gist:为了把这个问题从我自己的项目上下文中提出来,我提出了这个要点:
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]
I get the error stating that newPeople
is not mutable and sort
is trying to mutate it.我收到错误消息,指出newPeople
不是可变的,而sort
正试图改变它。
It's not possible to mutate the value inside willSet
.不可能改变willSet
的值。 If you implement a willSet
observer, it is passed the new property value as a constant parameter.如果您实现willSet
观察者,它将新的属性值作为常量参数传递。
didSet
?
修改它以使用didSet
怎么didSet
?
var people:[Person] = [Person]() { didSet { people.sort({ (a:Person, b:Person) -> Bool in return a.age > b.age }) } }
willSet
is called just before the value is stored. willSet
在值被存储之前被调用。
didSet
is called immediately after the new value is stored. didSet
在存储新值后立即调用。
You can read more about property observers here https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Properties.html您可以在此处阅读有关财产观察者的更多信息https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Properties.html
You can also write a custom getter and setter like below.您还可以编写如下所示的自定义 getter 和 setter。 But didSet
seems more convenient.但是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 }) } }
It is not possible to change value types (including arrays) before they are set inside of willSet
.在将值类型(包括数组)设置在willSet
内部之前,无法更改它们。 You will need to instead use a computed property and backing storage like so:您将需要改为使用计算属性和后备存储,如下所示:
var _people = [Person]()
var people: [Person] {
get {
return _people
}
set(newPeople) {
_people = newPeople.sorted { $0.age > $1.age }
}
}
Another solution for people who like abstracting away behavior like this (especially those who are used to features like C#'s custom attributes) is to use a Property Wrapper , available since Swift 5.1 (Xcode 11.0).对于喜欢抽象这种行为的人(尤其是那些习惯于 C# 的自定义属性等功能的人)的另一种解决方案是使用Property Wrapper , 自 Swift 5.1 (Xcode 11.0) 起可用。
First, create a new property wrapper struct that can sort Comparable
elements:首先,创建一个可以对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()
}
}
}
and then assuming you implement Comparable
-conformance for Person
:然后假设您为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
}
}
you can declare your property like this and it will be auto-sorted on init
or set:你可以像这样声明你的属性,它将在init
或设置时自动排序:
struct SomeStructOrClass
{
@Sorting var people: [Person]
}
// … (given `someStructOrClass` is an instance of `SomeStructOrClass`)
someStructOrClass.people = sample
let oldestPerson = someStructOrClass.people.last
Caveat: Property wrappers are not allowed (as of time of writing, Swift 5.7.1) in top-level code— they need to be applied to a property var in a struct, class, or enum.警告:在顶级代码中不允许属性包装器(截至撰写本文时,Swift 5.7.1)——它们需要应用于结构、类或枚举中的属性 var。
To more literally follow your sample code, you could easily also create a ReverseSorting
property wrapper:为了更真实地遵循您的示例代码,您还可以轻松地创建一个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()
}
and then the oldest person will be at the first element:然后最年长的人将处于第一个元素:
// …
@Sorting var people: [Person]
// …
someStructOrClass.people = sample
let oldestPerson = someStructOrClass.people[0]
And even more directly, if your use-case demands using a comparison closure via sort(by:…)
instead of implementing Comparable
conformance, you can do that to:更直接地说,如果您的用例需要通过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]
Caveat: The way SortingBy
's init
currently works, you'll need to specify an initial value ( []
).警告:按照SortingBy
的init
当前工作方式,您需要指定一个初始值 ( []
)。 You can remove this requirement with an additional init
(see Swift docs ), but that approach much less complicated when your property wrapper works on a concrete type (eg if you wrote a non-generic PersonArraySortingBy
property wrapper), as opposed to a generic-on-protocols property wrapper.您可以使用额外的init
来移除此要求(请参阅Swift 文档),但是当您的属性包装器在具体类型上工作时(例如,如果您编写了一个非通用的PersonArraySortingBy
属性包装器),这种方法要复杂得多,而不是通用的 -协议属性包装器。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.