📜 ⬆️ ⬇️

Using generics in Swift

If you have already figured out how to program in Swift, then you probably already know the basics of Swift and how to write classes and structures. But Swift - more than that - much more. The goal of this article is to tell about the very strong side of Swift, which has already become popular in several other languages ​​called generics .

Using a type-safe programming language, this is a common problem as writing code that affects only one type, but is completely 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 is the type of value of the variables.

In a strongly typed language, you would have to define separate functions like addInts, addFloats, addDoubles, etc., where each function had a valid argument, and types of return values.
')
Many programming languages ​​implement solutions to this problem. C ++, for example, uses templates. Swift, as Java and C # use generics - hence the topic of this lesson!

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

Note: This Swift functional programming article is for those who already know the basics of Swift. If you are new to the basics of Swift, we recommend that you first look at some of our other lessons about Swift.

Introduction to generics

You might not know this, but you have probably already seen Generics at work at Swift. Arrays and dictionaries are classic examples of generic security in action.

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 to be stored? You can verify by looking at the documentation or the names of variables, another form of documentation. Even in the case of documentation, there are no ways (except for the error-free code!) To prevent the collection from failing at runtime.

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

For example, in Objective-C UIKit, a method that handles a touch based on a custom view is as follows:

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


The set in this method, as is known, contains only UITouch instances, but only because it is said in the documentation. But nothing prevents objects from being something else, and you usually have to calculate touch in a set as UITouch instances in order to effectively view 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 generate an error if the code, when accessing this method, tries to transfer something else. Not only does the type set / define the compiler, you no longer need to calculate the elements of the UITouch instances!

In general, generics provide types as a class parameter. All arrays act in the same way, storing values ​​in a list, but common arrays parameterize the type of value. You might find this useful if this was the case: The algorithms you will use in arrays are not atypical, 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 do generics work

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

Start by downloading the startup project for this lesson. Open it and quickly familiarize yourself with the main classes. The Flickr class can handle 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 worry, pictures with cats will appear soon.

Ordered dictionaries

Your app will load images for each user request, and the most recent search will display as a list at the top of the screen.

But what if your user searches 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 a 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, allowing you to quickly find results, as well as maintain a table in order.

If you were imprudent, you can create a user data structure to process an ordered dictionary. But you are farsighted! You want to create something that you can use in applications for many years to come! Generics are perfect.

Primary data structure

Add a new file by selecting File \ New \ File ... and then iOS \ Source \ Swift File. Click on 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 { } 


While this is not surprising. The object will become a structure, because it must have semantics of values.

Note : In a word, semantics of values ​​is an unusual way to say “copy / paste” rather than “public link”. The semantics of values ​​provides many advantages, for example, there is no need to worry about another part of the code that can unexpectedly change your data. To learn more, go to Chapter 3 how to understand Swift with the help of lessons, “ Classes and Structures” .

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

 struct OrderedDictionary<KeyType, ValueType> 


Items in angle brackets are generic type parameters. KeyType and ValueType are not types 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 soon.

The easiest way to implement an ordered dictionary is to maintain both arrays and dictionaries. The dictionary will store the data conversion, 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 an 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 parameters of 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 as any type of user of an Ordered Dictionary during instantiation of a generic type.

At this point, you will notice a compiler error:

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

This could be a surprise for you. Take a look at the implementation of the Dictionary :

 struct Dictionary<KeyType: Hashable, ValueType> 


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

Limiting generic type parameters in this way has become very common. For example, you might limit 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<KeyType: Hashable, ValueType> 


This shows that the KeyType for OrderedDictionary must be 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, values ​​and all that stuff.

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 introduce you to newer information. Let's look at them step by step:

  1. A method that helps insert a new object, insert (_: forKey: atIndex), must have three parameters: value for a specific key and index by which a key-value pair can be inserted. There is a keyword here that you may not have seen before: mutating (modifying).
  2. You send 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 the Dictionary updateValue and therefore retains the existing value for the key.
  3. If there is an existing value, then only with the help of the method and find the index in the array for that key.
  4. If the existing key is before the insert index, then you must configure the insert index, since you will need to delete the existing key.
  5. If necessary, you will need to update the arrays and dictionaries.
  6. Finally, you return the existing value. Because there could be no existing value, since the function returns an additional value.


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

Add the following function to the structure definition in 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 needed. This helps developers using your API to feel at ease on the platform.
  2. First, you can check the index to see if it is within an array. Attempting to delete an element outside the allowable range from the underlying array will generate a runtime error, so the check will detect it all a little earlier. You may have used assertions in Objective-C with the approve function; assert is available in Swift as well, but precodition is currently being used in final builds so that your applications can complete if the prerequisites 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 delete 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 a given key, removeValueForKey will return additional material. In this case, you know that the dictionary will contain the value for a given key, because this is the only method that can add to the dictionary, is your own insert (_: _: forKey: atIndex :) that you wrote. Thus, you can immediately uncover additional material, knowing that there will be a value.
  5. Finally, you return the key and value to the tuple. This corresponds to the behavior of the array removeAtIndex and Dictionary removeValueForKey, which return the existing values.


Access to values

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

Open OrderedDictionary.swift and add the following code to the definition structure, and specify 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, usually the necessary data for such a data structure. The number in the array will always correspond to the number of the ordered dictionary, so everything will be just

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


This is what this code does:



Now you can index into an ordered dictionary, as if it were a regular dictionary. You can get the value for a particular key, but what about having access using the index, as with an array? Seeing how this works with an ordered vocabulary, it would be nice to also access the item through an 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 array index. This time, however, the result type is a tuple of a key and a 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 a setter method for it also by first checking the indices that are in the size range of an 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 attempt to gain access outside an ordered dictionary.
  3. The key can be found by getting it from the array.
  4. The value can be found by retrieving it from the dictionary for a given key. Again, note 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 the setter for this index. Add a set followed by completion, as in the previous index definition.

At this point, you may be wondering what will happen if KeyType is an int. The advantage of generics is to enter 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. Please note that each index has a different return type. Therefore, if you try to set a tuple of key values, the compiler will know that it must use a subscript in the similarity of an array.

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 to the new playground. You have to do this because, unfortunately, at the time of writing this lesson, the site cannot “see” the code in your module of the application.

Note: There is a workaround for this, except as a copy / paste method, which is implemented here. If you moved your application code to a framework, then your Playground can access your code, as indicated by Korin Creech.

Now add the following code to the playground:

 var dict = OrderedDictionary<Int, String>() 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 sidebar (or via 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 the variable that will determine which index to use. Since byIndex is (Int, String) tuple, the compiler knows that you need to use an indexed version of the subscript array style that matches the expected type of the return value.

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

Hint: To run type inference, the compiler requires that the type of the expression be unambiguous. When several methods exist with the same argument types, but with different return types, then the calling function must be specific. Adding a method to Swift can make critical changes to the assembly, so be careful!

Experiment with the ordered vocabulary in the playground to understand how it works. Try adding to it, removing it, and changing the key and value of the types before returning to the application.

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

Add image search

It's time to go back to the app.

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

 var searches = OrderedDictionary<String, [Flickr.Photo]>() 


This should be an ordered dictionary that contains the search result that the user received from Flickr. , String, , Flickr.Photo, , API Flickr. , , . KeyType ValueType .

, Flickr.Photo . , , Flickr. Swift, , . Flickr, Photo, , , .

, tableView(_:numberOfRowsInSection:) :

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


, , .

, tableView (_:cellForRowAtIndexPath:) :

 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 } 


:

  1. -, UITableView UITableViewCell dequeueReusableCellWithIdentifier AnyObject ( Objective-C) UITableViewCell. , , Apple API, !
  2. , , , .
  3. , .


«». UISearchBarDelegate :

 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() } } } 


, . , :

  1. , , Flickr . Flickr . : , .
  2. , . , , , . , Swift, .
  3. , SearchResults. , . , .
  4. , , .


--! !

. :

image

, . , :

image

, . !

!

MasterViewController.swift prepareForSegue. :

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


searches , . ( ), , , , , tuple .

, , . :

image

! ?

What's next?

, ! , , , , , .

, Swift.

, , . - , , !

Source: https://habr.com/ru/post/239207/


All Articles