Now, with knowledge of Protocols in Swift in hand, let’s get to talking about one of the other big additions to the Swift language: Generics. I had previously discussed in my post Swift Optionals – Declaration, Unwrapping, and Binding how Swift optionals work under the hood. In that case, the OptionalValue enum is a generic type. Today we are going to cover a slightly easier aspect of generics, generic functions. Generic functions are functions that can take parameters of any type (with or without constraints based on protocol adoption), and perform some action with them.
A Fully Generic Function
How would you check if an arbitrary object is an Objective-C object? For the most part, you would have to check if it inherits from NSObject. Anytime I read introduction to Objective-C object texts, they will say that almost all objects inherit from NSObject. I have yet to find one that does not for Objective-C myself, but just in case, I had to give that disclaimer.
Update: Reader Doug Knowles has pointed out (sorry that the update of this is MUCH later) to me that at least ONE class does not inherit from NSObject. The caveat to this is that it adopts the NSObjectProtocol, which, when conformed to, makes it act much like a real object. I din’t know much about NSProxy personally, but the documentation says that an NSProxy object basically stands-in for a real object that has yet to exist. I’m thinking that NSProxy probably won’t be used much in Swift, seems a bit more dynamic than Swift prefers to be, but hey, at least we know probably the only Objective-C class that does not inherit from NSObject!
Anyway, here is a function that can take any object, and then returns a Bool describing whether the object inherits from NSObject:
func isAnNSObject<T>(_ someObject: T) -> Bool { if someObject is NSObject { return true } else { return false } }
I am using a new operator there that I have not used before. The “is” operator checks if a value is of a certain type (or inherits from it). You can read more about this operator on the post Type Casting in Swift.
So for this function, I am checking if “someObject” is an NSObject (or at least inherits from it). If it is, the function returns true, if it is not, it returns false.
After the name of the function, you see a <T>. The “T” in the angle brackets is a placeholder name for a type. The function prototype then says that the input parameter must be of that type. That’s all there is to it for a simple generic function. We only have one parameter in this function, but if we had multiple, we would state that the parameters must be of the same type by giving them both the same type placeholder (“T” in this case). If they can be other types, then different placeholder values can be used for the different types.
We then use this function like any other function. As far as calling a generic function goes in Swift, there is no difference, you just give it the arguments like normal. Here is a simple test of that function:
class BlankClass { } let objcObject = NSDate() let swiftObject = BlankClass() isAnNSObject(objcObject) //returns "true" isAnNSObject(swiftObject) //returns "false"
Generic Function with Type Constraints
That’s all well and good, but there are only a few times you would ever be able to take absolutely ANY kind of value, you usually have to give your generic function some constraints. For instance, you could make a function that returns the maximum value of two Ints or Doubles that you put into it, like these functions:
func maximumInt(_ first: Int, _ second: Int) -> Int { if (first >= second) { return first } return second } func maximumDouble(_ first: Double, _ second: Double) -> Double { if (first >= second) { return first } return second }
Those are well and good too, but they only take Ints and Doubles. This sounds like the perfect time for a generic function! But we couldn’t just let it take ANY class and return that. If we made a fully generic function that had no constraints, and give it a couple of objects of some arbitrary class I make up like “Car”, and tell it to give me the maximum… What does it do? Which is the maximum “Car” object?
This is where type constraints come in. Remember in Protocols in Swift, we talked about whether a class was Equatable or not? There is another protocol that is much much more useful here, the Comparable protocol.
Swift’s Comparable protocol itself inherits from the Equatable protocol, and has a requirement of its own. To conform to the Comparable protocol itself, you must implement the less-than “<” operator. Then to conform the the inherited Equatable protocol, the class must implement how the “==” operator applies to that class. Once you implement both of those requirements, you automatically get the use of >, <=, and >= (since they are just combinations of the two you did implement with some boolean NOTs mixed in).
With that in mind, here is how we make a Generic form of our maximumInt and maximumDouble functions:
func maximumValue<T: Comparable>(_ first: T, _ second: T) -> T { if (first >= second) { return first } return second }
One thing to note, the method body is the exact same between all three functions. They don’t really need the type information inside, as long as they are the same and conform to the Equatable protocol.
We verify that the type conforms to the Comparable protocol by basically treating that protocol as a type of the placeholder type name, as you see:
<T: Comparable>
Doesn’t that look an awful lot like the name of a variable (in this case “T”), and the type of that variable (in this case “Comparable“)? While I’m not certain if that was intended, it’s certainly a good way to remember how this syntax works in Swift.
We then verify that our two values “first” and “second” both are the same type “T”, and then we will return type T. So now we can use this for Ints, Doubles, or any types that conform to the Comparable protocol.
Using a Custom Class with our Constrained Generic Function
I don’t know how to objectively compare “Cars” like my earlier idea suggests, but let’s compare something more objective, like a contestant of a game show. They have a score, which you can compare, so let’s make a contestant class:
class Contestant { var name: String var score = 0 init(name: String) { self.name = name } } extension Contestant: Comparable { static func < (lhs: Contestant, rhs: Contestant) -> Bool { return lhs.score < rhs.score } static func == (lhs: Contestant, rhs: Contestant) -> Bool { return lhs.score == rhs.score } }
Now, there are some new things in here, like operator overloading. At least for the Comparable protocol, you have to give these operators functions at the global scope (so outside of the class). In this case, I just wanted to compare contestants by their score. You can read more about operator overloading in the post Operator Overloading — Tailor Swift To Your Needs.
Now yes, we could just compare their scores, but then we would get the maximum score returned, not the object with the maximum score. This way, we can just compare contestants, and it will look inside the Contestant object, and compare their scores, so we would use our new function like this:
let Lee = Contestant(name: "Lee") let Roy = Contestant(name: "Roy") Lee.score = 3 Roy.score = 7 let winner = maximumValue(Lee, Roy) print(winner.name) //Outputs "Roy"
There’s our own custom class that adopts the Comparable protocol, and works with our generic function to see who has the highest score. Now that is quite useful!
Conclusion
Generics are a very powerful tool in Swift. In this post, we’ve seen how they allow you to make a single function to handle multiple types, as long as the conform to some appropriate constraints. We have not even touched the concept of generic types yet (but we do later, in the post Generic Types in Swift), but you probably have worked with them already. The Swift Array and Dictionary types are actually generic type collections under the hood.
One thing to note, similar to the “swap” example in Apple’s Swift iBook, we reimplemented something that already exists in Swift for an example. Swift’s standard library already has a function named “max” that does this for us. So, while we can implement one of our own, you might as well use the built in one.
I hope you found this article helpful. If you did, please don’t hesitate to share this post on Twitter or your social media of choice, every share helps. Of course, if you have any questions, don’t hesitate to contact me on the Contact Page, or on Twitter @CodingExplorer, and I’ll see what I can do. Thanks!