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.