繁体   English   中英

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

[英]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]

警告:按照SortingByinit当前工作方式,您需要指定一个初始值 ( [] )。 您可以使用额外的init来移除此要求(请参阅Swift 文档),但是当您的属性包装器在具体类型上工作时(例如,如果您编写了一个非通用的PersonArraySortingBy属性包装器),这种方法要复杂得多,而不是通用的 -协议属性包装器。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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