How to compare two `Any` objects those actually conform to `Equatable` - swift

I want to implement a ValueObjectSharedExampleConfiguration: QuickConfiguration using Quick.
class ValueObjectSharedExampleConf: QuickConfiguration {
override class func configure(_ configuration: Configuration) {
sharedExamples("Value Object") {
(context: #escaping SharedExampleContext) in
describe("same objects") {
it("should be equal") {
let obj1a = context()["1a"]
let obj1b = context()["1b"]
expect(obj1a == obj1b).to(beTrue())
}
}
describe("different objects") {
it("should not be equal") {
let obj1 = context()["1a"]
let obj2 = context()["2"]
expect(obj1 == obj2).to(beFalse())
}
}
}
}
}
And then I want to test any classes/structs that conforms to Equatable with this shared Example like this:
itBehavesLike("Value Object") { [ "obj1a": foo1a, "obj1b": foo1b, "obj2": foo2] }
But the problem is, SharedExampleContext is actually a closure returns [String: Any], so obj1a, obj1b, obj2 variables I get in sharedExample closure are all of type Any, which doesn't necessarily conform to Equatable. Thus the code obj1a == obj1b won't compile.
Actually if I check obj1a is Equatable it returns true. But I don't know how to cast it to a proper type that compiler will accept. obj1a as! Equatable won't compile because Equatable is a generic protocol.
I can't just write obj1a as! Foo because if there is another class Bar: Equatable I want my sharedExample also works for that.
The main problem here is: I have two variables cast to Any, which are guaranteed to be originally of same type that conforms to Equatable. How should I legally compare these two variables without knowledge of the actual type of them ?

You could have a MyComparable protocol with a function isEqualTo(object: Any) and implement it for Foo and Bar. You can then:
let object1 = obj1 as! MyComparable
object1.isEqualTo(obj2)
and in the implementation of isEqualTo check the type or force convert it if you are sure it is always the same:
class Foo: MyComparable {
func isEqualTo(_ object: Any) -> Bool {
let obj2 = object as! Foo
return self == obj2
}
}

I found an strange solution.
func == (lhs: any Equatable, rhs: any Equatable) -> Bool {
return ([lhs] as NSArray) == ([rhs] as NSArray)
}
Here is the code tested with swift 5.7.1
protocol GenericItem: Hashable {
associatedtype Element
var value: Element { get }
}
struct StringItem: GenericItem {
var value: String
}
struct IntItem: GenericItem {
var value: Int
}
// comparison function [A]
func == (lhs: any Equatable, rhs: any Equatable) -> Bool {
return ([lhs] as NSArray) == ([rhs] as NSArray)
}
let item1: any GenericItem = StringItem(value: "hello")
let item2: any GenericItem = StringItem(value: "hello")
let item3: any GenericItem = StringItem(value: "world")
let item4: any GenericItem = IntItem(value: 24)
// without [A] it causes error as follows
// item1 == item2 // Binary operator '==' cannot be applied to two 'any GenericItem' operands
([item1] as NSArray) == ([item2] as NSArray) // true
([item2] as NSArray) == ([item3] as NSArray) // false
item1 == item2 // true
item2 == item3 // false
item3 == item4 // false
Since StringItem nor IntItem are not inherited from NSObject, I am not sure how NSArray or NSDictionary compares two instances of any Equatable. If we know how it works behind the scenes, we may come up with better implementation.
Note: Strange to say, if you change Hashable to Equatable, it behaves differently.
protocol GenericItem: Equatable { // Hashable -> Equatable
associatedtype Element
var value: Element { get }
}
// ...snip...
item1 == item2 // false <-- !?
item2 == item3 // false
item3 == item4 // false
It may not be a stable behavior. If you like to try, please test you code well before ship your products.

Related

Comparing Two Protocol Instances for Equality in Swift

Here's the deal,
I'm writing an SDK, and I want to declare observers as protocols, instead of classes or structs (It's sort of an "Observer/Delegate" hybrid).
I want to be able to compare two arguments that are passed in as protocol references, as opposed to the concrete classes/structs they actually are, IRL.
I know that the "easy" way to get comparison is to constrain the protocols to Hashable or Equatable, but I want to avoid burdening the user (It's an SDK).
Here's a little playground with what I mean:
protocol A {
func AFunc() -> String
}
class APrime: A {
func AFunc() -> String { "I AM GROOT" }
}
let variableA = APrime()
let variableB = APrime()
func compareTypes(_ inA: A, _ inB: A) -> String {
// if inA == inB {
// return ""
// }
return "not "
}
print("A is \(compareTypes(variableA, variableB))B.")
print("A is \(compareTypes(variableA, variableA))A.")
The raunchy bit is the commented-out section in compareTypes(_: A, _: A). I need to figure out how to compare them without going into "Hacksylvania," which I could do by doing something like comparing addresses of the AFunc() in each instance.
The expected output is:
A is not B.
A is A.
Any ideas for a more "swifty" approach? I must be missing the forest for the trees.
Just to add some closure to this, here is how I solve this:
protocol A {
var uuid: Int { get } // This is the secret sauce. It will contain a unique UUID, associated with the instance.
func AFunc() -> String
}
class APrime: A {
let uuid: Int = Int.random(in: 0..<1000) // The UUID is initialized with the instance.
func AFunc() -> String { "I AM GROOT" }
}
let variableA = APrime()
let variableB = APrime()
let variableC = variableA
func compareTypes(_ inA: A, _ inB: A) -> String {
if inA.uuid == inB.uuid { // We compare UUIDs.
return ""
}
return "not "
}
print("C is \(compareTypes(variableC, variableB))B.")
print("C is \(compareTypes(variableC, variableA))A.")
The "uuid" variable is usually an actual UUID type, but I didn't want to import Foundation in the example, so I just did a simple rand. It gets the point across.
This outputs:
C is not B.
C is A.
And there is another way (that I also use, sometimes):
protocol B {
func BFunc() -> String
func amIThisOne(_ instanceToCompare: B) -> Bool // This is an identity comparator
}
class BPrime: B {
func BFunc() -> String { "I AM GROOT'S BROTHER" }
// We compare ourselves against the other instance, assuming it can be cast to our own type.
func amIThisOne(_ inInstanceToCompare: B) -> Bool {
guard let instanceToCompare = inInstanceToCompare as? Self else { return false }
return self === instanceToCompare
}
}
let variableD = BPrime()
let variableE = BPrime()
let variableF = variableD
print("D is \(variableE.amIThisOne(variableD) ? "" : "not ")E.")
print("D is \(variableD.amIThisOne(variableF) ? "" : "not ")F.")
Which outputs:
D is not E.
D is F.
This allows a more programmatic way of comparing the instances.
HOW NOT TO DO IT
And then, of course, if we have control of the instances, we can truly do the Equatable thing (This requires that the playground import Foundation):
protocol C: Equatable {
func CFunc() -> String
}
class CPrime: C {
// This is actually not what I want, as I want to compare protocols, not conforming classes.
static func == (lhs: CPrime, rhs: CPrime) -> Bool {
guard let lhs = lhs as? Self else { return false }
guard let rhs = rhs as? Self else { return false }
return lhs === rhs
}
func CFunc() -> String { "I AM GROOT'S UDDER BROTHER" }
}
let variableG = CPrime()
let variableH = CPrime()
let variableI = variableG
print("G is \(variableG == variableH ? "" : "not ")H.")
print("G is \(variableI == variableG ? "" : "not ")I.")
Which outputs:
G is not H.
G is I.

Can you check if a Type (not an instance) is a subclass of another Type?

Given this code...
class Vehicle{}
class Car : Vehicle {}
class Honda : Car {}
How would you write the function 'findFirst' below...
class TypeManager {
var managedTypes:[Any.Type]?
func findFirst(_ type:Any.Type) -> Any.Type? {
return managedTypes.first{ t in t is type.Type } // <-- Doesn't like 'type'
}
}
var typeManager = TypeManager()
typeManager.managedTypes = [
String.self,
Int.self,
Honda.self
]
let firstCarType = typeManager.findFirst(Car.Type)
Note: This is actually doing a reverse-key-lookup on a dictionary. In a perfect solution, I'd first try finding an exact match on 'type', and if not found, settle for a subclass of 'type'. I just simplified the (faked) code to focus on the matching portion.
To expand upon Martin R's great answer you can make an array extension like the following:
extension Array {
func first<T>(ofType: T.Type) -> T.Type? {
return first { $0 is T.Type } as? T.Type
}
func first<T>(ofExactType type: T.Type) -> T.Type? {
return first { $0 as? Any.Type == type } as? T.Type
}
}
class Vehicle {}
class Car : Vehicle {}
class Honda: Car {}
let carTypes = [Honda.self, Vehicle.self, Car.self] // Inferred type [Vehicle]
print(carTypes.first(ofType: Car.self) ?? "n/a") // prints Honda
print(carTypes.first(ofExactType: Car.self) ?? "n/a") // prints Car
Also, just FYI, $0 as? Any.Type == type is the same as doing $0 as? Any.Type == T.self. Either one would work.
Classes are instances of a meta-type and can be checked with is and as?.
You can use a generic function to pass in the sought type:
class TypeManager {
var managedTypes:[Any.Type] = []
func findFirst<T>(_: T.Type) -> Any.Type? {
return managedTypes.first { $0 is T.Type }
}
}
Example:
if let firstCarType = typeManager.findFirst(Car.self) {
print(firstCarType) // Honda
}
Or with conditional binding and compactMap:
class TypeManager {
var managedTypes:[Any.Type] = []
func findFirst<T>(_: T.Type) -> T.Type? {
return managedTypes.compactMap { $0 as? T.Type }.first
}
}
This has the advantage that the returned type is T.Type? and not Any.Type?. (Use managedTypes.lazy.compactMap if the list can be large and short circuiting is wanted.)

Swift 3 generics / associated types limitations with protocols

I have a generic protocol:
protocol SectionType {
associatedtype I: Equatable
associatedtype T: Equatable
var info: I? { get }
var items: [T] { get }
}
and an Array extension for it:
/// Offers additional method(s) to process SectionType list.
extension Array where Element: SectionType {
/// Dummy comparision method. Implementation is not that relevant but just to employ Equatable.
/// - Parameter to: Another array of sections.
/// - Returns: True if first info and first elemements in both arrays exist and both are equal.
func dummyCompare(with otherArray: [Element]) -> Bool {
guard
let first = self.first,
let firstOther = otherArray.first,
let firstElement = first.items.first,
let firstOtherElement = firstOther.items.first,
let firstInfo = first.info, let firstOtherInfo = firstOther.info
else { return false }
return firstInfo == firstOtherInfo && firstElement == firstOtherElement
}
}
No problem when using with concrete implementations:
Example 1: Works with specific implementation with build-in String type:
Declaration:
struct StringSection: SectionType {
let info: String?
let items: [String]
}
Usage:
let stringSection1 = StringSection(info: "Info 1", items: ["Item 1", "Item 2"])
let stringSection2 = StringSection(info: "Info 2", items: ["Item 3", "Item 4"])
let stringSections1 = [stringSection1, stringSection2]
let stringSections2 = [stringSection2, stringSection1]
var areEqual = stringSections1.dummyCompare(with: stringSections2)
print("String section 1 equals 2?: \(areEqual)")
Example 2 - Works with specific implementation with custom types:
Declarations:
protocol SectionInfoType {
var title: String { get }
}
/// BTW: This is just Swift's stragne way of implementing Equatable protocol for your type:
func == (lhs: SectionInfoType, rhs: SectionInfoType) -> Bool {
return lhs.title == rhs.title
}
struct SpecificSectionInfo: SectionInfoType, Equatable {
let title: String
static func == (lhs: SpecificSectionInfo, rhs: SpecificSectionInfo) -> Bool {
return lhs.title == rhs.title
}
}
protocol SectionItemType {
var text: String { get }
}
/// BTW: This is just Swift's stragne way of implementing Equatable protocol for your type:
func == (lhs: SectionItemType, rhs: SectionItemType) -> Bool {
return lhs.text == rhs.text
}
struct SpecificSectionItem: SectionItemType, Equatable {
let text: String
static func == (lhs: SpecificSectionItem, rhs: SpecificSectionItem) -> Bool {
return lhs.text == rhs.text
}
}
struct SpecificSection: SectionType {
let info: SpecificSectionInfo?
let items: [SpecificSectionItem]
}
Usage:
let specInfo1 = SpecificSectionInfo(title: "Info 1")
let specItem1 = SpecificSectionItem(text: "Specific item 1")
let specItem2 = SpecificSectionItem(text: "Specific item 2")
let specInfo2 = SpecificSectionInfo(title: "Info 2")
let specItem3 = SpecificSectionItem(text: "Specific item 3")
let specItem4 = SpecificSectionItem(text: "Specific item 4")
let specSection1 = SpecificSection(info: specInfo1, items: [specItem1, specItem2])
let specSection2 = SpecificSection(info: specInfo2, items: [specItem3, specItem4])
let specSections1 = [specSection1, specSection2]
let specSections2 = [specSection2, specSection1]
let areEqual = specSections1.dummyCompare(with: specSections2)
print("Specific section 1 equals 2?: \(areEqual)")
So far, so good, everything works and compiles. But ... I have at least 2 problems with this approach:
Problem 1:
Just from 2 examples above, one can see that this approach needs 'specific' implementation of a SectionType protocol for every combination of info and items type. This seems not so efficient (tremendous amount of code for every each implementation) nor generic.
What I need is a more generalised embodiment of SectionType protocol where types for info and items need to be protocols (provided from external APIs as protocols).
A perfect example (but does not compile):
struct Section: SectionType {
typealias I = SectionInfoType
typealias T = SectionItemType
let info: I?
let items: [T]
}
Problem 2:
I need to be able to pass it over to other protocol oriented API, f.ex.: as an argument to function like:
func consume(section: SectionType<SectionInfoType, SectionItemType>) {}
But above function declaration produces:
Cannot specialize non-generic type 'SectionType'
with Xcode proposing a fix: 'Delete <SectionInfoType, SectionItemType>' resulting in this:
func consume(section: SectionType) {}
This does not compile neither and I can not find a way to make it work.
Is it possible to do or is that Swift 3 limitation (I am using Swift 3.1)?
I created Git repository demonstrating those issues. Feel free to collaborate:
https://github.com/lukaszmargielewski/swift3-learning-generics
If I understand correctly problem 2 you want your a function that consumes a generic variable that is also specialised, adhering to protocol SectionType. Try:
func consume<Section: SectionType>(section: Section) {
let foo = section.info
let foobar = section.items
}

Swift sorting on arbitrary types

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...

Runtime comparison of Swift Protocol MetaTypes

I'm trying to dynamically match a Swift protocol to an implementation, but I've gotten blocked on trying to perform comparisons of Protocols at runtime - it seems that maybe protocols don't really exist at runtime?
Some examples of things I've tried:
var protocols[Any]
func findProtocol(aProtocol: Any) -> Bool {
// Nope, protocols don't implement equatable
aProtocol == protocols[0]
// Doesn't work, unsafeAddressOf() only applies to AnyObjects
let pointer: UnsafePointer = unsafeAddressOf(aProtocol)
}
I think I might have hit the boundaries of trying to defeat the type system... any thoughts?
If you know that you compare the types themselves you should use a more appropriate type (Any.Type):
var protocolArray: [Any.Type] = [...]
func findProtocol(aProtocol: Any.Type) -> Bool {
// you can do that because Any.Type has an == operator
return protocolArray.contains{ $0 == aProtocol }
}
For Any type you have to cast it:
var protocolArray: [Any] = [...]
func findProtocol(aProtocol: Any) -> Bool {
return protocolArray.contains{
if let p1 = $0 as? Any.Type, p2 = aProtocol as? Any.Type {
return p1 == p2
}
return false
}
}
I may be slightly misunderstanding what you're looking to do, but you should be able to use reflection for this. How about something like this?
protocol One {}
protocol Two {}
protocol Three {}
var protocols: [Any] = [One.self, Two.self]
func findProtocol(aProtocol: Any) -> Bool {
let findMirror = Mirror(reflecting: aProtocol)
for checkProtocol in protocols {
let mirror = Mirror(reflecting: checkProtocol)
if findMirror.subjectType == mirror.subjectType {
return true
}
}
return false
}
findProtocol(One) // Returns true
findProtocol(Two) // Returns true
findProtocol(Three) // Returns false