Advanced usage and best practices of Swift tuples
As a rare syntax feature in Swift, tuples only occupy a very small position between struct and array. In addition, it does not have a structure in Objective-C (or many other languages. Finally, the standard library and Apple Sample Code rarely use tuples. Maybe it gives the impression in Swift that it is used for pattern matching, but I don't think so.
Most meta-group-related tutorials only focus on three scenarios (pattern matching, return value, and deconstruct. This article will introduce tuples in detail, and explain the best practices for using tuples, to tell you when to use and when to use tuples. At the same time, I will also list things that you cannot do with tuples, so that you will not always ask questions in StackOverflow. Now, go to the topic.
Absolute Foundation
You may already know this part, so I will give a brief introduction.
Tuples allow you to combine different types of data. It is variable. Although it looks like a sequence, it is not because it cannot directly traverse all content. First, we will use a simple getting started example to learn how to create and use tuples.
Create and access tuples
// Create a simple tuples let tp1 = (2, 3) let tp2 = (2, 3, 4) // create a name tuple let tp3 = (x: 5, y: 3) // let tp4 = (name: Carl, age: 78, pets: [Bonny, Houdon, Miki]) of different types // access the element of the tuples let tp5 = (13, 21) tp5.0 // 13tp5. 1 // 21let tp6 = (x: 21, y: 33) tp6.x // 21tp6. y // 33
Use tuples for Pattern Matching
As mentioned earlier, this is probably the most common use case for tuples. Swiftswitch
Statements provide an extremely powerful method to define complex conditional statements without messing up the source code. In this way, you can match the type, instance, and values of multiple variables in a statement:
// Specially crafted example // These are the return values of multiple methods let age = 23let job: String? = Operatorlet payload: AnyObject = NSDictionary ()
In the above Code, we want to find a person under the age of 30 and a dictionarypayload
. Assume thatpayload
It is something in the Objective-C world. It may be a dictionary, array, or number. Now you have to deal with the following code that someone else wrote many years ago:
switch (age, job, payload) { case (let age, _?, _ as NSDictionary) where age < 30: print(age) default: ()}
Setswitch
Is constructed as a tuples.(age, job, payload)
We can use well-designed constraints to access all specific or unspecified properties in the tuples at a time.
Use tuples as the return type
This may be an application scenario where there are more than two tuples. Because tuples can be built in real time, it is a simple and effective way to return multiple values in a method.
func abc() -> (Int, Int, String) { return (3, 5, Carl)}
Tuples deconstruct
Swift draws a lot of inspiration from different programming languages, which has been a task of Python for many years. Most of the previous examples only show how to insert things into tuples. Deconstruct is a way to quickly extract things from the tuples.abc
Example:
let (a, b, c) = abc()print(a)
Another example is to write multiple method calls in one line of code:
let (a, b, c) = (a(), b(), c())
Or, simply swap two values:
var a = 5var b = 4(b, a) = (a, b)
As an anonymous struct
Like a struct, tuples allow you to combine different types into one type:
struct User { let name: String let age: Int}// vs.let user = (name: Carl, age: 40)
As you can see, these two types are very similar, but the struct is declared through the struct description. After the declaration, the struct can be used to define the instance, and the tuples are just an instance. If you need to define a temporary struct in a method or function, you can use this similarity. As stated in the Swift document:
"Tuples are useful when you need to temporarily combine related values. (...) If the data structure still exists outside the Temporary range. Then abstract it into a class or struct (...)"
The following example shows how to collect the return values of multiple methods, deduplicate them, and insert them into the dataset:
Func zipForUser (userid: String)-> String {return 12124} func streetForUser (userid: String) -> String {return Charles Street} // find all non-repeated streets in the dataset var streets: [String: (zip: String, street: String, count: Int)] = [:] for userid in users {let zip = zipForUser (userid) let street = streetForUser (userid) let key = (zip)-(street) if let (_,_, count) = streets [key] {streets [key] = (zip, street, count + 1)} else {streets [key] = (zip, street, 1 )}} drawStreetsOnMap (streets. values)
Here, we use simple tuples in a short temporary scenario. The struct can also be defined, but this is not necessary.
Let's look at another example: in the class that processes algorithm data, you need to pass the temporary results returned by a method into another method. Defining a struct that only contains two or three methods is obviously unnecessary.
// Compile the algorithm func calculateInterim (values: [Int])-> (r: Int, alpha: CGFloat, chi: (CGFloat, CGFLoat )){...} func expandInterim (interim: (r: Int, alpha: CGFloat, chi: (CGFloat, CGFLoat)-> CGFloat {...}
Obviously, this line of code is very elegant. Defining a struct for an instance is sometimes too complex, and defining the same tuple for four times is also not desirable without using the struct. So the method you choose depends on a variety of factors.
Private status
In addition to the previous example, tuples also have a very practical scenario: they are used outside the Temporary range. Rich Hickey said: "If a tree falls down in the woods, will it sound? "Because the scope is private, tuples are only valid in the current implementation method. Using tuples can store internal statuses.
Let's look at a simple example: Save a staticUITableView
Structure. This structure is used to display various information in the user profile and the corresponding information values.keypath
And also useeditable
Icon indicates clickCell
Whether these values can be edited.
let tableViewValues = [(title: Age, value: user.age, editable: true),(title: Name, value: user.name.combinedName, editable: true),(title: Username, value: user.name.username, editable: false),(title: ProfilePicture, value: user.pictures.thumbnail, editable: false)]
Another option is to define the struct, but if the implementation details of the data are purely private, it is enough to use tuples.
A cool example is: You define an object and want to add multiple change listeners to this object. each listener contains its name and the closure called when it changes:
func addListener(name: String, action: (change: AnyObject?) -> ())func removeListener(name: String)
How do you save these listeners in an object? The obvious solution is to define a struct, but these listeners can only be used in three cases, that is, they have extremely limited scope of use, and struct can only be definedinternal
Therefore, using tuples may be a better solution, because its deconstruct capability makes things very simple:
var listeners: [(String, (AnyObject?) -> ())]func addListener(name: String, action: (change: AnyObject?) -> ()) { self.listeners.append((name, action))}func removeListener(name: String) { if let idx = listeners.indexOf({ e in return e.0 == name }) { listeners.removeAtIndex(idx) }}func execute(change: Int) { for (_, listener) in listeners { listener(change) }}
Just as you areexecute
As seen in the method, the deconstruct capability of tuples makes it particularly useful in this case, because the content is directly deconstruct in a local scope.
Use tuples as sequences of fixed sizes
Another application field of tuples is to fix the number of elements contained in a type. Assume that you need to use an object to calculate various statistical values of all months in a year, you need to separately store a definiteInteger
Value. The first possible solution is as follows:
var monthValues: [Int]
However, we cannot determine that this attribute contains exactly 12 elements. Users using this object may accidentally insert 13 values or 11 values. We cannot tell the type Checker that the object is an array of 12 elements (interestingly, this is supported by C ). However, if you use tuples, you can easily implement these special constraints:
var monthValues: (Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int)
Another option is to add the constraint logic to the object function (that is, through the newguard
Statement), but this is checked at runtime. The check of tuples is during the compilation period. If you want to assign an object an 11-month value, the compilation will fail.
Tuples as complex variable parameter types
Variable parameters (such as variable function parameters) are very useful when the number of function parameters is variable.
// Traditional example func sumOf (numbers: Int ...) -> Int {// use the + operator to add all numbers to return numbers. reduce (0, combine: +)} sumOf (1, 2, 5, 7, 9) // 24
If your demand is not justinteger
. The following function is used to update databases in batches.n
Entity (s:
Func batchUpdate (updates: (String, Int )...) -> Bool {self. db. begin () for (key, value) in updates {self. db. set (key, value)} self. db. end ()} // our hypothetical database is a very complex batchUpdate (tk1, 5), (tk7, 9), (tk21, 44), (tk88, 12 ))
Advanced usage, tuples, and iterations
In the previous content, I tried to avoid calling tuples as sequences or sets because they are indeed not. Because each element in a tuples can be of different types, you cannot use a type-safe method to traverse or map the contents of the tuples. Or at least there is no elegant way.
Swift provides limited reflection capabilities, allowing us to check the content of the tuples and traverse them. The bad thing is that the type checker does not know how to determine the type of the traversal element, so the type of all content isAny
. You need to convert and match the types that may be useful and decide what to do to them.
let t = (a: 5, b: String, c: NSDate())let mirror = Mirror(reflecting: t)for (label, value) in mirror.children { switch value { case is Int: print(int) case is String: print(string) case is NSDate: print(nsdate) default: () }}
This is certainly not as simple as array iteration, but you can use this code if necessary.
Tuples and generics
Swift does notTuple
This type. If you don't know why, you can think like this: each tuple is of a completely different type, and its type depends on the type of the element it contains.
Therefore, it is better to define a tuples that contain specific data types as needed than to define a tuples that support generics.
func wantsTuple
(tuple: (T1, T2)) -> T1 { return tuple.0}wantsTuple((a, b)) // awantsTuple((1, 2)) // 1
You can alsotypealiases
Use tuples to allow child classes to specify specific types. This seems complicated and useless, but I have already met the application scenarios that need to be specifically implemented.
class BaseClass { typealias Element = (A, B) func addElement(elm: Element) { print(elm) }}class IntegerClass : BaseClass {}let example = IntegerClass ()example.addElement((5, ))// Prints (5, )
Define specific tuples
In the previous several examples, we repeat some determined types multiple times, such(Int, Int, String)
. This certainly does not need to be written every time. You can definetypealias
:
typealias Example = (Int, Int, String)func add(elm: Example) {}
However, if you need to use a fixed tuples so frequently, you want to addtypealias
The best way is to define a struct.
Using tuples as function parameters
As Paul Robinson mentioned in his article,(a: Int, b: Int, c: String) ->
And(a: Int, b: Int, c:String)
There is a wonderful similarity between them. Indeed, for the Swift compiler, the method/function parameter header is nothing more than a tuples:
// From Paul Robinson's blog, you should also read this article: // http://www.paulrobinson.net/function-parameters-are-tuples-in-swift/func foo (a: Int, _ B: Int, _ name: String) -> Int return a} let arguments = (4, 3, hello) foo (arguments) // return 4
It looks cool, isn't it? But wait... The function signature here is a bit special. What happens when we add or remove tags like tuples? Now, let's start the experiment:
// Let's try the labeled: func foo2 (a: Int, B: Int, name: String)-> Int {return a} let arguments = (4, 3, hello) foo2 (arguments) // you cannot use let arguments2 = (a: 4, B: 3, name: hello) foo2 (arguments2) // you can use (4)
Therefore, if the function signature contains tags, you can support the tuples with tags.
But do we need to explicitly write the tuples into the variables?
Foo2 (a: 4, B: 3, name: hello) // Error
Okay, it's unlucky. The above code won't work, but what if it is a tuple returned by calling a function?
Func foo (a: Int, _ B: Int, _ name: String)-> Int return a} func get_tuple ()-> (Int, Int, String) {return (4, 4, hello)} foo (get_tuple () // available! 4!
Great! This method works!
This method contains many interesting meanings and possibilities. If you plan the types well, you don't even need to deconstruct the data, and then directly pass them as parameters between functions.
Even better, for functional programming, you can directly return a tuples containing multiple parameters to a function without deconstruct.
Tuples cannot be used ~
Finally, we will present some tuples that cannot be implemented in a list.
Dictionary using tuplesKey
If you want to do the following:
let p: [(Int, Int): String]
That is impossible, because the tuples do not conform to the hash protocol. This is really a sad thing, because this writing method has many application scenarios. There may be crazy type checkers that hackers expand tuples to make them conform to the hash protocol, but I have not really studied this, so if you just find this is available, please feel free to contact me via twitter.
Protocol Compliance of tuples
The following protocol is given:
protocol PointProtocol { var x: Int { get } var y: Int { set }}
You cannot tell the type checker this(x: 10, y: 20)
The tuples comply with this Protocol.
Func addPoint (point: PointProtocol) addPoint (x: 10, y: 20) // unavailable.
Appendix
That's it. If I forget to say something or do something wrong, if you find an exact error or something else I forgot, please feel free to contact me
Update
07/23/2015AddUsing tuples as function parametersChapter
08/06/2015Update the reflection example to the latest Swift beta 4 (removedreflect
)
08/12/2015UpdateUsing tuples as function parametersChapter, add more examples and information
08/13/2015Fixed some bugs...