1

I have a Swift syntax question.

Let's say I have two different array sorts.

...myArray.sorted(by: { $0.title < $1.title })
...myArray.sorted(by: { $0.dollar < $1.dollar })

But, instead, I want to feed in a VARIABLE into this closure to represent ANY member I might want to sort on. So I'm looking for something closer to this:

var mySort = "title"
myArray.sorted(by: { $0.mySort < $1.mySort })

What's the correct syntax to make this work?

Thanks.

Mike Taverne
  • 9,156
  • 2
  • 42
  • 58

1 Answers1

0

Depends on how much you can tolerate Objective-C code, the answer ranges from "impossible" (pure Swift), to "inelegant" (Swift with Objective-C casting), to "just use Objective-C types" (i.e. NSArray / NSMutableArray).

Since Swift is a statically dispatched language, dynamic features are not its strong suit. Swift 4 added support for key path and allow you to do something like this (lifted from Martin R's answer):

extension Array {
    mutating func sort<T: Comparable>(byKeyPath keyPath: KeyPath<Element, T>) {
        sort(by: { $0[keyPath: keyPath] < $1[keyPath: keyPath] })
    }
}

struct DataModel {
    var title: String
    var dollar: Double
}

var myArray = [DataModel(title: "A", dollar: 12), DataModel(title: "B", dollar: 10)]
let keyPath = \DataModel.dollar
myArray.sort(byKeyPath: keyPath)

But there is no way to construct the key path from String. It must be known at compile time to ensure type safety.


This is something Objective-C really excels at due to its dynamic nature:

@objcMembers
class DataModel: NSObject {
    var title: String
    var dollar: Double

    init(title: String, dollar: Double) {
        self.title = title
        self.dollar = dollar
    }
}

let myArray = [DataModel(title: "A", dollar: 12), DataModel(title: "B", dollar: 10)]

let sortKey = "title"
let sortDescriptors = [NSSortDescriptor(key: sortKey, ascending: true)]
let sortedArray = (myArray as NSArray).sortedArray(using: sortDescriptors) as! [DataModel]

Of course if you keep myArray as NSArray / NSMutableArray, you can leave all the castings behind. But then you will be firmly in Objective-C land and lose access to Swift's Array functions like map, filter, etc.

Code Different
  • 90,614
  • 16
  • 144
  • 163