J'ai écrit un simple cours sur les sacs. Un sac est rempli avec un rapport fixe d'ensembles de température. Il vous permet d’en prendre un au hasard et de se remplir automatiquement s’il est vide. Cela ressemble à ceci:
class Bag {
var items = Temperature[]()
init () {
refill()
}
func grab()-> Temperature {
if items.isEmpty {
refill()
}
var i = Int(arc4random()) % items.count
return items.removeAtIndex(i)
}
func refill() {
items.append(.Normal)
items.append(.Hot)
items.append(.Hot)
items.append(.Cold)
items.append(.Cold)
}
}
L'énumération de température ressemble à ceci:
enum Temperature: Int {
case Normal, Hot, Cold
}
Mon GameScene:SKScene
a une propriété d'instance constante bag:Bag
. (J'ai aussi essayé avec une variable.) Quand j'ai besoin d'une nouvelle température, j'appelle bag.grab()
, une fois dans didMoveToView
et lorsque cela est approprié dans touchesEnded
.
Au hasard, cet appel se bloque sur la ligne if items.isEmpty
dans Bag.grab()
. L'erreur est EXC_BAD_INSTRUCTION
. La vérification du débogueur montre que les éléments sont size=1
et [0] = (AppName.Temperature) <invalid> (0x10)
.
Edit On dirait que je ne comprends pas les informations du débogueur. Même les tableaux valides affichent size=1
et des valeurs non liées pour [0] =
. Donc, aucune aide là-bas.
Je n'arrive pas à le faire planter isolé dans un terrain de jeu. C'est probablement quelque chose d'évident mais je suis perplexe.
La fonction arc4random
renvoie un UInt32
. Si vous obtenez une valeur supérieure à Int.max
, la distribution Int(...)
se bloquera.
En utilisant
Int(arc4random_uniform(UInt32(items.count)))
devrait être une meilleure solution.
(Blame les messages de crash étranges dans la version Alpha ...)
J'ai trouvé que le meilleur moyen de résoudre ce problème est d'utiliser Rand () au lieu de arc4random ()
le code, dans votre cas, pourrait être:
var i = Int(Rand()) % items.count
Cette méthode générera une valeur Int
aléatoire entre le minimum et le maximum donnés
func randomInt(min: Int, max:Int) -> Int {
return min + Int(arc4random_uniform(UInt32(max - min + 1)))
}
Le crash que vous avez rencontré est dû au fait que Swift a détecté une incohérence de type lors de l'exécution. Depuis Int! = UInt32, vous devrez d'abord transtyper l'argument d'entrée de arc4random_uniform avant de pouvoir calculer le nombre aléatoire.
Swift ne permet pas de transtyper d'un type entier à un autre si le résultat de la conversion ne correspond pas. Par exemple. le code suivant fonctionnera bien:
let x = 32
let y = UInt8(x)
Pourquoi? Parce que 32 est une valeur possible pour un int de type UInt8
. Mais le code suivant va échouer:
let x = 332
let y = UInt8(x)
En effet, vous ne pouvez pas affecter 332 à un type non signé de 8 bits, il ne peut prendre que les valeurs de 0 à 255 et rien d’autre.
Lorsque vous effectuez un casting en C, l'int est simplement tronqué, ce qui peut être inattendu ou non souhaité, car le programmeur peut ne pas être conscient du fait que la troncature peut avoir lieu. Donc, Swift gère les choses un peu différentes ici. Cela autorisera ce type d'incantation tant qu'aucune troncation n'a lieu, mais s'il y a troncation, vous obtenez une exception d'exécution. Si vous pensez que la troncature est acceptable, vous devez le faire vous-même pour que Swift sache qu'il s'agit du comportement souhaité, sinon Swift doit supposer qu'il s'agit d'un comportement accidentel.
Ceci est même documenté (documentation de UnsignedInteger
):
Convertissez à partir du type entier non signé le plus large de Swift, le recouvrement lors du débordement.
Et ce que vous voyez, c'est le "piégeage par débordement", qui est mal fait car, bien sûr, on aurait pu faire en sorte que ce piège explique réellement ce qui se passe.
En supposant que items
ne comporte jamais plus de 2 ^ 32 éléments (un peu plus de 4 milliards), le code suivant est sécurisé:
var i = Int(arc4random() % UInt32(items.count))
S'il peut avoir plus de 2 ^ 32 éléments, vous rencontrez un autre problème car vous avez besoin d'une fonction de nombre aléatoire différente qui produit des nombres aléatoires au-delà de 2 ^ 32.
Ce crash n'est possible que sur les systèmes 32 bits. Int
change entre 32 bits (Int32) et 64 bits (Int64) en fonction de l'architecture du périphérique ( voir la documentation ).
Le maximum de UInt32
est 2^32 − 1
. Le maximum de Int64
est 2^63 − 1
, donc Int64
peut facilement gérer UInt32.max
. Cependant, le nombre maximal de Int32
est 2^31 − 1
, ce qui signifie que UInt32
peut gérer des nombres supérieurs à Int32
et que la création d'un Int32
à partir d'un nombre supérieur à 2^31-1
entraînera un débordement.
J'ai confirmé cela en essayant de compiler la ligne Int(UInt32.max)
. Sur les simulateurs et les appareils plus récents, la compilation est parfaite. Mais j'ai connecté mon ancien iPod Touch (appareil 32 bits) et j'ai eu cette erreur de compilation:
Débordement d'entier lors de la conversion de
UInt32
enInt
Xcode ne compilera même pas cette ligne pour les périphériques 32 bits, ce qui est probablement le crash qui se produit au moment de l'exécution. Beaucoup des autres réponses de cet article sont de bonnes solutions, je ne les ajouterai donc pas et ne les copierai pas. Je pensais simplement qu'il manquait à cette question une explication détaillée de ce qui se passait.
Vous pouvez utiliser
Int(Rand())
Pour éviter les mêmes nombres aléatoires au démarrage de l'application, vous pouvez appeler srand ()
srand(UInt32(NSDate().timeIntervalSinceReferenceDate))
let randomNumber: Int = Int(Rand()) % items.count
Cela créera automatiquement un Int aléatoire pour vous:
var i = random() % items.count
i est de type Int, aucune conversion n'est donc nécessaire!