I downloaded the Xcode 8.0 beta yesterday and consequently Swift 3. The first thing I did was trying to update my project for Swift 3 and I nearly cried. One of the gravest changes is (in my opinion) the new management of Swifts Range
struct, especially because the automatic conversion to the current Swift syntax does not do anything with the ranges.
Range
is split into Range
, CountableRange
, ClosedRange
and CountableClosedRange
which does make sense when considering what is now possible when using ranges (though it's mostly fairly unnecessary).
However: I have lots of functions accepting a Range<Int>
as parameter or returning a Range<Int>
. The problem is: I called these functions by 0..<5
for example or 0...4
(because it's semantically more expressive sometimes). Of course, I could simply adjust these type of things. But why don't all these range types have a common interface? I'd have to overload every single function for each of these range types and it would perform the exact same operations every time.
Are there any best practices yet for using ranges in Swift 3?
The closed range operator (a...b)
defines a range that runs from a
to b
, and includes the values a
and b
. The value of a
must not be greater than b
.
The closed range operator is useful when iterating over a range in which you want all of the values to be used, such as with a for-in loop:
for index in 1...5 {
print("\(index) times 5 is \(index * 5)")
}
// 1 times 5 is 5
// 2 times 5 is 10
// 3 times 5 is 15
// 4 times 5 is 20
// 5 times 5 is 25
The half-open range operator (a..<b)
defines a range that runs from a
to b
, but does not include b
. It is said to be half-open because it contains its first value, but not its final value. As with the closed range operator, the value of a
must not be greater than b
. If the value of a
is equal to b
, then the resulting range will be empty.
Half-open ranges are particularly useful when you work with zero-based lists such as arrays, where it is useful to count up to (but not including) the length of the list:
let names = ["Anna", "Alex", "Brian", "Jack"]
let count = names.count
for i in 0..<count {
print("Person \(i + 1) is called \(names[i])")
}
// Person 1 is called Anna
// Person 2 is called Alex
// Person 3 is called Brian
// Person 4 is called Jack
Note that the array contains four items, but 0..<count
only counts as far as 3
(the index of the last item in the array), because it is a half-open range.
a...b
let myRange = 1...3
let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c", "d"]
a..<b
let myRange = 1..<3
let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c"]
Here's a real-world SpriteKit example I had to convert using arc4Random which is in virtually all SpriteKit projects. Random often deals with ranges.
Swift 2
Tools.swift
func randomInRange(_ range: Range<Int>) -> Int {
let count = UInt32(range.upperBound - range.lowerBound)
return Int(arc4random_uniform(count)) + range.lowerBound
}
GameScene.swift
let gap = CGFloat(randomInRange(StackGapMinWidth...maxGap))
Swift 3
Tools.swift
func randomInRange(range: ClosedRange<Int>) -> Int {
let count = UInt32(range.upperBound - range.lowerBound)
return Int(arc4random_uniform(count)) + range.lowerBound
}
GameScene.swift
let gap = CGFloat(randomInRange(range: StackGapMinWidth...maxGap))
So if the randomInRange()
calculates a random number in the given range, including the upper bound, then it should be defined as ClosedRange<Bound>
Migrating to Swift 3
Range
and ClosedRange
can’t be iterated over (they are not collections anymore), since a value that is merely Comparable
cannot be incremented.
CountableRange
and CountableClosedRange
require Strideable
from their bound and they conform to Collection
so that you can iterate over them.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With