J'ai téléchargé la version bêta de Xcode 8.0 hier et par conséquent Swift 3. La première chose que j'ai faite a été d'essayer de mettre à jour mon projet pour Swift 3 et j'ai presque pleuré. Un des changements les plus graves est (à mon avis) la nouvelle gestion de la structure de Swifts Range
, en particulier parce que la conversion automatique vers la syntaxe actuelle Swift ne fait rien avec les plages.
Range
est divisé en Range
, CountableRange
, ClosedRange
et CountableClosedRange
ce qui ne est logique lorsque l'on considère ce est désormais possible lors de l'utilisation de plages (bien que ce soit généralement assez inutile).
Cependant: j'ai beaucoup de fonctions acceptant un Range<Int>
comme paramètre ou renvoyant un Range<Int>
. Le problème est: j'ai appelé ces fonctions par 0..<5
par exemple ou 0...4
(parce que c'est sémantiquement plus expressif parfois). Bien sûr, je pouvais simplement ajuster ce genre de choses. Mais pourquoi tous ces types de plages n'ont-ils pas une interface commune? Je devrais surcharger chaque fonction pour chacun de ces types de plage et elle effectuerait exactement les mêmes opérations à chaque fois.
Existe-t-il des meilleures pratiques pour l'utilisation des plages dans Swift 3?
C'est en fait assez simple:
UNE Closed...Range
est produit en utilisant trois points: 0...10
. Cela comprend la limite inférieure et la limite supérieure. Le contraire est une gamme non fermée, produite par 0..<10
qui n'inclut pas la limite supérieure.
UNE Countable...Range
est une plage d'un type que vous pouvez parcourir avec un entier signé, elle est produite par 0...10
ou 0..<10
. Ces types sont conformes au protocole Sequence
.
Quelques exemples:
0..<10 // CountableRange
0...Int.max // CountableClosedRange (this was not possible before Swift 3)
"A"..<"A" // Range, empty
"A"..."Z" // ClosedRange, because cannot stride through, only check if a character is in the bounds
Vous devriez probablement faire en sorte que vos méthodes acceptent un Collection
/Sequence
générique en fonction de ce dont vous avez besoin:
func test<C: Collection where C.Iterator.Element == Int>(s: C) {
print(s.first)
}
Vous pouvez peut-être montrer l'une de vos utilisations pour Range<Int>
L'opérateur de plage fermée (a...b)
Définit une plage qui va de a
à b
et inclut les valeurs a
et b
. La valeur de a
ne doit pas être supérieure à b
.
L'opérateur de plage fermée est utile lors de l'itération sur une plage dans laquelle vous souhaitez utiliser toutes les valeurs, comme avec une boucle for-in:
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
L'opérateur de plage semi-ouverte (a..<b)
Définit une plage qui va de a
à b
, mais n'inclut pas b
. Il est dit semi-ouvert car il contient sa première valeur, mais pas sa valeur finale. Comme pour l'opérateur à plage fermée, la valeur de a
ne doit pas être supérieure à b
. Si la valeur de a
est égale à b
, la plage résultante sera vide.
Les plages semi-ouvertes sont particulièrement utiles lorsque vous travaillez avec des listes à base zéro telles que des tableaux, où il est utile de compter jusqu'à (mais sans inclure) la longueur de la liste:
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
Notez que le tableau contient quatre éléments, mais 0..<count
Ne compte que jusqu'à 3
(L'index du dernier élément du tableau), car il s'agit d'une plage semi-ouverte.
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"]
Voici un exemple de SpriteKit réel que j'ai dû convertir en utilisant arc4Random qui est dans pratiquement tous les projets SpriteKit. Aléatoire traite souvent des plages.
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))
Donc, si la randomInRange()
calcule un nombre aléatoire dans la plage donnée, y compris la limite supérieure, alors il doit être défini comme ClosedRange<Bound>
Range
et ClosedRange
ne peuvent pas être itérés (ce ne sont plus des collections), car une valeur qui est simplement Comparable
ne peut pas être incrémentée. CountableRange
et CountableClosedRange
nécessitent Strideable
de leur limite et ils sont conformes à Collection
afin que vous puissiez les parcourir.