web-dev-qa-db-fra.com

Swift 3 gammes: meilleures pratiques

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?

16
borchero

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>

14
Kametrixom

Opérateur de gamme fermée

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

Opérateur semi-ouvert

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.

Plage fermée: a...b

let myRange = 1...3
let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c", "d"]

Plage semi-ouverte: 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>

Migration vers Swift

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.

15
tymac