Comment créer un tableau d'objets de taille fixe

102

Dans Swift, j'essaye de créer un tableau de 64 SKSpriteNode. Je veux d'abord l'initialiser vide, puis je mettrais les Sprites dans les 16 premières cellules, et les 16 dernières cellules (simulant une partie d'échecs).

D'après ce que j'ai compris dans la doc, je m'attendrais à quelque chose comme:

var sprites = SKSpriteNode()[64];

ou

var sprites4 : SKSpriteNode[64];

Mais ça ne marche pas. Dans le second cas, j'obtiens une erreur disant: "Les tableaux de longueur fixe ne sont pas encore pris en charge". Cela peut-il être réel? Pour moi, cela ressemble à une fonctionnalité de base. J'ai besoin d'accéder à l'élément directement par leur index.

Henri Lapierre
la source

Réponses:

150

Les tableaux de longueur fixe ne sont pas encore pris en charge. Qu'est-ce que cela signifie réellement? Non pas que vous ne puissiez pas créer un tableau de nbeaucoup de choses - évidemment, vous pouvez simplement le faire let a = [ 1, 2, 3 ]pour obtenir un tableau de trois Ints. Cela signifie simplement que la taille du tableau n'est pas quelque chose que vous pouvez déclarer comme information de type .

Si vous voulez un tableau de nils, vous aurez d'abord besoin d'un tableau d'un type optionnel - [SKSpriteNode?], pas [SKSpriteNode]- si vous déclarez une variable de type non optionnel, que ce soit un tableau ou une valeur unique, cela ne peut pas être nil. (Notez également que [SKSpriteNode?]c'est différent de [SKSpriteNode]?... vous voulez un tableau d'options, pas un tableau facultatif.)

Swift est très explicite par conception sur le fait d'exiger l'initialisation des variables, car les hypothèses sur le contenu des références non initialisées sont l'une des façons dont les programmes en C (et dans certains autres langages) peuvent devenir bogués. Vous devez donc demander explicitement un [SKSpriteNode?]tableau contenant 64 nils:

var sprites = [SKSpriteNode?](repeating: nil, count: 64)

Cela renvoie en fait un [SKSpriteNode?]?, cependant: un tableau facultatif de sprites facultatifs. (Un peu étrange, car init(count:,repeatedValue:)il ne devrait pas être possible de renvoyer nil.) Pour travailler avec le tableau, vous devrez le déballer. Il y a plusieurs façons de le faire, mais dans ce cas, je préfère la syntaxe de liaison facultative:

if var sprites = [SKSpriteNode?](repeating: nil, count: 64){
    sprites[0] = pawnSprite
}
Rickster
la source
Merci, j'ai essayé celui-là, mais j'avais oublié le "?". Cependant, je ne parviens toujours pas à modifier la valeur? J'ai essayé les deux: 1) sprites [0] = spritePawn et 2) sprites.insert (spritePawn, atIndex: 0).
Henri Lapierre
1
Surprise! Cmd-cliquez spritesdans votre éditeur / terrain de jeu pour voir son type inféré - c'est en fait SKSpriteNode?[]?: un tableau facultatif de sprites facultatifs. Vous ne pouvez pas indiquer un optionnel, vous devez donc le dérouler ... voir la réponse modifiée.
rickster
C'est vraiment étrange. Comme vous l'avez mentionné, je ne pense pas que le tableau devrait être facultatif, car nous l'avons explicitement défini comme? [] Et non? [] ?. Un peu ennuyeux de devoir le déballer chaque fois que j'en ai besoin. Dans tous les cas, cela semble fonctionner: var sprites = SKSpriteNode? [] (Nombre: 64, valeur répétée: nil); si var unrappedSprite = sprites {unrappedSprite [0] = spritePawn; }
Henri Lapierre
La syntaxe a changé pour Swift 3 et 4, veuillez voir les autres réponses ci
Crashalot
62

Le mieux que vous puissiez faire pour l'instant est de créer un tableau avec un décompte initial répétant nul:

var sprites = [SKSpriteNode?](count: 64, repeatedValue: nil)

Vous pouvez ensuite indiquer les valeurs que vous souhaitez.


Dans Swift 3.0 :

var sprites = [SKSpriteNode?](repeating: nil, count: 64)
dessiner
la source
5
existe-t-il un moyen de déclarer un tableau de taille fixe?
ア レ ッ ク ス
2
@AlexanderSupertramp non, il n'y a aucun moyen de déclarer une taille pour un tableau
dessiné
1
@ ア レ ッ ク ス Il n'y a aucun moyen de déclarer une taille fixe pour un tableau, mais vous pouvez certainement créer votre propre structure qui encapsule un tableau qui impose une taille fixe.
tirage
10

Cette question a déjà été répondue, mais pour quelques informations supplémentaires au moment de Swift 4:

En cas de performances, vous devez réserver de la mémoire pour le tableau, en cas de création dynamique, comme l'ajout d'éléments avec Array.append().

var array = [SKSpriteNode]()
array.reserveCapacity(64)

for _ in 0..<64 {
    array.append(SKSpriteNode())
}

Si vous connaissez la quantité minimale d'éléments que vous y ajouterez, mais pas la quantité maximale, vous devriez plutôt utiliser array.reserveCapacity(minimumCapacity: 64).

Andreas
la source
6

Déclarez un SKSpriteNode vide, il n'y aura donc pas besoin de déballer

var sprites = [SKSpriteNode](count: 64, repeatedValue: SKSpriteNode())
Carlos.V
la source
7
Soyez prudent avec cela. Il remplira le tableau avec la même instance de cet objet (on pourrait s'attendre à des instances distinctes)
Andy Hin
Ok, mais cela résout la question OP, aussi, sachant que le tableau est rempli du même objet d'instance, vous devrez vous en occuper, sans infraction.
Carlos.V
5

Pour l'instant, le plus proche sémantiquement serait un tuple avec un nombre fixe d'éléments.

typealias buffer = (
    SKSpriteNode, SKSpriteNode, SKSpriteNode, SKSpriteNode,
    SKSpriteNode, SKSpriteNode, SKSpriteNode, SKSpriteNode,
    SKSpriteNode, SKSpriteNode, SKSpriteNode, SKSpriteNode,
    SKSpriteNode, SKSpriteNode, SKSpriteNode, SKSpriteNode,
    SKSpriteNode, SKSpriteNode, SKSpriteNode, SKSpriteNode,
    SKSpriteNode, SKSpriteNode, SKSpriteNode, SKSpriteNode,
    SKSpriteNode, SKSpriteNode, SKSpriteNode, SKSpriteNode,
    SKSpriteNode, SKSpriteNode, SKSpriteNode, SKSpriteNode,
    SKSpriteNode, SKSpriteNode, SKSpriteNode, SKSpriteNode,
    SKSpriteNode, SKSpriteNode, SKSpriteNode, SKSpriteNode,
    SKSpriteNode, SKSpriteNode, SKSpriteNode, SKSpriteNode,
    SKSpriteNode, SKSpriteNode, SKSpriteNode, SKSpriteNode,
    SKSpriteNode, SKSpriteNode, SKSpriteNode, SKSpriteNode,
    SKSpriteNode, SKSpriteNode, SKSpriteNode, SKSpriteNode,
    SKSpriteNode, SKSpriteNode, SKSpriteNode, SKSpriteNode,
    SKSpriteNode, SKSpriteNode, SKSpriteNode, SKSpriteNode)

Mais c'est (1) très inconfortable à utiliser et (2) la disposition de la mémoire n'est pas définie. (du moins inconnu de moi)

éonil
la source
5

Swift 4

Vous pouvez en quelque sorte le considérer comme un tableau d'objets contre un tableau de références.

  • [SKSpriteNode] doit contenir des objets réels
  • [SKSpriteNode?] peut contenir soit des références à des objets, soit nil

Exemples

  1. Création d'un tableau avec 64 par défaut SKSpriteNode :

    var sprites = [SKSpriteNode](repeatElement(SKSpriteNode(texture: nil),
                                               count: 64))
  2. Création d'un tableau avec 64 emplacements vides (aka optionnels ):

    var optionalSprites = [SKSpriteNode?](repeatElement(nil,
                                          count: 64))
  3. Conversion d'un tableau d'options en un tableau d'objets (réduction [SKSpriteNode?]en [SKSpriteNode]):

    let flatSprites = optionalSprites.flatMap { $0 }

    Le countrésultat flatSpritesdépend du nombre d'objets dans optionalSprites: les options vides seront ignorées, c'est-à-dire ignorées.

SwiftArchitect
la source
flatMapest obsolète, il doit être mis à jour compactMapsi possible. (Je ne peux pas modifier cette réponse)
HaloZero
1

Si vous voulez un tableau de taille fixe et l'initialisez avec des nilvaleurs, vous pouvez utiliser un UnsafeMutableBufferPointer, allouer de la mémoire pour 64 nœuds avec lui, puis lire / écrire depuis / vers la mémoire en indiquant l'instance de type pointeur. Cela a également l'avantage d'éviter de vérifier si la mémoire doit être réallouée, ce qui le Arrayfait. Je serais cependant surpris si le compilateur n'optimise pas cela pour les tableaux qui n'ont plus d'appels à des méthodes pouvant nécessiter un redimensionnement, autre que sur le site de création.

let count = 64
let sprites = UnsafeMutableBufferPointer<SKSpriteNode>.allocate(capacity: count)

for i in 0..<count {
    sprites[i] = ...
}

for sprite in sprites {
    print(sprite!)
}

sprites.deallocate()

Ce n'est cependant pas très convivial. Alors, faisons un emballage!

class ConstantSizeArray<T>: ExpressibleByArrayLiteral {
    
    typealias ArrayLiteralElement = T
    
    private let memory: UnsafeMutableBufferPointer<T>
    
    public var count: Int {
        get {
            return memory.count
        }
    }
    
    private init(_ count: Int) {
        memory = UnsafeMutableBufferPointer.allocate(capacity: count)
    }
    
    public convenience init(count: Int, repeating value: T) {
        self.init(count)
        
        memory.initialize(repeating: value)
    }
    
    public required convenience init(arrayLiteral: ArrayLiteralElement...) {
        self.init(arrayLiteral.count)
        
        memory.initialize(from: arrayLiteral)
    }
    
    deinit {
        memory.deallocate()
    }
    
    public subscript(index: Int) -> T {
        set(value) {
            precondition((0...endIndex).contains(index))
            
            memory[index] = value;
        }
        get {
            precondition((0...endIndex).contains(index))
            
            return memory[index]
        }
    }
}

extension ConstantSizeArray: MutableCollection {
    public var startIndex: Int {
        return 0
    }
    
    public var endIndex: Int {
        return count - 1
    }
    
    func index(after i: Int) -> Int {
        return i + 1;
    }
}

Maintenant, c'est une classe, et non une structure, il y a donc une surcharge de comptage de références encourue ici. Vous pouvez le changer en un à la structplace, mais comme Swift ne vous offre pas la possibilité d'utiliser des initialiseurs de copie et deinitsur des structures, vous aurez besoin d'une méthode de désallocation ( func release() { memory.deallocate() }), et toutes les instances copiées de la structure référenceront la même mémoire.

Maintenant, cette classe peut être juste assez bonne. Son utilisation est simple:

let sprites = ConstantSizeArray<SKSpriteNode?>(count: 64, repeating: nil)

for i in 0..<sprites.count {
    sprite[i] = ...
}

for sprite in sprites {
    print(sprite!)
}

Pour plus de protocoles auxquels implémenter la conformité, consultez la documentation Array (faites défiler jusqu'à Relations ).

Andreas
la source
-3

Une chose que vous pourriez faire serait de créer un dictionnaire. Peut-être un peu bâclé compte tenu de votre recherche de 64 éléments, mais cela fait le travail. Je ne sais pas si c'est la "façon préférée" de le faire mais cela a fonctionné pour moi en utilisant un tableau de structures.

var tasks = [0:[forTasks](),1:[forTasks](),2:[forTasks](),3:[forTasks](),4:[forTasks](),5:[forTasks](),6:[forTasks]()]
Craig
la source
2
En quoi est-ce mieux que le tableau? Pour moi, c'est un hack qui ne résout même pas le problème: vous pourriez très bien faire un tasks[65] = foodans ce cas et le cas d'un tableau à partir de la question.
LaX