Cómo crear una matriz de objetos de tamaño fijo

102

En Swift, estoy tratando de crear una matriz de 64 SKSpriteNode. Primero quiero inicializarlo vacío, luego pondría Sprites en las primeras 16 celdas y las últimas 16 celdas (simulando un juego de ajedrez).

Por lo que entendí en el documento, habría esperado algo como:

var sprites = SKSpriteNode()[64];

o

var sprites4 : SKSpriteNode[64];

Pero no funciona. En el segundo caso, aparece un error que dice: "Las matrices de longitud fija aún no son compatibles". ¿Puede eso ser real? Para mí eso suena como una característica básica. Necesito acceder al elemento directamente por su índice.

Henri Lapierre
fuente

Respuestas:

148

Las matrices de longitud fija aún no son compatibles. ¿Qué significa eso realmente? No es que no pueda crear una matriz de nmuchas cosas; obviamente, puede hacerlo let a = [ 1, 2, 3 ]para obtener una matriz de tres Ints. Significa simplemente que el tamaño de la matriz no es algo que pueda declarar como información de tipo .

Si desea una matriz de nils, primero necesitará una matriz de un tipo opcional [SKSpriteNode?], no [SKSpriteNode], si declara una variable de tipo no opcional, ya sea una matriz o un valor único, no puede serlo nil. (También tenga en cuenta que [SKSpriteNode?]es diferente de [SKSpriteNode]?... desea una matriz de opcionales, no una matriz opcional).

Swift es muy explícito por diseño sobre la necesidad de inicializar las variables, porque las suposiciones sobre el contenido de las referencias no inicializadas son una de las formas en que los programas en C (y algunos otros lenguajes) pueden tener errores. Por lo tanto, debe solicitar explícitamente una [SKSpriteNode?]matriz que contenga 64 nils:

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

Esto en realidad devuelve [SKSpriteNode?]?, aunque: una matriz opcional de sprites opcionales. (Un poco extraño, ya init(count:,repeatedValue:)que no debería poder devolver nil). Para trabajar con la matriz, deberá desenvolverla. Hay algunas formas de hacerlo, pero en este caso preferiría la sintaxis de enlace opcional:

if var sprites = [SKSpriteNode?](repeating: nil, count: 64){
    sprites[0] = pawnSprite
}
rickster
fuente
Gracias, probé ese, pero había olvidado el "?". Sin embargo, ¿sigo sin poder cambiar el valor? Probé ambos: 1) sprites [0] = spritePawn y 2) sprites.insert (spritePawn, atIndex: 0).
Henri Lapierre
1
¡Sorpresa! Cmd-clic spritesen su editor / área de juegos para ver su tipo inferido - en realidad es SKSpriteNode?[]?: una matriz opcional de sprites opcionales. No puede subíndice un opcional, por lo que debe desenvolverlo ... vea la respuesta editada.
rickster
Eso es bastante extraño en verdad. Como mencionaste, no creo que la matriz deba ser opcional, ya que la definimos explícitamente como? [] Y no? [] ?. Es un poco molesto tener que desenvolverlo cada vez que lo necesito. En cualquier caso, esto parece funcionar: var sprites = SKSpriteNode? [] (Cuenta: 64, valorrepetido: nulo); if var UnwrappedSprite = sprites {UnwrappedSprite [0] = spritePawn; }
Henri Lapierre
La sintaxis ha cambiado para Swift 3 y 4, consulte otras respuestas a continuación
Crashalot
61

Lo mejor que podrá hacer por ahora es crear una matriz con un recuento inicial que se repite nulo:

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

A continuación, puede completar los valores que desee.


En Swift 3.0 :

var sprites = [SKSpriteNode?](repeating: nil, count: 64)
dibujar
fuente
5
¿Hay alguna forma de declarar una matriz de tamaño fijo?
ア レ ッ ク ス
2
@AlexanderSupertramp no, no hay forma de declarar un tamaño para una matriz
drewag
1
@ ア レ ッ ク ス No hay forma de declarar un tamaño fijo para una matriz, pero ciertamente puede crear su propia estructura que envuelva una matriz que imponga un tamaño fijo.
drewag
10

Esta pregunta ya ha sido respondida, pero para obtener información adicional en el momento de Swift 4:

En caso de rendimiento, debe reservar memoria para la matriz, en caso de crearla dinámicamente, como agregar elementos con Array.append().

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

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

Si conoce la cantidad mínima de elementos que le agregará, pero no la cantidad máxima, debería usarla array.reserveCapacity(minimumCapacity: 64).

Andreas
fuente
6

Declare un SKSpriteNode vacío, por lo que no será necesario desenvolver

var sprites = [SKSpriteNode](count: 64, repeatedValue: SKSpriteNode())
Carlos.V
fuente
7
Tenga cuidado con esto. Llenará la matriz con la misma instancia de ese objeto (uno podría esperar instancias distintas)
Andy Hin
Ok, pero resuelve la pregunta de OP, también, sabiendo que la matriz está llena con el mismo objeto de instancia, entonces tendrás que lidiar con él, sin ofender.
Carlos.V
5

Por ahora, semánticamente más cercano sería una tupla con un número fijo de elementos.

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)

Pero esto es (1) muy incómodo de usar y (2) el diseño de la memoria no está definido. (al menos desconocido para mí)

eonil
fuente
5

Rápido 4

De alguna manera, puede pensar en ello como una matriz de objeto frente a una matriz de referencias.

  • [SKSpriteNode] debe contener objetos reales
  • [SKSpriteNode?] puede contener referencias a objetos o nil

Ejemplos

  1. Creando una matriz con 64 por defecto SKSpriteNode :

    var sprites = [SKSpriteNode](repeatElement(SKSpriteNode(texture: nil),
                                               count: 64))
  2. Creando una matriz con 64 ranuras vacías (también conocidas como opcionales ):

    var optionalSprites = [SKSpriteNode?](repeatElement(nil,
                                          count: 64))
  3. Convertir una matriz de opcionales en una matriz de objetos (colapsar [SKSpriteNode?]en [SKSpriteNode]):

    let flatSprites = optionalSprites.flatMap { $0 }

    El countresultado flatSpritesdepende del recuento de objetos en optionalSprites: los opcionales vacíos serán ignorados, es decir, saltados.

SwiftArquitecto
fuente
flatMapestá en desuso, debería actualizarse a compactMapsi es posible. (No puedo editar esta respuesta)
HaloZero
1

Si lo que desea es una matriz de tamaño fijo e inicializarla con nilvalores, puede usar una UnsafeMutableBufferPointer, asignar memoria para 64 nodos con ella y luego leer / escribir desde / hacia la memoria subindicando la instancia del tipo de puntero. Esto también tiene la ventaja de evitar comprobar si la memoria debe reasignarse, lo que Arrayhace. Sin embargo, me sorprendería si el compilador no optimiza eso para las matrices que no tienen más llamadas a métodos que pueden requerir un cambio de tamaño, excepto en el sitio de creación.

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()

Sin embargo, esto no es muy fácil de usar. Entonces, ¡hagamos un envoltorio!

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;
    }
}

Ahora, esta es una clase, y no una estructura, por lo que hay algunos gastos generales de recuento de referencias incurridos aquí. En su structlugar, puede cambiarlo a a , pero debido a que Swift no le brinda la capacidad de usar inicializadores de copia y deiniten estructuras, necesitará un método de desasignación ( func release() { memory.deallocate() }), y todas las instancias copiadas de la estructura harán referencia a la misma memoria.

Ahora, esta clase puede ser lo suficientemente buena. Su uso es sencillo:

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

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

for sprite in sprites {
    print(sprite!)
}

Para obtener más protocolos para implementar la conformidad, consulte la documentación de Array (desplácese hasta Relaciones ).

Andreas
fuente
-3

Una cosa que podría hacer sería crear un diccionario. Puede ser un poco descuidado teniendo en cuenta que buscas 64 elementos, pero hace el trabajo. No estoy seguro si es la "forma preferida" de hacerlo, pero funcionó para mí usando una matriz de estructuras.

var tasks = [0:[forTasks](),1:[forTasks](),2:[forTasks](),3:[forTasks](),4:[forTasks](),5:[forTasks](),6:[forTasks]()]
Craig
fuente
2
¿Cómo es eso mejor que la matriz? Para mí, es un truco que ni siquiera resuelve el problema: muy bien podría hacer tasks[65] = footanto en este caso como en el caso de una matriz de la pregunta.
LaX