I am curious how to do a for loop with a tuple in swift.
I know that to access each member you can use dot notation using the index number
var tuple
Swift does not currently support iterating over tuples.
The biggest reasons are:
tupleList.0
. You would really want a subscript tupleList[0]
but that is not provided to usFrankly, I can't see a reason that you would use a tuple instead of an Array if you want to iterate over it.
It doesn't make sense to iterate over a tuple because:
Arrays are well made to iterate over:
var list = ["A",2.9,3,8,5,6,7,8,9]
You can using reflection Swift 5
Try this in a Playground:
let tuple = (1, 2, "3")
let tupleMirror = Mirror(reflecting: tuple)
let tupleElements = tupleMirror.children.map({ $0.value })
tupleElements
Output:
@dankogai's excellent solution, updated for Swift 3.0:
func iterate<Tuple>(_ tuple:Tuple, body:(_ label:String?,_ value:Any)->Void) {
for child in Mirror(reflecting: tuple).children {
body(child.label, child.value)
}
}
Usage remains identical to @dankogai's examples (beyond Swift 2's println()
→print()
rename).
Note that the label is now of type String?
when it was formerly String
, to match the type change from Swift 1's MirrorType.subscript(…).0
to Swift 3's Mirror.Child.label
. However, for labelless tuples the label
arg comes back as ".0"
, ".1"
, ".2"
, etc.— it's only nil
for some other types.
Also, I took the liberty of renaming types & args to better match Swift 3's solidified naming standards, and changing the closure return type to Void
.
Sidenote: I noticed somebody downvoted me here— I can't imagine why, other than the (fair) argument that building app functionality around reflection in Swift is hacking the type system, and is likely to lead to crappy code throughout (Swift's tuples shouldn't be considered an abstract data type, but rather a small collection of variables, akin to method args). As a counter-argument, I originally ended up porting this to Swift 3 in project because I needed it— for better description
s and debugDescription
s. Because sane debug output will saves you hours and hours of frustration. ;-) Additionally, this could be really useful for unit tests… because tests are ultimately most interested in “did the result of this operation match what we expect?”
Yes, you can!
func iterate<C,R>(t:C, block:(String,Any)->R) {
let mirror = reflect(t)
for i in 0..<mirror.count {
block(mirror[i].0, mirror[i].1.value)
}
}
And voila!
let tuple = ((false, true), 42, 42.195, "42.195km")
iterate(tuple) { println("\($0) => \($1)") }
iterate(tuple.0){ println("\($0) => \($1)")}
iterate(tuple.0.0) { println("\($0) => \($1)")} // no-op
Note the last one is not a tuple so nothing happens (though it is a 1-tuple or "Single" which content can be accessed .0
, reflect(it).count
is 0).
What's interesting is that iterate()
can iterate even other types of collection.
iterate([0,1]) { println("\($0) => \($1)") }
iterate(["zero":0,"one":1]) { println("\($0) => \($1)") }
And that collection includes class
and struct
!
struct Point { var x = 0.0, y = 0.0 }
class Rect { var tl = Point(), br = Point() }
iterate(Point()) { println("\($0) => \($1)") }
iterate(Rect()) { println("\($0) => \($1)") }
Caveat: the value passed as the 2nd argument of the block is type Any
. You have to cast it back to the values with original type.
No, you can't. The reason is that tuple items are not all required to have the same type, so you would not be able to know what type each
should have.
struct Tuple<T> {
let original: T
private let array: [Mirror.Child]
init(_ value: T) {
self.original = value
array = Array(Mirror(reflecting: original).children)
}
func forEach(closure: (Mirror.Child) -> Void) { array.forEach { closure($0) } }
func getOnlyValues<T: Any>() -> [T] { array.compactMap { $0.value as? T } }
func getAllValues() -> [Any] { array.compactMap { $0.value } }
}
let num: Int? = 3
let str: String? = nil
let x = (1, "stew", str, 5.4, 2, num)
let tuple = Tuple(x)
tuple.forEach { print("\($0)") }
print("\(tuple.getAllValues())") // [1, "stew", nil, 5.4, 2, Optional(3)]
print("\(tuple.getOnlyValues() as [Int])") // [1, 2, 3]
func valuesFrom<V>(tuple: V) -> [Any] { return Tuple(tuple).getAllValues() }
func onlyValuesFrom<T,V>(tuple: V) -> [T] { return Tuple(tuple).getOnlyValues() as [T] }
print(valuesFrom(tuple: x)) // [1, "stew", nil, 5.4, 2, Optional(3)]
print(onlyValuesFrom(tuple: x) as [Int]) // [1, 2, 3]