简体   繁体   English

更改在变量的 willSet 块中设置的值

[英]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观察者,它将新的属性值作为常量参数传递。


What about modifying it to use 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 ( [] ).警告:按照SortingByinit当前工作方式,您需要指定一个初始值 ( [] )。 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM