Using Generics in Swift

Original author: Matt Galloway
  • Transfer
  • Tutorial
If you've already figured out how to program in Swift, then you probably already know the basics of the Swift language and how to write classes and structures. But Swift is more than that - much more. The purpose of this article is to talk about the very strong side of Swift, which has already become popular in several other languages ​​called generics .

Using a programming language that is safe with respect to types, this is a common problem like writing code that acts on only one type, but is quite correct for another type. Imagine, for example, that a function adds two integers. A function that adds two floating point numbers would look very similar - but in fact, it would look identical.

The only difference will be the type of value of the variables.

In a strongly typed language, you would have to define individual functions as addInts, addFloats, addDoubles, etc., where each function had the correct argument, and return types.

Many programming languages ​​implement solutions to this problem. C ++, for example, uses templates. Swift, like Java and C # use generics - hence the topic of this tutorial!

In this article about generalized Swift programming, you will dive into the world of existing generics in a programming language, including those that you have already seen. Then, create a photo search program on Flickr with a custom universal data structure to track user search criteria.

Note:This Swift functional programming article is for anyone who already knows the basics of Swift. If you are new to the basics of Swift, we recommend that you first check out some of our other Swift tutorials.

An Introduction to Generics

You might not know this, but you probably already saw Generics working in Swift. Arrays and dictionaries are classic examples of generic security in business.

Objective-C developers are used to arrays and dictionaries containing objects of different types in the same collection. This provides more flexibility, but did you know that the array returned from the API is intended for storage? You can see by looking at the documentation or variable names, another form of documentation. Even in the case of documentation, there is no way (other than code without errors!) To prevent collection failures at run time.

Swift, on the other hand, has arrays and dictionaries. An Ints array can only contain Ints and can never (for example) contain String. This means that you can register the code by writing code, which allows the compiler to perform type checking for you.

For example, in Objective-C UIKit, a method that handles touch based on a user view looks like this:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event; 


The set in this method, as you know, contains only UITouch instances, but only because the documentation says so. But nothing prevents objects from being something else, and you usually need to calculate the touch in a set as UITouch instances in order to effectively treat them as UITouch objects.

At this time, Swift does not have a set, which is defined in the standard library. However, if you used an array instead of a set, then you could write the above method as follows:

func touchesBegan(touches: [UITouch]!, withEvent event: UIEvent!)


This indicates that the touches array contains only UITouch instances, and the compiler will throw an error if the code, accessing this method, tries to pass something else. Not only are the types set / defined by the compiler, you no longer need to calculate the elements of UITouch instances!

In general, generics provide types as a parameter to a class. All arrays act the same way, storing values ​​in a list, but common arrays parameterize the type of value. You might find this useful if it were: The algorithms that you will use in arrays are not nonspecific, so that all arrays, with all types of values, can separate them.

Now that you have basic information about generics and their benefits, you can safely apply them to a specific scenario.

How Generics Work

To test generics, you must create an application that searches for images on Flickr.

Start by downloading the starter project for this tutorial. Open it and quickly familiarize yourself with the main classes. The Flickr class can handle to the Flickr API. Please note that the key for the API that is in this class is provided immediately, but you can use your own in case you want to expand the application. You can subscribe to one of them here .

Compile and run the application, and you will see this:

image

Not really yet! Do not be afraid, pictures with cats will appear soon.

Ordered Dictionaries

Your application will upload images for each user request, and the most recent search will be displayed as a list at the top of the screen.

But what if your user searches for the same item twice? It would be nice if the application moved the old results to the top of the new list and replaced it with the new result.

You can use an array for the data structure to return the result, but in order to study the generics, you need to create a new collection: an ordered dictionary.

In many programming languages ​​and frameworks (including Swift), sets and dictionaries do not guarantee any order, unlike arrays. An ordered dictionary looks like a regular dictionary, but contains keys in a specific order. You will use this functionality to store search results, which allows you to quickly find results and also maintain order in the table.

If you were imprudent, you can create a user data structure to process an ordered dictionary. But you are far-sighted! You want to create something that you can use in applications over the years to come! Generics are an ideal option.

Primary Data Structure

Add a new file by selecting File \ New \ File ... and then iOS \ Source \ Swift File. Click Next and name the file OrderedDictionary. Finally, click on Create.

As a result, you will have an empty Swift file and you will need to add the following code:

struct OrderedDictionary {  
}


So far, this is not surprising. An object will become a structure because it must have value semantics.

Note : In short, the semantics of meanings are an unusual way of saying “copy / paste” rather than “public link”. The semantics of values ​​provide many advantages, for example, there is no need to worry about another part of the code that can suddenly change your data. To learn more, go to Chapter 3 on how to understand Swift through the lessons, “ Classes and Structures” .

Now you need to make it a generic, so it can contain any type of value you want. Change the structure definition to the following:

struct OrderedDictionary


Elements in angle brackets are generic type parameters. KeyType and ValueType are not types in themselves, but become parameters that can be used instead of types within the structure definition. If you do not understand, then everything will become clear in the near future.

The easiest way to implement an ordered dictionary is to maintain both arrays and dictionaries. The dictionary will store the data transformation, and the array keys.

Add the following code to the structure definition:

typealias ArrayType = [KeyType]
typealias DictionaryType = [KeyType: ValueType]
var array = ArrayType()
var dictionary = DictionaryType()


This indicates two properties as described, as well as two type aliases that give a new name to the existing type. Here you respectively give aliases to arrays and types of dictionaries for backup arrays and dictionaries. Type aliases are a great way to take a complex type and give it a much shorter name.

Note that you can use the KeyType and ValueType type parameters from the structure definition instead of types. The array is an array of KeyTypes. Of course, there is no such type as KeyType; instead, Swift treats it like any type of ordered dictionary user during instantiation of a generic type.

At this point, you will notice a compiler error:

Type 'Keytype' oes not conform to protocol 'Hashable'

This might be a surprise to you. Take a look at the Dictionary implementation :

struct Dictionary


This is very similar to the definition of OrderedDictionary, except for one thing - “: Hashable” after KeyType. Hashable after the semicolon indicates that the type passed to KeyType must conform to the Hashable protocol. This is because the Dictionary must be able to hash keys for its implementation.

Limiting generic type parameters this way has become very common. For example, you might restrict the type of value to conform to the Equatable or Printable protocols, depending on what your application needs to do with those values.

Open OrderedDictionary.swift and replace your structure definition with the following:

struct OrderedDictionary


This shows that the KeyType for the OrderedDictionary should match Hashable. This means that no matter what type KeyType becomes, it will still be acceptable as a key for the main dictionary.

Now the file will compile without errors!

Keys, meanings and all that jazz.

What is the use of a dictionary if you cannot add values ​​to it? Open OrderedDictionary.swift and add the following function to your structure definition:

// 1
mutating func insert(value: ValueType, forKey key: KeyType, atIndex index: Int) -> ValueType?
{
  var adjustedIndex = index
  // 2
  let existingValue = self.dictionary[key]
  if existingValue != nil {
    // 3
    let existingIndex = find(self.array, key)!
    // 4
    if existingIndex < index {
      adjustedIndex--
    }
    self.array.removeAtIndex(existingIndex)
  }
  // 5
  self.array.insert(key, atIndex:adjustedIndex)
  self.dictionary[key] = value
  // 6
  return existingValue
}


All this will acquaint you with newer information. Let's look at them step by step:

  1. The method that helps to insert a new object, insert (_: forKey: atIndex), must have three parameters: value for a particular key and index, by which you can insert a key-value pair. There is a keyword here that you may not have seen before: mutating.
  2. You direct the key to the Dictionary indexer, which returns the existing value, if it already exists for that key. This insert method mimics the same behavior as updateValue in the Dictionary and therefore retains the existing value for the key.
  3. If there is an existing value, then only using the method will they find the index in the array for that key.
  4. If the existing key is in front of the insert index, then you must configure the insert index, since you will need to delete the existing key.
  5. If necessary, arrays and dictionaries will need to be updated.
  6. Finally, you return the existing value. Since there could not be an existing value, since the function returns an additional value.


Now that you have the ability to add values ​​to the dictionary, what about deleting values?

Add the following function to the structure definition in the OrderedDictionary:

// 1
mutating func removeAtIndex(index: Int) -> (KeyType, ValueType)
{
  // 2
  precondition(index < self.array.count, "Index out-of-bounds")
  // 3
  let key = self.array.removeAtIndex(index)
  // 4
  let value = self.dictionary.removeValueForKey(key)!
  // 5
  return (key, value)
}


Let's take another look at the code step by step:

  1. This is a function that modifies the internal state of the structure, and therefore you perceive it as such. The name removeAtIndex corresponds to the method on Array. It is a good idea to consider mirroring the API in the system library when you need it. This helps developers using your API feel free on the platform.
  2. First, you can check the index to see if it is within the array. Attempting to remove an item outside the valid range from the base array will throw a runtime error, so the check will detect all this a bit earlier. You may have used statements in Objective-C with the claim function; assert is also available in Swift, but precodition is currently being used in final builds so that your applications may exit if the preconditions stop working.
  3. Then, you will get the key from the array for the given index, while removing the value from the array.
  4. Then you remove the value for that key from the dictionary, which also returns the value that was present before. Since the dictionary may not contain a value for this key, removeValueForKey will return additional material. In this case, you know that the dictionary will contain a value for this key, because, the only method that can be added to the dictionary is your own insert (_: _: forKey: atIndex :), which you wrote. Thus, you can immediately reveal additional material, knowing that there will be value.
  5. Finally, you return the key and value to the tuple. This is consistent with the behavior of the array removeAtIndex and the Dictionary removeValueForKey, which return existing values.




You can now write access to values in the dictionary, but you cannot read from it - it is useless for the data structure! Now you need to add methods that will allow you to get values ​​from the dictionary.

Open OrderedDictionary.swift and add the following code to the definition structure, and point the dictionary under the array and variable declarations:

var count: Int {
  return self.array.count
}


This is a computed property for the number of ordered vocabulary, commonly needed data for such a data structure. The number in the array will always match the number of the ordered dictionary, so everything will be simple.

Then, you need to access the elements of the dictionary. In Swift, you will access the dictionary using the index syntax, as follows:

let dictionary = [1: "one", 2: "two"]
let one = dictionary[1] // Subscript


You are now familiar with the syntax, but you probably only saw that it was used for arrays and dictionaries. How would you use your own classes and structures? Swift, fortunately, makes it easy to add index behavior to custom classes.

Add the following code to the bottom of the structure definition:

// 1
subscript(key: KeyType) -> ValueType? {
  // 2(a)
  get {
    // 3
    return self.dictionary[key]
  }
  // 2(b)
  set {
    // 4
    if let index = find(self.array, key) {
    } else {
      self.array.append(key)
    }
    // 5
    self.dictionary[key] = newValue
  }


Here is what this code does:

  • This works the same as adding index behavior, only instead of func or var, the index keyword is used. The parameter, in this case the key, defines the object that appears in square brackets.
  • Subscripts can contain access methods, just like computed properties. Note that (a) get and (b) set methods respectively define access methods.
  • The get method is simple: You need to query the dictionary for the values ​​for this key. The dictionary subscript already returns additional material, which allows you to indicate that there is no value for that key.
  • The set method is more complex. First, it checks if the key already exists in the ordered dictionary. If it does not exist, then you must add it to the array. It is advisable for the new key to go to the end of the array, so you add the value to the array by adding.
  • And finally, you add a new value to the dictionary for the given key, passing to the new value through the implicitly named newValue variable.


Now you can index into an ordered dictionary, as if it were a regular dictionary. You can get the value for a specific key, but what about accessing with an index, as with an array? Seeing how it works with an ordered dictionary, it would be nice to also access the element through the index.

Classes and structures can have multiple index definitions for different types of arguments. Add the following function to the bottom of the structure definition:

subscript(index: Int) -> (KeyType, ValueType) {
  // 1
  get {
    // 2
    precondition(index < self.array.count, 
                 "Index out-of-bounds")
    // 3
    let key = self.array[index]
    // 4
    let value = self.dictionary[key]!
    // 5
    return (key, value)
  }
}


This is similar to the subscript you added earlier, except that the parameter type is now Int, because, this is what you use to refer to the index of the array. This time, however, the result type is a tuple of key and value, because it is your OrderedDictionary that stores the specified index.

How this code works:

  1. This subscript has only a getter method. You can implement the setter method for it also by first checking the indexes that are in the size range of the ordered dictionary.
  2. The index must be within the array, which determines the length of the ordered dictionary. Use a precondition to warn programmers who try to access outside an ordered dictionary.
  3. The key can be found by getting it from the array.
  4. The value can be found by obtaining it from the dictionary for a given key. Again, pay attention to the use of expanded additional material, because as you know, the dictionary must contain a value for any key that is in the array.
  5. Finally, you return a tuple containing the key and value.


Task: Implement a setter for this index. Add the set and then complete it, as in the previous index definition.

At this point, you may wonder what happens if KeyType is Int. The advantage of generics is the input of any hash type as a key, including Int. In this case, how does the index know which index code to use?

This is where you will need to give more type information to the compiler so that it knows what to do. Note that each index has a different return type. Therefore, if you are trying to set a tuple of a key value, the compiler will know that it should use a subscript in an array like manner.

System testing

Let's run the program so that you can experiment with how to compile, which index method to use, and how your OrderedDictionary works as a whole.

Create a new Playground by clicking on File \ New \ File ..., selecting iOS \ Source \ Playground and clicking Next. Name it ODPlayground and then click Create.

Copy and paste OrderedDictionary.swift into the new playground. You must do this because, unfortunately, at the time of writing this lesson, the site cannot “see” the code in your application module.

Note:There is a workaround for this, except for the copy / paste method that is implemented here. If you moved the code of your application to the framework, then your Playground can access your code, as Korin Krich points out.

Now add the following code to the playground:

var dict = OrderedDictionary()
dict.insert("dog", forKey: 1, atIndex: 0)
dict.insert("cat", forKey: 2, atIndex: 1)
println(dict.array.description 
        + " : " 
        + dict.dictionary.description)
var byIndex: (Int, String) = dict[0]
println(byIndex)
var byKey: String? = dict[2]
println(byKey)


On the side panel (or through View \ Assistant Editor \ Show Assistant Editor) you can see the output variable println ():

image

In this example, the dictionary has an Int key, since the compiler will consider the type of variable that will determine which index to use. Since byIndex is (Int, String) tuple, the compiler knows that you need to use the index version of the subscript array style that matches the expected return type.

Try to remove the data type definition from a single byIndex or byKey variable. You will see a compiler error, which indicates that the compiler does not know which subscript to use.

Hint:To run type inferences, the compiler requires the type of the expression to be unique. When several methods exist with the same types of arguments, but with different types of returns, the calling function must be specific. Adding a method to Swift can make critical changes to the assembly, so be careful!

Experiment with an ordered dictionary in the playground to understand how this works. Try adding to it, removing from it, and changing the key and type value before returning to the application.

Now you can read and write in your ordered dictionary! This will help take care of your data structure. Now you can start working with the application!

Adding an image search

It's time to go back to the application.

Open MasterViewController.swift add the following variable definition, just below the two @IBOutlets:

var searches = OrderedDictionary()


This should be an ordered dictionary that contains the search result that the user received from Flickr. As you can see, it displays a String, search criteria, an Flickr.Photo array, or photos returned from the Flickr API. Note that you direct the key and value to angle brackets in the same way as for a regular dictionary. They become parameters of type KeyType and ValueType in this implementation.

You may wonder why the Flickr.Photo type has a period. This is because photography is a class that is defined inside the Flickr class. This hierarchy is a pretty useful feature of Swift, helping to maintain the namespace while keeping class names short. Inside the Flickr class, you can use Photo alone, which belongs to the photo class, because the context tells the compiler what it is.

Next, find the table view data source method called tableView (_: numberOfRowsInSection :) and change it to the following code:

func tableView(tableView: UITableView, 
               numberOfRowsInSection section: Int) -> Int
{
  return self.searches.count
}


This method now uses an ordered dictionary that indicates how many cells our table has.

Next, find the tableview table view data source method (_: cellForRowAtIndexPath :) and change it to:

func tableView(tableView: UITableView, 
               cellForRowAtIndexPath indexPath: NSIndexPath)
              -> UITableViewCell
{
  // 1
  let cell = 
    tableView.dequeueReusableCellWithIdentifier("Cell", 
      forIndexPath: indexPath) as UITableViewCell
  // 2
  let (term, photos) = self.searches[indexPath.row]
  // 3
  if let textLabel = cell.textLabel {
    textLabel.text = "\(term) (\(photos.count))"
  }
  return cell
}


Here is what you do in this method:

  1. First, select a cell from the queue from the UITableView and direct it directly to the UITableViewCell since dequeueReusableCellWithIdentifier still returns to AnyObject (identifier in Objective-C) and not to UITableViewCell. Perhaps in the future, Apple will rewrite its API so that you can also take advantage of generics!
  2. Then, you will get the key and value for the given row, using the subscript at the index that you wrote.
  3. Finally, you install the text into the cell’s facet and return the cell.


Now let's talk about the "stuffing." Locate the UISearchBarDelegate extension and change the single method as follows:

func searchBarSearchButtonClicked(searchBar: UISearchBar!) {
  // 1
  searchBar.resignFirstResponder()
  // 2
  let searchTerm = searchBar.text
  Flickr.search(searchTerm) {
    switch ($0) {
    case .Error:
      // 3
      break
    case .Results(let results):
      // 4
      self.searches.insert(results, 
                           forKey: searchTerm, 
                           atIndex: 0)
      // 5
      self.tableView.reloadData()
    }
  }
}


This method is called when the user clicks on the Search button. Here is what is done in this method:

  1. Then, refer to the search criteria as text in the search bar right now, and use the Flickr class to search for this query. The Flickr search method uses both a search query and a closure to perform a successful or unsuccessful search. Closing resorts to only one parameter: listing either errors or results.
  2. In case of an Error, you will not see any signs about it. But you can do it, so a warning will appear if you want to, but why complicate things. The code needs to stop to notify the Swift compiler that the error will not do any harm.
  3. If the search works as it should, the search returns the results as a matching value in the SearchResults enumeration type. You can add results to the top of the ordered dictionary, with the search term as the key. If the search criteria already exists in the dictionary, then this will transfer the search criteria to the top of the list and update its results.
  4. Finally, you can reload the table of the screen, because now you have new data.


Yeah yeah! Now your application will search for images!

Compile and run the application and do a few searches. You will see something like this:

image

Now repeat one of the searches that are not at the top of the list. And you will see how he returns to the top of the list:

image

Clicked on one of the searches and notice that he does not show photos. It's time to fix it!

Give me photos!

Open MasterViewController.swift and find the prepareForSegue method. And replace it with:

override func prepareForSegue(segue: UIStoryboardSegue, 
                              sender: AnyObject?)
{
  if segue.identifier == "showDetail" {
    if let indexPath = self.tableView.indexPathForSelectedRow()
    {
      let (_, photos) = self.searches[indexPath.row]
      (segue.destinationViewController
         as DetailViewController).photos = photos
    }
  }
}


In this case, the same searches method is used for the ordered dictionary as when creating cells. It does not use the key (search by keyword), however, this way you specify by yourself, emphasizing that this part of the tuple should not be associated with a local variable.

Compile and run the application, do a search, and then click on it. You will see something like this:

image

Cats! Don't you feel like purring instead of such pleasure?

What's next?

Congratulations, you have learned a lot about generics! In addition, you learned about other interesting things like indexing, structures, preconditions, and much more.

If you want to learn more about Generics, you need to see the full chapter of the Swift tutorial.

I hope that you can use the full power of generics in your future applications to avoid code duplication and optimize code for repeated use. If you have any questions or comments, please join the discussion on the forum!

Also popular now: