Quantcast
Channel: Kodeco | High quality programming tutorials: iOS, Android, Swift, Kotlin, Unity, and more
Viewing all articles
Browse latest Browse all 4370

Swift Tutorial: Introduction to Generics

$
0
0
Why make three boxes when a generic one will do?

Why make three boxes when a generic one will do?

Generic programming is a way to write functions and data types without being specific about the types they use. Arrays are a good example – whether you have an array of strings or an array of integers or an array of AnyObject, they’re all arrays that work in the same way.

The difference is the type of thing they store, but there’s still just a single Array type. The big idea here is that you say what the array will hold when you declare it. In a way, it’s like having a type become an additional parameter to a function, saving you from writing many different versions of a similar function or data type.

You’ll find generics in use throughout Swift, which makes understanding them essential to a complete understanding of the language. In this tutorial, you’ll experiment in a Swift playground to learn what generics are and why they’re useful, how to write generic functions and data structures, as well as more intermediate tasks such as how to use type constraints and extend generic types. Finally, you’ll take a look at some of the new generics features in Swift 2.

Note: This tutorial requires Xcode 7 and Swift 2.

Getting Started

Begin by creating a new playground: In Xcode, go to File\New\Playground…, name the playground Generics and select OS X as the platform. Click Next to choose where you’d like to save your new playground and finally, click Create.

Suppose your application requires a function to add two integers. Add the following to your newly created playground:

func adderInt(x: Int, _ y: Int) -> Int {
  return x + y
}

adderInt(_:_:) takes two Int values and returns their sum. You can give it a try by adding the following code to the playground:

let intSum = adderInt(1, 2)

This is a pretty simple example that also demonstrates Swift’s type safety. You can call this function with two integers, but not any other type. Now suppose you also need to add two Double values. You would create a second function adderDouble:

func adderDouble(x: Double, _ y: Double) -> Double {
  return x + y
}
 
let doubleSum = adderDouble(1.0, 2.0)

The function signatures of adderInt and adderDouble are different, but the function bodies are identical. Not only do you have two functions, you’ve also repeated the code inside them. Soon you’ll see how you can use generics to reduce these two functions to one and remove the redundant code.

Arrays as Generic Types

Add the following to your playground:

let numbers = [1, 2, 3]
 
let firstNumber = numbers[0]

Here, you create a simple array of three numbers and then take the first number out of that array.

Now Option-click first on numbers and then on firstNumber. What do you see? Because Swift has type inference, you don’t have to explicitly define the types of your constants, but they both have an exact type: numbers is an [Int]—that is, an array of integers—and firstNumber is an Int.

The Swift Array type is a generic type, or one that requires a type parameter in order to be fully specified. Via type inference and also thanks to Swift’s type safety, numbers can only contain Int values, and when you take anything out of that array, Swift—and more importantly you—both know it must be an Int.

You can better see the generic nature of Array by forgoing type inference and the Swift array syntactic sugar and adding the following code to the end of the playground:

var numbersAgain = Array<Int>()
numbersAgain.append(1)
numbersAgain.append(2)
numbersAgain.append(3)
 
let firstNumberAgain = numbersAgain[0]

Check the types of numbersAgain and firstNumberAgain by Option-clicking on them; the types will be exactly the same as the previous values. Here you specify the type of numbersAgain using explicit generic syntax, by putting Int in angle brackets after Array.

Try appending something else to the array, like a Double or a String:

numbersAgain.append("hello")

You’ll get an error—something like, Cannot invoke 'append' with an argument list of type '(String)'. The compiler is telling you that you can’t add a string to an array of integers. As a method on the generic type Array, append is a so-called generic method. It knows the type of the containing array’s elements, and won’t let you add something of an incorrect type.

Other Generic Types

In Swift, some of the most common structures you use are generic types. You took a quick look at Array, above. Dictionaries and optionals are also generic types and result in type-safe data structures.

Create the following dictionary of country codes at the end of your playground and then look up the country code of Austria:

let countryCodes = ["Austria": "AT", "United States of America": "US", "Turkey": "TR"]
 
let at = countryCodes["Austria"]

Check the types of both declarations. You’ll see that countryCodes is a dictionary of String keys and String values—nothing else can ever be in this dictionary. The formal generic type is Dictionary.

The type of at is String?, which is shorthand for Optional, so you can see that optional types are generic types. Generics are all over the place! The compiler enforces that you can only look up string keys and you always get string values back. The string value is optional, because there might not be a value corresponding to the passed-in key.

Add the following to your playground to see the full explicit syntax for creating an optional string:

let optionalName = Optional<String>.Some("John")
 
if let name = optionalName {
}

Check the type of name, which you’ll see is String. Optional binding, that is, the if-let construct, is a generic operator of sorts. It takes a value of type T? and gives you a value of type T.

T_time

These are the main ideas behind generics. For generic functions, the parameter list and/or the return value depend on one or more generic types, such as T. You specify those types on an individual basis when you call the function, either explicitly or via type inference.

Now that you grasp of the basics of generics, you can learn about writing your own generic functions and types!

Writing a Generic Function

Add the following function to the bottom of the playground:

func pairsFromDictionary<KeyType, ValueType>(dictionary: [KeyType: ValueType]) -> [(KeyType, ValueType)] {
  return Array(dictionary)
}

Take a good look at the function declaration, parameter list and return type.

The function is generic over two types that you’ve named KeyType and ValueType. The only parameter is a dictionary from KeyType to ValueType, and the return value is an array of tuples of the form—you guessed it— (KeyType, ValueType).

You can use pairsFromDictionary on any valid dictionary and it will work, thanks to generics:

let pairs = pairsFromDictionary(["minimum": 199, "maximum": 299])
let morePairs = pairsFromDictionary([1: "Swift", 2: "Generics", 3: "Rule"])

Of course, since you can’t control the order in which the dictionary items go into the array, you may see an order of tuple values in your playground more like “Generics”, “Rule”, “Swift”, and indeed, they kind of do! :]

The compiler generates separate functions for each possible KeyType and ValueType, filling in the real types in the function declaration and body. In the first case of pairs, pairsFromDictionary returns an array of (String, Int) tuples, and in the second case of morePairs, it returns an array of (Int, String) tuples. At the code level, you created both of these using a single function.

Writing a Generic Data Structure

A queue is a data structure kind of like a list or a stack, but you can only add new values to the end (enqueuing) and only take values from the front (dequeuing). This concept might be familiar if you’ve ever used NSOperationQueue or Grand Central Dispatch, with dispatch_async and friends.

Add the following struct declaration to the end of your playground:

struct Queue<Element> {
}

Queue is a generic type over Element. Queue and Queue, for example, will become types of their own, generated by the compiler, that can only enqueue and dequeue strings and integers, respectively.

Add the following private property to the queue:

private var elements = [Element]()

You’ll use this array to hold the elements, which you initialize as an empty array. Note that you can use Element as if it’s a real type, even though it’ll be filled in later.

Finally, implement the two main queue methods:

mutating func enqueue(newElement: Element) {
  elements.append(newElement)
}
 
mutating func dequeue() -> Element? {
  guard !elements.isEmpty else {
    return nil
  }
  return elements.removeAtIndex(0)
}

Again, the type parameter Element is available everywhere in the struct body including inside methods. Making a type generic is like making every one of its methods implicitly generic over the same type. You’ve implemented a type-safe generic data structure, just like the ones in the standard library.

Play around with your new data structure for a bit at the bottom of the playground.

var q = Queue<Int>()
 
q.enqueue(4)
q.enqueue(2)
 
q.dequeue()
q.dequeue()
q.dequeue()
q.dequeue()

Also, have some fun by intentionally making as many mistakes as you can to trigger the different error messages related to generics—for example, add a string to your queue. The more you know about these errors now, the easier it will be to recognize and deal with them later.

Intermediate Topics

Now that you know the basics of creating and working with generic types and functions, it’s time to move on to some more advanced features. You’ve already seen how useful generics are to limit things by type, but you can add additional constraints as well as extend your generic types to make them even more useful.

Type Constraints and Where Clauses

The function below sorts an array and finds the middle value. Add it to your playground:

func mid<T>(array: [T]) -> T {
  return array.sort()[(array.count - 1) / 2]
}

You’ll get an error. The problem is that for sort() to work, the elements of the array need to be Comparable. You need to somehow tell Swift that mid can take any array as long as the element type implements Comparable.

Change the function declaration to the following:

func mid<T where T: Comparable>(array: [T]) -> T {
  return array.sort()[(array.count - 1) / 2]
}

Here, you use the where keyword to add a type constraint to the generic type parameter T. You can now only call the function with an array of comparable elements, so that sort() will always work! Try out the constrained function by adding:

mid([3, 5, 1, 2, 4]) // 3

In the case that where makes the type conform to a protocol, such as above, you can use the following syntactic sugar and remove where:

func mid<T: Comparable>(array: [T]) -> T {
  return array.sort()[(array.count - 1) / 2]
}

Other uses of where include when there is more than one generic type, and you want to add a constraint between two or more of them.

Now that you know about type constraints, you can create a generic version of the adder functions from the beginning of the playground. Add the following protocal and extensions to your playground:

protocol Summable { func +(lhs: Self, rhs: Self) -> Self }
 
extension Int: Summable {}
extension Double: Summable {}

First, you create a Summable protocol that says any type that conforms must have the addition operator + available. Then, you specify that the Int and Double types conform to it.

Now using a generic type T and a type constraint, you can create a generic function adder:

func adder<T: Summable>(x: T, _ y: T) -> T {
  return x + y
}

You’ve reduced your two functions (actually more, since you would have needed more for other Summable types) down to one and removed the redundant code. You can use the new function on both integers and doubles:

let adderIntSum = adder(1, 2)
let adderDoubleSum = adder(1.0, 2.0)

And you can also use it on other types, such as strings:

extension String: Summable {}
let adderString = adder("Generics", " are Awesome!!! :]")

By adding other conforming types to Summable, your adder(_:_:) function becomes more widely available thanks to its generics-powered definition!

Extending a Generic Type

Extend the Queue type and add the following method right below the Queue definition:

extension Queue {
  func peek() -> Element? {
    return elements.first
  }
}

peek returns the first element without dequeuing it. Extending a generic type is easy! The generic type parameter is visible just like in the original definition’s body. You can use your extension to peek into a queue:

q.enqueue(5)
q.enqueue(3)
q.peek()

You’ll see the value 5 as the first element in the queue, but nothing has been dequeued and the queue has the same number of elements as before.

Challenge: Extend the Queue type to implement a function homogeneous that checks if all elements of the queue are equal. You’ll need to add a type constraint in the Queue declaration to ensure its elements can be checked for equality to each other.

challenge_considered

Solution Inside: Homogeneous queue SelectShow>

New Generics Features in Swift 2

Along with many other language features, generics got a nice update in the latest Swift 2 release. Here, you’ll review some of the latest updates.

Subclassing Generic Classes

Swift 2 added the ability to subclass generic classes, which can be useful in some cases. Previously, you could subclass generic classes but they had to also be generic. Now, you can make a more “concrete” subclass of a generic class.

Add the following generic class to the playground:

class Box<T> {
 
}

You define a Box class. The box can contain anything, and that’s why it’s a generic class. There are two ways you could subclass Box:

  1. You might want to extend what the box does and how it works but keep it generic, so you can still put anything in the box;
  2. You might want to have a specialized subclass that always knows what’s in it.

Swift 2 allows both. Add this to your playground:

class Gift<T>: Box<T> {
}
 
class StringBox: Box<String> {
}

You define two Box subclasses here: Gift is a special kind of box that will presumably have different methods and properties defined on it, such as recipient and wrap(). However, it still has a generic on the type, meaning it could contain anything.

In contrast, StringBox is a box of strings, and thus isn’t generic anymore.

boxes

Declare instances of each class under the subclass declarations:

let box = Box<Int>()
let gift = Gift<Double>()
let stringBox = StringBox()

Notice that the StringBox initializer doesn’t need to take the generic type parameter anymore, since it’s fixed in the declaration of StringBox.

Enums With Multiple Generic Associated Values

Before WWDC 2015, when Swift still didn’t support the new error-handling mechanism, the most popular way to do “functional” error-handling was using a so-called result enum. This is a generic enum with two associated values: one for the actual result value and one for the possible error. Swift didn’t support this, and you would get a compiler error if you were lucky, and strange undefined behavior if you weren’t.

There were hacks to work around this limitation. Thankfully, Swift 2 supports declaring such a result type, among other useful enums with multiple generic associated values.

Add the following declaration to the end of your playground:

enum Result<ValueType, ErrorType> {
  case Success(ValueType)
  case Failure(ErrorType)
}

The main use case for such an enum is as a return value for a function with specific error information, kind of like a more general optional:

func divideOrError(x: Int, y: Int) -> Result<Int, String> {
  guard y != 0 else {
    return Result.Failure("Division by zero is undefined")
  }
  return Result.Success(x / y)
}

Here, you declare a function that divides two integers. If the division is legal, you return the resulting value in the .Success case; otherwise you return an error message.

Try out your new function:

let result1 = divideOrError(42, y: 2)
let result2 = divideOrError(42, y: 0)

The first result is a success case with a value of 21, and the second result is the failure case with the message Division by zero is undefined. Even though the enum type has two types associated with it, you can see how the cases are defined to use just one or the other.

Where to Go From Here?

Here’s the downloadable final playground with all the code you’ve written in this tutorial.

Generics are at the core of many common language features, such as arrays and optionals. You’ve seen how to use them to build your own container types and to avoid duplicating code that would only differ by type.

For more information, read through the Generics” chapter and the Generic Parameters and Arguments” language reference chapter of Apple’s guide, The Swift Programming Language. You’ll find more detailed information about generics in Swift, as well as some handy examples.

Thank you for taking the time to learn about generics in Swift, an integral feature that you’ll use everyday to write powerful and type-safe abstractions. You can get the best of both worlds – generic classes, while specifying an actual type to get type safety when you instantiate it.

If you have any questions, please let me know in the forum discussion below! :]

The post Swift Tutorial: Introduction to Generics appeared first on Ray Wenderlich.


Viewing all articles
Browse latest Browse all 4370

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>