If you have written Swift's program, you have learned about the swift language, such as how to write classes (class) and structure (struct). But Swift is not so simple, oh oh. This tutorial focuses on a powerful feature of Swift: Generics. This feature is very popular in many programming languages.
A common problem with type safety (Type-safe) languages is how to write programs that apply to multiple types of input. Imagine that a program that adds two integers and two floating-point numbers should look very similar or even identical. The only difference is that the variables are of different types.
In strongly typed languages, you need to define methods such as Addints, Addfloats, and adddoubles to handle parameters and return values correctly.
Many programming languages have solved this problem. For example, in C + +, use template to resolve. and Swift,java and C # Use generics to solve this problem. Generics, this article is to focus on the introduction.
In this article, you will learn how to use generics in Swift, perhaps you have contacted, maybe not, but that's OK, we'll come to one by one to explore. We then create a flicker image search application that uses a custom generic data structure to hold the content of the user's search.
Note: This article assumes that you have a basic understanding of swift or experienced in swift development. If you are in contact with Swift for the first time or do not know much about Swift, it is recommended that you first read the other swift tutorials.
Generics Introduction
Maybe you don't know the term, but believe you've seen it in Swift. The array and dictionary types in Swift are classic examples of using generics.
Object-c developers have become accustomed to using arrays and dictionaries to save multiple data types. This provides a lot of flexibility, but who knows what exactly is in an array returned by an API (data type)? The only thing you can do is view the document or view the variable command (which is another document!). )。 Even if you look at the document, you cannot guarantee that the program will not generate bugs or other exceptions during runtime.
The arrays and dictionaries in Object-c,swift are type-safe compared to each other. An int array can save only an int, but not a string. This means you don't have to look at the document again, the compiler can help you do type checking, and then you'll be happy to coding!
For example, in Object-c's uikit, handling a touch event in a custom view can be written like this:
Copy Code code as follows:
-(void) Touchesbegan: (Nsset *) touches withevent: (Uievent *) event;
The set in the above method can only save Uitouch instances, because that's what the document says. Because you can put any object in the collection, you need to convert the type within the code, which means to turn the objects inside the touches into Uitouch objects.
There is no collection object defined in the current Swift's standard library, but you can use an array instead of a collection object, and you can rewrite the above code with Swift:
Copy Code code as follows:
Func Touchesbegan (touches: [uitouch]!, Withevent event:uievent!)
The code above explicitly tells you that the touches array can contain only Uitouch instances, otherwise the compiler will report an exception. In this way, you don't have to do that annoying type conversion, because the compiler gives you a type of security check to ensure that only Uitouch objects are allowed in the array.
Briefly, generics provide a type parameter for a class. All arrays have the same effect, that is, to store the values of the variables sequentially, and there is no difference in the generic array except for one more type parameter. It may be easier to understand that the various algorithms you will apply to arrays are independent of the stored numeric types, so they apply to both generic and non-generic arrays.
Now that you understand the basics and usage of generics, let's start applying it to a concrete example.
Generic Instance
To test generics, you will write an application that searches for images on Flickr.
First please download the prototype of this program, and as soon as possible familiar with the main class. The Flickr class is used to interact with the Flickr API. Please note that this class contains an API key (usually for user authorization-translator), but if you want to extend this application you may need to register with your key, please click me.
To construct and run this application, you will see this:
Like there's nothing? Don't worry, it won't be long before you can get it to help you crawl the cute meow!
Ordered dictionary (original ordered dictionaries)
Your application will be based on each user's query to download the picture, according to the frequency of the image is searched from high to low to sort and display.
But what if the user searches for the same keyword twice? It would be nice if the application could show the results of the last search.
It might work with arrays, but in order to learn generics you would use an entirely new data structure: an ordered dictionary.
and arrays differ, including Swift's on the mainland many programming languages and frameworks do not guarantee the order of data storage for collections (sets) and dictionaries (dictionaries). An ordered dictionary is similar to a common dictionary, except that its key is ordered. You will use this feature to store search results sequentially according to search keywords. The benefit of this storage is that you can quickly query and update the list of pictures.
A hasty idea is to customize a data structure to handle an ordered dictionary. But you need to be more forward-looking! You have to think about how to make your application work in the next few years! So it would be appropriate to use generics here.
Initial data Structure
Click "File \ new \ File ..." To create a new file and select "Ios\source\swift file". Click "Next" and name the file "OrderedDictionary". Finally, click "Create".
You will get an empty swift file and add such a piece of code into it:
Copy Code code as follows:
struct OrderedDictionary {}
There should be no problem so far. It is possible to see the object as a struct by semantics.
Note: In short, the semantics of values can be imagined as "copying, pasting behavior," rather than "sharing, reference behavior." The semantics of values bring a series of benefits, such as not having to worry about a piece of code unintentionally modifying your data. For more information, click on the third chapter of "Swift by Tutorials": Classes and structs.
Now you need to generalize it so that it can load any type of data you need. Change your definition of "structure" in Swift by the following:
Copy Code code as follows:
struct Ordereddictionary<keytype, valuetype>
Elements in the angle bracket are parameters of a generic type. KeyType and ValueType are not their own types, but you can use the types that are substituted in the structure. It's a lot simpler and fresher now!
The simplest implementation of a sequential dictionary is to keep an array and a dictionary. The dictionary will load diffraction, and the array will load the order of the keys.
In the definition within the structure, add the following code:
Copy Code code as follows:
Typealias arraytype = [Keytype]typealias dictionarytype = [Keytype:valuetype] var array = arraytype () var dictionary = Dic Tionarytype ()
This declaration has two purposes, as described in the previous example, and there are two types of aliases used to give new names to existing types. Here, you will assign aliases to the following arrays and dictionaries separately. Declaring an alias is a very efficient way to define a complex type as a type with a shorter name.
You will notice how to replace the type in the struct body with the parameter type "KeyType" and "ValueType". The "Keytypes" in the example above is an array type. Of course there is no such type of "KeyType", and when in general instantiation, it replaces swift like all types of ordereddictionary.
Because of this, you will notice compilation errors:
Copy Code code as follows:
Type ' Keytype ' does not conform to protocol ' hashable '
Maybe you'll be amazed at how it happened. Please look again at the successor of the Dictionary:
Copy Code code as follows:
struct dictionary<keytype:hashable, valuetype>
In addition to the Hashtable after the KeyType, the rest are very similar to the ordereddictionary definition. The hashable that follows the semicolon for the KeyType declaration must conform to the Hashable protocol. This is because the dictionary needs to be implemented for the hash key.
It is very common to constrain generic parameters in this way. For example, you want to use parameters based on your application to constrain the type of value to ensure equality, printable protocol.
Open the Ordereddictionary.swift and use the following example to replace your definition of the structure:
Copy Code code as follows:
struct ordereddictionary<keytype:hashable, valuetype>
This keytype for OrderedDictionary declaration must conform to hashable. This means that no matter what type of keytype becomes, it can be accepted as the key of a dictionary that is not declared.
This way, the file compiles again, will not error!
Keys, Values and all of these anecdotes
If you can't add a value to a dictionary, what's the effect of the dictionary? Open Ordereddictionary.swift and add the following function to your structure definition:
Copy Code code as follows:
1mutating 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}
Some of the new features are described below. Let's take a step-by-step introduction:
- Insert a new object method, insert (_:forkey:atindex), requires three parameters: a special key value, insert a pair of Key-value index. This is one of the key words you didn't notice before: change.
- The design of the structure is the default invariant, which means that you usually cannot change the member variables of the struct in the instantiated method. This is very limited and you can add changed keywords and tell the compiler that this way is allowed to change in the structure. This will help the compiler make decisions about when to copy the structure (they are copied at write time) and also facilitate the documentation of the API.
- You enter a key for the dictionary's indexer that returns an existing value if it already exists, and the insertion method simulates the behavior of the dictionary updating the same value, thus maintaining the existing value for that value.
- If this has a value that already exists, only such a function can find the index in the array for that value.
- If the already existing key is inserted before the index, then you need to adjust the index of the insertion because you need to remove the existing key.
- You will update the array and the dictionary appropriately.
- Finally, you return a value that already exists, and when this may not have an existing value, the function returns an optional value!
Now you can add a removal value to the dictionary?
Functions like the following for the definition of a ordereddictionary structure body:
Copy Code code as follows:
1mutating 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)}
Now let's take a step-by-step analysis:
1. This is the function that changes the state of the structure, and the name of the Removeatindex needs to match the methods of the array. It is a good choice to consider using the API in the mirrored system library when appropriate. This helps developers to use your APIs very easily in their work platform.
2. First, you need to check the index to see if they are in a large number of arrays. Attempting to remove an offside element from an array that has never been declared will result in a timeout error, all of which are checked earlier. You may use Assert functions in objective-c, and assertions in Swift are also available. But the premise is that in the release of the project is active, otherwise you run the application will be terminated.
3. Then, when you remove the value from the array at the same time, you get the value in the array given the index.
4. Then, you remove the value from the dictionary for this key, and it also returns this value. Perhaps in the given key, the dictionary also has no corresponding value, so Removevalueforkey returns an optional. In this case, you know that the dictionary will contain a value for the given key, because this is the only way to add a value to the dictionary--insert (_:forkey:atindex:), at which point you can choose to use "! "To show that there will be a sense of justice.
5. Finally, you return the key and value in a tuple. The removeatindex of the array is the same as the removevalueforkey of the dictionary. Returns the existing value function.
Reading and writing of values
Writing the value to the Dictionary (dictionary) is no problem, but it's not enough! You also need to implement a method to read the corresponding value from the dictionary.
Open the Ordereddictionary.swift file and add the following code to the structure definition (struct definition), placed under the Thearrayanddictionaryvariable declaration:
Copy Code code as follows:
var Count:int {
Return Self.array.count}
This commonly used attribute is used to calculate a few records in a dictionary. Just return the median value of the Count property of the array!
Next, it's how to access the records (Element) in the dictionary (Access). We can access it by subscript (subscript), and the code is as follows:
Copy Code code as follows:
Let dictionary = [1: "One", 2: "Two"]let one = dictionary[1]//subscript
We'll use the subscript syntax, but how do we use it if it's our own defined class? Fortunately, Swift supports adding this functionality to the custom class. And the implementation is not complicated.
Add the following code to the bottom of the structure definition:
Copy Code code as follows:
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
}
}
Let's explain this code in detail:
- The code note above has 1 of the winning section: like Func and Var, subscript is also a keyword that defines the subscript. The parameter key is the object that appears in the brackets.
- Note 2 of the winning paragraph: subscript by the setter and getter two parts. This example also defines the setter (set in code) with the getter (get in code). Of course, not every subscript has to define both the setter and the getter.
- Note the winning 3 of the paragraph: Getter relatively simple, as long as the key through the key, in the dictionary to find the corresponding value can be. The dictionary returns an optional value (optinal), and if the key does not exist, the value is nil.
- Note The winning section is 4: The setter is more complex. The first thing to detect is whether this key exists in an ordered dictionary. If it does not exist, the key is added to the array. Because we need to add the key to the end of the array, we call the Append method here.
- Note 5 of the winning section: Add the value to the dictionary. This is used to get the value passed by the implicitly named variable NewValue.
As with the subscript of the dictionary class with Swift, you can look up a value by key. But what if we need to access a value with the subscript index, like an array? Since it is an ordered dictionary, there is no reason why it cannot be accessed sequentially through the subscript index one by one.
A struct can define a subscript (subscript) with a different parameter type than a class. Add the following code to the bottom of the structure definition:
Copy Code code as follows:
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 code is similar to the previous paragraph, but the parameter type is changed to Int. Because now we want to actually function as an array, we use the subscript index to access the ordered dictionary. This time, however, a tuple (tuple) consisting of key and value is returned. Because an ordered dictionary is composed of such a tuple.
The following is a detailed explanation of this code:
- This subscript defines only the getter. Of course, you can also add the setter. But be careful to check whether the index crosses the line first.
- The value of index cannot exceed the bounds of the array, which is the number of dictionary tuples. We can use the precondition to prompt the developer, the program appears the Cross-border access.
- Reads the key from the array with index.
- Then use key to read value from the dictionary. It should be noted that since each key in the array corresponds to the value of the dictionary, the symbol is used here! (unwrapped) to the read value of the unpacking.
- Finally, a tuple containing key and value is returned.
Challenge: Implement the setter for the subscript above. You can refer to the previous example.
Tip 1
Note that NewValue is a tuple that contains key and value.
Tip 2
The following code can extract a value from a tuple:
Copy Code code as follows:
Let (key, value) = NewValue
Perhaps you would wonder what would happen if the KeyType was Int type? The advantage of using generics is that, regardless of the type, as long as the hash value (hashable) can be calculated, the Int of course can also be used. The question is, when key is also Int, how do the two subscripts differentiate?
This requires us to provide more type information to the compiler. Let it know which subscript to call at what time. For example, we defined the two subscript, the return type is not the same. If you assign a value to it using the Key-value type tuple, the compiler automatically invokes the subscript for that array (Array-style).
Testing in a project
Let us in the actual project, the experiment compiles the inferred use of the subscript function, and in general, how OrderedDictionary works.
By clicking on "File", "Create", "file", create a new item, select "IOS", "Source", "playground" in turn, and then click Next. then click Create.
You have to do this: copy and paste the Ordereddictionary.swift whole into a new project. Because you can't "see" code in your application model when you're writing a tutorial
Note: There is a workaround that can replace the way you copy and paste. If you need to add the code you apply to a frame, your project will accept your code, as Corrine Krych points out.
Now, add the following code at the bottom of your project:
Copy Code code as follows:
var dict = Ordereddictionary<int, string> () Dict.insert ("Dog", Forkey:1, atindex:0) Dict.insert ("Cat", Forkey:2, a tindex:1) println (dict.array.description
+ " : "
+ dict.dictionary.description) var Byindex: (Int, String) = Dict[0]println (Byindex) var bykey:string? = Dict[2]println (Bykey)
In the sidebar (or through the View/Assistant Edit/Display Assistant edit/), you will see the contents of the println () function output:
In this example, the dictionary has an integer key, so the compiler examines the type of subscript variable that is used by the assignment to decide which to use. If the subscript being used is an (Int, String) Byindex, the compiler matches the expected return value type, using the subscript for the index of the array type.
If you want to remove the definition of a type from a byindex or Bykey variable. The compiler will make an error indicating that the compiler does not know which subscript to use.
Tip: Because compilers work according to type inference, you need to explicitly indicate the type. When there are multiple functions with the same disputed return value type, the caller needs to materialize. Note: Functions in Swift can be "built-broken" to change.
In the project, you can find out how he works by experimenting with sequential dictionaries. Before you return to the app, try adding, removing, and changing the key and value types. Now, you can read and write in your order dictionary! But be careful with your data structure. Now you can use the app to feel the fun!
Add a picture Lookup
It's time for you to look back at the app in hand. Open Masterviewcontroller.swift. Under two @iboutlets, add the following variable definition:
Copy Code code as follows:
var searches = ordereddictionary<string, [flickr.photo]> ()
You may be wondering why there is a period in the type of Flickr.photo. That's because photo is a class defined within the Flickr class. In swift, such hierarchies are very advantageous. It will help to shorten the name of the class. Inside Flickr, you can use the photo class alone, because the context tells the compiler what this is. This is a sequential dictionary that queries user-subscribed Flickr features. As you can see, the string that contains the query, the Flickr.photo array, or the photo returned from the Flickr API. Note that the key and value that you give in the angle brackets will be the KeyType and valuetype parameter types in the implementation.
Next, locate the TableView (_:numberofrowsinsection:) method for the table view data source, and then change it to the following:
Copy Code code as follows:
Func TableView (Tableview:uitableview,
Numberofrowsinsection section:int)-> int{
Return Self.searches.count}
This method uses an ordered dictionary to tell how many rows the table view has. Next, locate the TableView (_:cellforrowatindexpath:) method of the table view data source and change it to the following:
Copy Code code as follows:
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}
This is what you do in this way:
1. First, move a cell from the UITableView. You need to convert it directly to UITableViewCell because Dequeuereusablecellwithidentifier still returns Anyobject (ID in objective-c) instead of UITableViewCell. Perhaps in the future, Apple will use generics to rewrite this part of the API.
2. Then, use the subscript index you gave to get the key and value from the specified line,
3. Finally, set the cell text label appropriately and return the current cell.
Now let's have a taste of fresh. Find Uisearchbardelegate extensions, just like the following code, to change a single instance method.
Copy Code code as follows:
Func searchbarsearchbuttonclicked (searchbar:uisearchbar!) {
1
Searchbar.resignfirstresponder ()//2
Let searchterm = Searchbar.text
Flickr.search (searchterm) {
Switch ($) {
Case. Error:
3
Break case. Results (Let Results):
4
Self.searches.insert (Results,
Forkey:searchterm,
atindex:0)//5
Self.tableView.reloadData ()
}
}}
This method will be invoked when the user clicks the query button. The following is what you are doing in this method:
1. Your first reaction is to give up using the query box and the keyboard.
2. You will then use the search box to query the text you have entered, and use the Flickr class to search for the text of the query. Flickr's Query method is: Query terminology, close the execution of the query succeeded or failed. Close by argument: if it's not a mistake, it's the result.
3. In the wrong circumstances, nothing will happen. But you can use an alert to prompt for an error, but now we can simplify the operation. The code needs to pause for a moment and tell the Swfit compiler that your error does not have any incentive to react.
4. If the query is valid, the related values will be displayed in the query results. You add the terms of the query as key to the Order dictionary. If it already exists in the dictionary, it will be placed at the top of the list, and then the entire dictionary is updated with the final result.
5. Eventually, as you have new data, the table view will be loaded again.
Wow! Your app will be available for querying pictures!
Build and run the app and do a few queries. You will see something like the following:
Now use another query that is different from the previous search term. You will see it jump to the top:
Select a query result to click on and you will find that it does not show photos. Now it's time to fix the problem!
Show me the pictures!
Open Masterviewcontroller.swift and find Prepareforsegue. Modify it to do the following:
Copy Code code as follows:
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}
}}
This is the same way as accessing a sorted query result dictionary When you create a cell item. Although no keywords are used (search terms), you can also use underscores to show that this part of the tuple does not need to be bound to a local variable.
Build and run the app, do a query and then click in. You'll see something like the following:
Hello, kitty! Do you ever want to make a happy call?:]