Swift sorting on arbitrary types - swift

I have a Set of instances of type Thingie, and I want to provide arrays of Thingies sorted on any property of Thingie. Some of the properties are Int, for instance, while others are String, and there could be others. So I wanted to create a sort routine that accepts a string as the name of the property and compares the two properties of two thingies to determine the order.
It seemed like a job for generics, and I'm getting close, but there's a hole.
Here's where I'm at right now:
func compare<T:Comparable>(lft: T, _ rgt: T) -> Bool {
return lft < rgt
}
func orderBy(sortField: String) -> [Thingie] {
let allArray = (self.thingies as NSSet).allObjects as! [Thingie]
//typealias T = the type of allArray[0][sortField]
// or maybe create an alias that conforms to a protocol:
//typealias T:Comparable = ?
return allArray.sort({(a, b) -> Bool in
return self.compare(a[sortField] as! T, b[sortField] as! T)
})
}
I created a compare function using generics, and invoke it in my sort routine. The catch is that AnyObject! will not work for my generic, so I need to cast the values returned from a[sortField] and b[sortField] to be of the same type. It doesn't even really matter what type as long as the compiler is happy that both values are of the same type and that it implements the Comparable protocol.
I figured a typealias would do the trick, but maybe there's a better way?
Side question: surely there's a better way to create the initial, unsorted array from the set without resorting to NSSet. A little hint would be welcome. [Solved that bit! Thanks, Oliver Atkinson!]
Here's a big 'ol chunk of code you can paste into a playground. It has three attempts at the orderBy implementation, each with a problem.
//: Playground - noun: a place where people can play
import Foundation
class Thingie: Hashable {
var data: [String: AnyObject]
var hashValue: Int
init(data: [String: AnyObject]) {
self.data = data
self.hashValue = (data["id"])!.hashValue
}
subscript(propName: String) -> AnyObject! {
return self.data[propName]
}
}
func ==(lhs: Thingie, rhs: Thingie) -> Bool {
return lhs.hashValue == rhs.hashValue
}
var thingies: Set = Set<Thingie>()
thingies.insert(Thingie(data: ["id": 2, "description": "two"]));
thingies.insert(Thingie(data: ["id": 11, "description": "eleven"]));
// attempt 1
// won't compile because '<' won't work when type is ambiguous e.g., AnyObject
func orderByField1(sortField: String) -> [Thingie] {
return thingies.sort { $0[sortField] < $1[sortField] }
}
// compare function that promises the compiler that the operands for < will be of the same type:
func compare<T:Comparable>(lft: T, _ rgt: T) -> Bool {
return lft < rgt
}
// attempt 2
// This compiles but will bomb at runtime if Thingie[sortField] is not a string
func orderByField2(sortField: String) -> [Thingie] {
return thingies.sort { compare($0[sortField] as! String, $1[sortField] as! String) }
}
// attempt 3
// Something like this would be ideal, but protocol Comparable can't be used like this.
// I suspect the underlying reason that Comparable can't be used as a type is the same thing preventing me from making this work.
func orderByField3(sortField: String) -> [Thingie] {
return thingies.sort { compare($0[sortField] as! Comparable, $1[sortField] as! Comparable) }
}
// tests - can't run until a compiling candidate is written, of course
// should return array with thingie id=2 first:
var thingieList: Array = orderByField2("id");
print(thingieList[0]["id"])
// should return array with thingie id=11 first:
var thingieList2: Array = orderByField2("description");
print(thingieList2[0]["id"])

My previous answer, though it works, does not make the most of the Swift's excellent type checker. It also switches between the types that can be used in one centralised place which limits extensibility to the framework owner.
The following approach solves these issues. (Please forgive me for not having the heart to delete my previous answer; let us say that it's limitations are instructive...)
As before, we'll start with the target API:
struct Thing : ThingType {
let properties: [String:Sortable]
subscript(key: String) -> Sortable? {
return properties[key]
}
}
let data: [[String:Sortable]] = [
["id": 1, "description": "one"],
["id": 2, "description": "two"],
["id": 3, "description": "three"],
["id": 4, "description": "four"],
["id": 4, "description": "four"]
]
var things = data.map(Thing.init)
things.sortInPlaceBy("id")
things
.map{ $0["id"]! } // [1, 2, 3, 4]
things.sortInPlaceBy("description")
things
.map{ $0["description"]! } // ["four", "one", "three", "two"]
To make this possible we must have this ThingType protocol and an extension to mutable collections (which will work for sets as well as arrays):
protocol ThingType {
subscript(_: String) -> Sortable? { get }
}
extension MutableCollectionType
where Index : RandomAccessIndexType, Generator.Element : ThingType
{
mutating func sortInPlaceBy(key: String, ascending: Bool = true) {
sortInPlace {
guard let lhs = $0[key], let rhs = $1[key] else {
return false // TODO: nil handling
}
guard let b = (try? lhs.isOrderedBefore(rhs, ascending: ascending)) else {
return false // TODO: handle SortableError
}
return b
}
}
}
Evidently, the whole idea revolves around this Sortable protocol:
protocol Sortable {
func isOrderedBefore(_: Sortable, ascending: Bool) throws -> Bool
}
... which can be conformed to independently by any type we want to work with:
import Foundation
extension NSNumber : Sortable {
func isOrderedBefore(other: Sortable, ascending: Bool) throws -> Bool {
try throwIfTypeNotEqualTo(other)
let f: (Double, Double) -> Bool = ascending ? (<) : (>)
return f(doubleValue, (other as! NSNumber).doubleValue)
}
}
extension NSString : Sortable {
func isOrderedBefore(other: Sortable, ascending: Bool) throws -> Bool {
try throwIfTypeNotEqualTo(other)
let f: (String, String) -> Bool = ascending ? (<) : (>)
return f(self as String, other as! String)
}
}
// TODO: make more types Sortable (including those that do not conform to NSObject or even AnyObject)!
This throwIfTypeNotEqualTo method is just a convenience extension of Sortable:
enum SortableError : ErrorType {
case TypesNotEqual
}
extension Sortable {
func throwIfTypeNotEqualTo(other: Sortable) throws {
guard other.dynamicType == self.dynamicType else {
throw SortableError.TypesNotEqual
}
}
}
And that's it. Now we can conform new types to Sortable even outside of the framework and the type checker is validating our [[String:Sortable]] source data at compile time. Also, if Thing is extended to conform to Hashable then Set<Thing> will also be sortable by key...
Note that, although Sortable is itself unconstrained (which is awesome), source data and Thing's properties can be constrained to dictionaries with NSObject or AnyObject values if required by making use of a protocol like:
protocol SortableNSObjectType : Sortable, NSObjectProtocol { }
... or more directly by declaring data and Thing's properties as:
let _: [String : protocol<Sortable, NSObjectProtocol>]

I don't know the implementation of Thingie but maybe you could provide more context.
You could however go for something like this
func orderBy(sortField: String) -> [Thingie] {
return thingies.allObjects.map { $0 as! Thingie }.sort { $0[sortField] < $1[sortField] }
}
If you could provide a playground example so I can provide further help.
Also why did you use NSSet rather than a swift Set? would that give you what you want
let thingies: Set = Set<Thingie>()
func orderBy(sortField: String) -> [Thingie] {
return thingies.sort { $0[sortField] < $1[sortField] }
}
edit:
The trouble is with swift's type safety - it requires you to know what types you are dealing with so that it can compile correctly - if you specify the actual type when you want to order the field you can get it to work as expected.
func orderByField<T: Comparable>(sortField: String, type: T.Type) -> [Thingie] {
return thingies.sort { ($0[sortField] as? T) < ($1[sortField] as? T) }
}
var thingieList: Array = orderByField("id", type: Int.self);
print(thingieList[0]["id"])
var thingieList2: Array = orderByField("description", type: String.self);
print(thingieList2[0]["id"])
The above will print 2 then 11 - if you wanted to get around this you could store your objects in a different struct and then you can sort the array of 'Things' on the variable.
e.g.
struct Thing {
let id: Int
let description: String
}
var data: [Thing] = [
Thing(id: 2, description: "two"),
Thing(id: 11, description: "eleven")
]
let first = data.sort { $0.id < $1.id }.first?.id
let second = data.sort { $0.description < $1.description }.first?.id
print(first)
print(second)
Which would achieve the same thing - 2 and 11
I would advise against using AnyObject where possible as its trying to cheat the compiler into telling it you don't care for its help.
Its an interesting problem though and I hope this helps you towards your solution.

I will start with the target API (ignoring conformance to Hashable as its addition wont change anything in what follows). So, let's say we'd like to be able to write the following:
var thingies = [
["id": 1, "description": "one"],
["id": 2, "description": "two"],
["id": 3, "description": "three"],
["id": 4, "description": "four"]
].map(Thingie.init)
thingies.sortInPlace{ $0["id"] < $1["id"] }
... and even:
thingies.sortInPlaceBy("id")
thingies
.map{ $0["id"]!.value } // [1, 2, 3, 4]
thingies.sortInPlaceBy("description")
thingies
.map{ $0["description"]!.value } // ["four", "one", "three", "two"]
Obviously, we'd need an extension of MutableCollectionType protocol along the lines of:
protocol ThingieDatumSubscriptable {
subscript(_: String) -> ThingieDatum? { get }
}
extension Thingie : ThingieDatumSubscriptable {}
extension MutableCollectionType
where Index : RandomAccessIndexType, Generator.Element : ThingieDatumSubscriptable
{
mutating func sortInPlaceBy(datumName: String, ascending: Bool = true) {
let f: (ThingieDatum?, ThingieDatum?) -> Bool = ascending ? (<) : (>)
sortInPlace{ f($0[datumName], $1[datumName]) }
}
}
This ThingieDatum would then be something like:
import Foundation
struct ThingieDatum : Comparable {
let type: AnyObject.Type
let value: AnyObject
let name: String
init(keyValuePair: (String, AnyObject)) {
name = keyValuePair.0
value = keyValuePair.1
type = keyValuePair.1.dynamicType
}
}
... and its conformance to Comparable implemented in some sort of pedestrian way as follows (unless we introduce more protocols):
func == (lhs: ThingieDatum, rhs: ThingieDatum) -> Bool {
guard lhs.name == rhs.name && lhs.type == rhs.type else {
return false
}
switch lhs.type {
// TODO: implement for other types
case is NSNumber.Type: return lhs.value as! NSNumber == rhs.value as! NSNumber
case is NSString.Type: return (lhs.value as! String) == (rhs.value as! String)
default: break
}
return false
}
func < (lhs: ThingieDatum, rhs: ThingieDatum) -> Bool {
assert(lhs.name == rhs.name && lhs.type == rhs.type)
switch lhs.type {
// TODO: implement for other types
case is NSNumber.Type: return (lhs.value as! NSNumber).doubleValue < (rhs.value as! NSNumber).doubleValue
case is NSString.Type: return (lhs.value as! String) < (rhs.value as! String)
default: break
}
return false
}
Armed with such a ThingieDatum we can finally work out the Thingie itself:
struct Thingie {
var data: [ThingieDatum]
init(_ data: [String: AnyObject]) {
self.data = data.map(ThingieDatum.init)
}
subscript(datumName: String) -> ThingieDatum? {
for datum in data where datum.name == datumName {
return datum
}
return nil
}
}
And although this is, of course, all meant as a fun exercise, it does work (copy and paste into the playground if you can work our the correct order of snippets)... To take this idea further, however, we would probably want to constrain ThingiDatum initialiser to a custom protocol (rather than AnyObject), which would guarantee comparability. We would then conform to that protocol with each type we want to work with instead of switching through those types in one centralised place...

Related

How to filter objects of certain type from [AnyObject] array

Is it possible to filter an array of [AnyObject] to yield all elements of a given type, and none other?
I can do it if the type is known at compile time:
class MyClass1: CustomStringConvertible {
var value: Int
var description: String {
return "MyClass1: \(value)"
}
init(_ value: Int) {
self.value = value
}
}
class MyClass2: CustomStringConvertible {
var value: Int
var description: String {
return "MyClass1: \(value)"
}
init(_ value: Int) {
self.value = value
}
}
class MySubClass1: MyClass1 {
override var description: String {
return "MySubClass1: \(value)"
}
}
let a1 = MySubClass1(1)
let a2 = MySubClass1(2)
let b1 = MyClass1(3)
let b2 = MyClass2(4)
let array: [AnyObject] = [a1, b1, a2, b2]
func getClass1ObjectsFromArray(_ array: [AnyObject]) -> [MyClass1] {
return array.compactMap( { $0 as? MyClass1 })
}
func getSubClass1ObjectsFromArray(_ array: [AnyObject]) -> [MySubClass1] {
return array.compactMap( { $0 as? MySubClass1 })
}
print(getClass1ObjectsFromArray(array))
print(getSubClass1ObjectsFromArray(array))
Prints:
[MySubClass1: 1, MyClass1: 3, MySubClass1: 2]
[MySubClass1: 1, MySubClass1: 2]
For every type I want to filter on, I had to write a separate function. This looks ugly to me, and will not work when the type to be selected for is only known at run time.
Question:
Is there a generic way to write such a function? Preferably something like:
func getObjectsOfType(_ type: TypeExpression, fromArray array: [AnyObject])
-> [TypeExpression] {
...
}
Or any other way to achieve this?
Thanks for any help!
I think you could use something like this...
let filteredArray = array.compactMap { $0 as? RequiredType }
This will filter the array and return a typed array containing only the type you want.
Caveat
Having said that. In Swift you should be avoiding heterogeneous arrays where possible. Arrays should really only contain one type of item.
A bit of code testing...
Tested in Playground...
let array: [Any] = [1, "hello", 3, 3.1415, "world"]
let filteredArray = array.compactMap { $0 as? String }
filteredArray
Output:
filteredArray = ["hello", "world"]
👍🏻
Edit 1
You could also create a generic function something like this...
func filter<T>(array: [Any]) -> [T] {
return array.compactMap { $0 as? T }
}
let filteredArray: [String] = filter(array: array)
This will then filter based on the type of the output array that you want.
I'm not sure what you mean by only knowing the type you want at run time. Can you give a more concrete example of what you mean?
Edit 2
Another possibility is a generic function like this...
func filter<T>(array: [Any], byType typeObject: T) -> [T] {
return array.compactMap { $0 as? T }
}
let filteredArray = filter(array: array, byType: "some string")
This uses the type information of the second parameter to filter the array by that type of item.
Edit 3
If you don't like passing in an instance of the type then you can pass the type itself...
func filter<T>(array: [Any], byType typeObject: T.Type) -> [T] {
return array.compactMap { $0 as? T }
}
let filteredArray = filter(array: array, byType: String.self)
But I'm not sure what more you're getting from this than just filtering by string in the first place?

Custom comparator for Swift

This is my code (simplified code):
struct SomeStruct {
let id: Int
let age: Int
}
extension SomeStruct: Hashable {
var hashValue: Int {
return id.hashValue * age.hashValue
}
static func ==(lhs: SomeStruct, rhs: SomeStruct) -> Bool {
return lhs.id == rhs.id && lhs.age == rhs.age
}
}
struct Calculator {
let struct1: [SomeStruct]
let struct2: [SomeStruct]
func uniqueById() {
let struct3 = Set(struct2).union(Set(struct1))
// I want to union it by property 'id' only.
// If the property 'id' is equal for both objects,
// the object in struct2 should be used (since that can have a different age property)
}
}
SomeStruct is a generated struct which I do not want to edit. I want to create a Set for SomeStruct that is based on 1 property: id. For that, I think I need a custom Comparator, just as Java has. Is there any Swifty way? This is the only thing I can come up with, but I am wondering if there is a better way:
struct SomeStructComparatorById: Hashable {
let someStruct: SomeStruct
var hashValue: Int {
return someStruct.id.hashValue
}
static func ==(lhs: SomeStructComparatorById, rhs: SomeStructComparatorById) -> Bool {
return lhs.someStruct.id == rhs.someStruct.id
}
}
First, I don't think this would work in Java. addAll() doesn't take a Comparator (nor does contains, etc.) Comparators are for sorting, not equality. Conceptually this is breaking how Set works in any language. Two items are not "equal" unless they can be swapped in all cases.
That tells us that we don't want a Set here. What you want here is uniqueness based on some key. That's a Dictionary (as Daniel discusses).
You could either just have a "id -> age" dictionary or "id -> struct-of-other-properties" dictionary as your primary data type (rather than using Array). Or you can turn your Array into a temporary Dictionary like this:
extension Dictionary {
init<S>(_ values: S, uniquelyKeyedBy keyPath: KeyPath<S.Element, Key>)
where S : Sequence, S.Element == Value {
let keys = values.map { $0[keyPath: keyPath] }
self.init(uniqueKeysWithValues: zip(keys, values))
}
}
And merge them like this:
let dict1 = Dictionary(struct1, uniquelyKeyedBy: \.id)
let dict2 = Dictionary(struct2, uniquelyKeyedBy: \.id)
let merged = dict1.merging(dict2, uniquingKeysWith: { old, new in old }).values
This leaves merged as [SomeStruct].
Note that this Dictionary(uniquelyKeyedBy:) has the same preconditions as Dictionary(uniqueKeysWithValues:). If there are duplicate keys, it's a programming error and will raise precondition failure.
You could do something like this:
var setOfIds: Set<Int> = []
var struct3 = struct2.filter { setOfIds.insert($0.id).inserted }
struct3 += struct1.filter { setOfIds.insert($0.id).inserted }
The result would be an array of SomeStruct, with all elements with unique ids.
You could define this as a custom operator :
infix operator *>
func *> (lhs: [SomeStruct], rhs: [SomeStruct]) -> [SomeStruct] {
var setOfIds: Set<Int> = []
var union = lhs.filter { setOfIds.insert($0.id).inserted }
union += rhs.filter { setOfIds.insert($0.id).inserted }
return union
}
Your code would then look like this:
func uniqueById() {
let struct3 = struct2 *> struct1
//use struct3
}
The short answer is no. Swift sets do not have any way to accept a custom comparator and if you absolutely must have a Set, then your wrapper idea is the only way to do it. I question the requirement for a set though.
Instead of using Set in your calculator, I recommend using dictionary.
You can use a Dictionary to produce an array where each item has a unique ID...
let struct3 = Dictionary(grouping: struct1 + struct2, by: { $0.id })
.compactMap { $0.value.max(by: { $0.age < $1.age })}
Or you can keep the elements in a [Int: SomeStruct] dictionary:
let keysAndValues = (struct1 + struct2).map { ($0.id, $0) }
let dictionary = Dictionary(keysAndValues, uniquingKeysWith: { lhs, rhs in
lhs.age > rhs.age ? lhs : rhs
})

How to call a generic function with no parameter?

Adding an extension to Array, which returns the first Int or String is easy:
extension Array {
func firstInt() -> Int? {
return self.flatMap{$0 as? Int}.first
}
func firstString() -> String? {
return self.flatMap{$0 as? String}.first
}
}
let a1:[AnyObject?] = [nil, "abc", 3, 4]
let a2:[AnyObject?] = [nil, [3], [ "foo":"bar" ]]
print(a1.firstInt()) // Optional(3)
print(a2.firstInt()) // nil
print(a1.firstString()) // Optional("abc")
print(a2.firstString()) // nil
I can define a generic version, but I am not able to figure out how to call it because it does not take any parameter. Without a parameter, I can't specify the type!
extension Array {
func firstValueOfType<T>() -> T? {
return self.flatMap{$0 as? T}.first
}
}
// I can't figure out how to call this method!
I can work around it by adding a dummy parameter, but this is ugly.
extension Array {
func firstValueLike<T>(_:T) -> T? {
return self.flatMap{$0 as? T}.first
}
}
let a1:[AnyObject?] = [nil, "abc", 3, 4]
print(a1.firstValueLike(1)) // Optional(3)
print(a1.firstValueLike("")) // Optional("abc")
I'd appreciate if somebody could tell me how to call firstValueOfType function (or alternative way to define a generic function cleanly).
Additional Info
"Cannot explicitly specialize a generic function" is similar, but my problem is a bit more complicated because of Optional.
I've got a great answer from OOPer, which even includes the better implementation which uses lazy.filter, instead of flatMap.
extension Array {
func firstValueOfType<T>() -> T? {
return self.lazy.filter {$0 is T}.first as? T
}
}
let a1:[AnyObject?] = [nil, "abc", 3, 4]
print(a1.firstValueOfType() as Int?) // Optional(3)
print(a1.firstValueOfType() as String?) // Optional("abc")
Thank you very much for a very quick support!
One way is assigning the result to a variable with explicit type.
let i: Int? = a1.firstValueOfType()
print(i) // Optional(3)
let s: String? = a1.firstValueOfType()
print(s) // Optional("abc")
Another is using as:
print(a1.firstValueOfType() as Int?) // Optional(3)
print(a1.firstValueOfType() as String?) // Optional("abc")
You can pass the type as an argument with T.Type, like so:
extension Array {
func firstValue<T>(like type: T.Type) -> T? {
return self.flatMap{$0 as? T}.first
}
}
[1,2,3].firstValue(like: Int.self)

Swift generics: return type based on parameter type

Say I have a collection of objects inheriting from a common superclass (this is preferable to protocols in this case):
class ObjectSuperClass {
type: ObjectType
}
class ObjectClass1: ObjectSuperClass {
type = .Type1
}
class ObjectClass2: ObjectSuperClass {
type = .Type2
}
I'm looking to create a generic search function like this:
func objectsOfType<T: ObjectSuperClass>(T.class, otherFilter: Any?) -> [T]
Which could be used to search for a given sub-type, returning a more specific array of results:
let result = objectsOfType(ObjectClass2.class, otherFilter: nil) -> [ObjectClass2]
(pseudo-swift)
I feel like this is somewhere generics could help, but cannot see where constraints should be placed. Is it possible?
Well remarkably this works...
func filterType<T>(list: [AnyObject]) -> [T]
{
return list.filter{ $0 is T }.map{ $0 as! T }
}
...provided you assign the result to something that has been explicitly typed, as in the following example:
class ObjectSuperClass: CustomStringConvertible
{
let myType: String
init(aString: String)
{
myType = aString
}
var description: String { return myType }
}
class ObjectClass1: ObjectSuperClass
{
init()
{
super.init(aString: "<t 1>")
}
}
class ObjectClass2: ObjectSuperClass
{
init()
{
super.init(aString: "<t 2>")
}
}
let unfilteredList: [AnyObject] = [ ObjectClass1(), ObjectClass2(), ObjectSuperClass(aString: "<Who knows>")]
let filteredList1: [ObjectClass1] = filterType(list: unfilteredList)
print("\(filteredList1)") // <t 1>
let filteredList2: [ObjectClass2] = filterType(list: unfilteredList)
print("\(filteredList2)") // <t 2>
let filteredList3: [ObjectSuperClass] = filterType(list: unfilteredList)
print("\(filteredList3)") // [<t 1>, <t 2>, <Who knows>]
T is inferred in each case from the requested return type. The function itself filters the original array based on whether the elements are of the required type and then force casts the filtered results to the correct type.
If you want an "extra filter" you don't need to explicitly type the results as long as T can be inferred from your extra filter function.
func extraFilterType<T>(list: [AnyObject], extraFilter: T -> Bool) -> [T]
{
return list.filter{ $0 is T }.map{ $0 as! T }.filter(extraFilter)
}
let filteredList = extraFilterType(unfilteredList){
(element : ObjectClass2) -> Bool in
!element.description.isEmpty
}
print("\(filteredList)") // <t 2>
EDIT
A slicker version of the filterType function would use flatMap()
func filterType<T>(list: [Any]) -> [T]
{
return list.flatMap{ $0 as? T }
}
EDIT 2
Flatmap is deprecated for optionals, since Swift 4.something, use compactMap
func filterType<T>(list: [Any]) -> [T]
{
return list.compactMap{ $0 as? T }
}
This is the closest approximation I can come up with:
func objectsOfType<T: ObjectSuperClass>(type type: T.Type) -> [T] {
// Just returns an array of all objects of given type
}
func objectsOfType<T: ObjectSuperClass>(type type: T.Type, predicate: T -> Bool) -> [T] {
// Uses predicate to filter out objects of given type
}
Usage:
let bar = objectsOfType(type: ObjectClass1.self)
let baz = objectsOfType(type: ObjectClass2.self) {
// Something that returns Bool and uses $0
}
Technically, you can also go without type argument in the above, but then you will need to have explicitly typed receivers (bar and baz in the above example) so that Swift can correctly infer the types for you and use the right version of the generic function.
You can implement the function like this:
func objectsOfType<T: ObjectSuperClass>(objects: [ObjectSuperClass], subclass: T.Type, otherFilter: (T->Bool)?) -> [T] {
if let otherFilter = otherFilter {
return objects.filter{$0 is T && otherFilter($0 as! T)}.map{$0 as! T}
} else {
return objects.filter{$0 is T}.map{$0 as! T}
}
}
Usage example:
objectsOfType(arrayOfObjects, subclass: ObjectClass1.self, otherFilter: nil)
Note that I'm not a fan of forced casting, however in this scenario it should not cause problems.
Or, the more verbose version of the function, with one less forced cast:
func objectsOfType<T: ObjectSuperClass>(objects: [ObjectSuperClass], subclass: T.Type, otherFilter: (T->Bool)?) -> [T] {
return objects.filter({object in
if let object = object as? T {
if let otherFilter = otherFilter {
return otherFilter(object)
} else {
return true
}
} else {
return false
}
}).map({object in
return object as! T
})
}

Using reflection to set object properties without using setValue forKey

In Swift it's not possible use .setValue(..., forKey: ...)
nullable type fields like Int?
properties that have an enum as it's type
an Array of nullable objects like [MyObject?]
There is one workaround for this and that is by overriding the setValue forUndefinedKey method in the object itself.
Since I'm writing a general object mapper based on reflection. See EVReflection I would like to minimize this kind of manual mapping as much as possible.
Is there an other way to set those properties automatically?
The workaround can be found in a unit test in my library here
This is the code:
class WorkaroundsTests: XCTestCase {
func testWorkarounds() {
let json:String = "{\"nullableType\": 1,\"status\": 0, \"list\": [ {\"nullableType\": 2}, {\"nullableType\": 3}] }"
let status = Testobject(json: json)
XCTAssertTrue(status.nullableType == 1, "the nullableType should be 1")
XCTAssertTrue(status.status == .NotOK, "the status should be NotOK")
XCTAssertTrue(status.list.count == 2, "the list should have 2 items")
if status.list.count == 2 {
XCTAssertTrue(status.list[0]?.nullableType == 2, "the first item in the list should have nullableType 2")
XCTAssertTrue(status.list[1]?.nullableType == 3, "the second item in the list should have nullableType 3")
}
}
}
class Testobject: EVObject {
enum StatusType: Int {
case NotOK = 0
case OK
}
var nullableType: Int?
var status: StatusType = .OK
var list: [Testobject?] = []
override func setValue(value: AnyObject!, forUndefinedKey key: String) {
switch key {
case "nullableType":
nullableType = value as? Int
case "status":
if let rawValue = value as? Int {
status = StatusType(rawValue: rawValue)!
}
case "list":
if let list = value as? NSArray {
self.list = []
for item in list {
self.list.append(item as? Testobject)
}
}
default:
NSLog("---> setValue for key '\(key)' should be handled.")
}
}
}
I found a way around this when I was looking to solve a similar problem - that KVO can't set the value of a pure Swift protocol field. The protocol has to be marked #objc, which caused too much pain in my code base.
The workaround is to look up the Ivar using the objective C runtime, get the field offset, and set the value using a pointer.
This code works in a playground in Swift 2.2:
import Foundation
class MyClass
{
var myInt: Int?
}
let instance = MyClass()
// Look up the ivar, and it's offset
let ivar: Ivar = class_getInstanceVariable(instance.dynamicType, "myInt")
let fieldOffset = ivar_getOffset(ivar)
// Pointer arithmetic to get a pointer to the field
let pointerToInstance = unsafeAddressOf(instance)
let pointerToField = UnsafeMutablePointer<Int?>(pointerToInstance + fieldOffset)
// Set the value using the pointer
pointerToField.memory = 42
assert(instance.myInt == 42)
Notes:
This is probably pretty fragile, you really shouldn't use this.
But maybe it could live in a thoroughly tested and updated reflection library until Swift gets a proper reflection API.
It's not that far away from what Mirror does internally, see the code in Reflection.mm, around here: https://github.com/apple/swift/blob/swift-2.2-branch/stdlib/public/runtime/Reflection.mm#L719
The same technique applies to the other types that KVO rejects, but you need to be careful to use the right UnsafeMutablePointer type. Particularly with protocol vars, which are 40 or 16 bytes, unlike a simple class optional which is 8 bytes (64 bit). See Mike Ash on the topic of Swift memory layout: https://mikeash.com/pyblog/friday-qa-2014-08-01-exploring-swift-memory-layout-part-ii.html
Edit: There is now a framework called Runtime at https://github.com/wickwirew/Runtime which provides a pure Swift model of the Swift 4+ memory layout, allowing it to safely calculate the equivalent of ivar_getOffset without invoking the Obj C runtime. This allows setting properties like this:
let info = try typeInfo(of: User.self)
let property = try info.property(named: "username")
try property.set(value: "newUsername", on: &user)
This is probably a good way forward until the equivalent capability becomes part of Swift itself.
Swift 5
To set and get properties values with pure swift types you can use internal ReflectionMirror.swift approach with shared functions:
swift_reflectionMirror_recursiveCount
swift_reflectionMirror_recursiveChildMetadata
swift_reflectionMirror_recursiveChildOffset
The idea is to gain info about an each property of an object and then set a value to a needed one by its pointer offset.
There is example code with KeyValueCoding protocol for Swift that implements setValue(_ value: Any?, forKey key: String) method:
typealias NameFreeFunc = #convention(c) (UnsafePointer<CChar>?) -> Void
struct FieldReflectionMetadata {
let name: UnsafePointer<CChar>? = nil
let freeFunc: NameFreeFunc? = nil
let isStrong: Bool = false
let isVar: Bool = false
}
#_silgen_name("swift_reflectionMirror_recursiveCount")
fileprivate func swift_reflectionMirror_recursiveCount(_: Any.Type) -> Int
#_silgen_name("swift_reflectionMirror_recursiveChildMetadata")
fileprivate func swift_reflectionMirror_recursiveChildMetadata(
_: Any.Type
, index: Int
, fieldMetadata: UnsafeMutablePointer<FieldReflectionMetadata>
) -> Any.Type
#_silgen_name("swift_reflectionMirror_recursiveChildOffset")
fileprivate func swift_reflectionMirror_recursiveChildOffset(_: Any.Type, index: Int) -> Int
protocol Accessors {}
extension Accessors {
static func set(value: Any?, pointer: UnsafeMutableRawPointer) {
if let value = value as? Self {
pointer.assumingMemoryBound(to: self).pointee = value
}
}
}
struct ProtocolTypeContainer {
let type: Any.Type
let witnessTable = 0
var accessors: Accessors.Type {
unsafeBitCast(self, to: Accessors.Type.self)
}
}
protocol KeyValueCoding {
}
extension KeyValueCoding {
private mutating func withPointer<Result>(displayStyle: Mirror.DisplayStyle, _ body: (UnsafeMutableRawPointer) throws -> Result) throws -> Result {
switch displayStyle {
case .struct:
return try withUnsafePointer(to: &self) {
let pointer = UnsafeMutableRawPointer(mutating: $0)
return try body(pointer)
}
case .class:
return try withUnsafePointer(to: &self) {
try $0.withMemoryRebound(to: UnsafeMutableRawPointer.self, capacity: 1) {
try body($0.pointee)
}
}
default:
fatalError("Unsupported type")
}
}
public mutating func setValue(_ value: Any?, forKey key: String) {
let mirror = Mirror(reflecting: self)
guard let displayStyle = mirror.displayStyle
, displayStyle == .class || displayStyle == .struct
else {
return
}
let type = type(of: self)
let count = swift_reflectionMirror_recursiveCount(type)
for i in 0..<count {
var field = FieldReflectionMetadata()
let childType = swift_reflectionMirror_recursiveChildMetadata(type, index: i, fieldMetadata: &field)
defer { field.freeFunc?(field.name) }
guard let name = field.name.flatMap({ String(validatingUTF8: $0) }),
name == key
else {
continue
}
let clildOffset = swift_reflectionMirror_recursiveChildOffset(type, index: i)
try? withPointer(displayStyle: displayStyle) { pointer in
let valuePointer = pointer.advanced(by: clildOffset)
let container = ProtocolTypeContainer(type: childType)
container.accessors.set(value: value, pointer: valuePointer)
}
break
}
}
}
This approach works with both class and struct and supports optional, enum and inherited(for classes) properties:
// Class
enum UserType {
case admin
case guest
case none
}
class User: KeyValueCoding {
let id = 0
let name = "John"
let birthday: Date? = nil
let type: UserType = .none
}
var user = User()
user.setValue(12345, forKey: "id")
user.setValue("Bob", forKey: "name")
user.setValue(Date(), forKey: "birthday")
user.setValue(UserType.admin, forKey: "type")
print(user.id, user.name, user.birthday!, user.type)
// Outputs: 12345 Bob 2022-04-22 10:41:10 +0000 admin
// Struct
struct Book: KeyValueCoding {
let id = 0
let title = "Swift"
let info: String? = nil
}
var book = Book()
book.setValue(56789, forKey: "id")
book.setValue("ObjC", forKey: "title")
book.setValue("Development", forKey: "info")
print(book.id, book.title, book.info!)
// Outputs: 56789 ObjC Development
if you are afraid to use #_silgen_name for shared functions you can access to it dynamically with dlsym e.g.: dlsym(RTLD_DEFAULT, "swift_reflectionMirror_recursiveCount") etc.
UPDATE
There is a swift package (https://github.com/ikhvorost/KeyValueCoding) with full implementation of KeyValueCoding protocol for pure Swift and it supports: get/set values to any property by a key, subscript, get a metadata type, list of properties and more.
Unfortunately, this is impossible to do in Swift.
KVC is an Objective-C thing. Pure Swift optionals (combination of Int and Optional) do not work with KVC. The best thing to do with Int? would be to replace with NSNumber? and KVC will work. This is because NSNumber is still an Objective-C class. This is a sad limitation of the type system.
For your enums though, there is still hope. This will not, however, reduce the amount of coding that you would have to do, but it is much cleaner and at its best, mimics the KVC.
Create a protocol called Settable
protocol Settable {
mutating func setValue(value:String)
}
Have your enum confirm to the protocol
enum Types : Settable {
case FirstType, SecondType, ThirdType
mutating func setValue(value: String) {
if value == ".FirstType" {
self = .FirstType
} else if value == ".SecondType" {
self = .SecondType
} else if value == ".ThirdType" {
self = .ThirdType
} else {
fatalError("The value \(value) is not settable to this enum")
}
}
}
Create a method: setEnumValue(value:value, forKey key:Any)
setEnumValue(value:String forKey key:Any) {
if key == "types" {
self.types.setValue(value)
} else {
fatalError("No variable found with name \(key)")
}
}
You can now call self.setEnumValue(".FirstType",forKey:"types")