Como criar uma matriz de objetos de tamanho fixo

102

Em Swift, estou tentando criar uma matriz de 64 SKSpriteNode. Quero inicializá-lo vazio primeiro, depois colocaria Sprites nas primeiras 16 células e nas últimas 16 células (simulando um jogo de xadrez).

Pelo que entendi no documento, esperava algo como:

var sprites = SKSpriteNode()[64];

ou

var sprites4 : SKSpriteNode[64];

Mas não funciona. No segundo caso, recebo um erro dizendo: "Arrays de comprimento fixo ainda não são suportados". Isso pode ser real? Para mim, isso soa como um recurso básico. Preciso acessar o elemento diretamente por seu índice.

Henri Lapierre
fonte

Respostas:

148

Arrays de comprimento fixo ainda não são suportados. O que isso realmente significa? Não que você não possa criar uma matriz de nmuitas coisas - obviamente, você pode apenas fazer let a = [ 1, 2, 3 ]para obter uma matriz de três Ints. Significa simplesmente que o tamanho do array não é algo que você possa declarar como informação de tipo .

Se você quiser um array de nils, você primeiro precisará de um array de um tipo opcional - [SKSpriteNode?], não [SKSpriteNode]- se você declarar uma variável de tipo não opcional, seja um array ou um único valor, não pode ser nil. (Observe também que [SKSpriteNode?]é diferente de [SKSpriteNode]?... você deseja uma matriz de opcionais, não uma matriz opcional.)

O Swift é muito explícito por design sobre como exigir que as variáveis ​​sejam inicializadas, porque as suposições sobre o conteúdo das referências não inicializadas são uma das maneiras pelas quais os programas em C (e algumas outras linguagens) podem se tornar problemáticos. Portanto, você precisa pedir explicitamente uma [SKSpriteNode?]matriz que contenha 64 nils:

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

Na verdade, isso retorna um [SKSpriteNode?]?, porém: uma matriz opcional de sprites opcionais. (Um pouco estranho, já init(count:,repeatedValue:)que não deve ser capaz de retornar nil.) Para trabalhar com a matriz, você precisará desembrulhá-la. Existem algumas maneiras de fazer isso, mas, neste caso, prefiro a sintaxe de ligação opcional:

if var sprites = [SKSpriteNode?](repeating: nil, count: 64){
    sprites[0] = pawnSprite
}
rickster
fonte
Obrigado, eu tentei aquele, mas eu tinha esquecido o "?". No entanto, ainda não consigo alterar o valor? Eu tentei ambos: 1) sprites [0] = spritePawn e 2) sprites.insert (spritePawn, atIndex: 0).
Henri Lapierre
1
Surpresa! Clique com o botão direito do mouse spritesem seu editor / playground para ver seu tipo inferido - na verdade é SKSpriteNode?[]?: um array opcional de sprites opcionais. Você não pode subscrever um opcional, então você tem que desembrulhá-lo ... veja a resposta editada.
rickster
Isso é realmente muito estranho. Como você mencionou, não acho que a matriz deva ser opcional, já que a definimos explicitamente como? [] E não? [] ?. É meio chato ter que desembrulhar toda vez que preciso. Em qualquer caso, isso parece funcionar: var sprites = SKSpriteNode? [] (Contagem: 64, repeatValue: nil); if var unbrappedSprite = sprites {unbrappedSprite [0] = spritePawn; }
Henri Lapierre
A sintaxe mudou para Swift 3 e 4, veja outras respostas abaixo
Crashalot
61

O melhor que você poderá fazer agora é criar uma matriz com uma contagem inicial repetindo nulo:

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

Você pode então preencher os valores que desejar.


Em Swift 3.0 :

var sprites = [SKSpriteNode?](repeating: nil, count: 64)
Drewag
fonte
5
existe alguma maneira de declarar uma matriz de tamanho fixo?
ア レ ッ ク ス
2
@AlexanderSupertramp não, não há como declarar o tamanho de um array
desenhou em
1
@ ア レ ッ ク ス Não há como declarar um tamanho fixo para um array, mas você certamente pode criar sua própria estrutura que envolve um array que impõe um tamanho fixo.
Drewag em
10

Esta pergunta já foi respondida, mas para algumas informações extras no momento do Swift 4:

Em caso de desempenho, deve-se reservar memória para o array, no caso de criá-lo dinamicamente, como adicionar elementos com Array.append().

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

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

Se você sabe a quantidade mínima de elementos que vai adicionar, mas não a quantidade máxima, você deve usar array.reserveCapacity(minimumCapacity: 64).

Andreas
fonte
6

Declare um SKSpriteNode vazio, para que não haja necessidade de desempacotamento

var sprites = [SKSpriteNode](count: 64, repeatedValue: SKSpriteNode())
Carlos.V
fonte
7
Cuidado com isso. Ele preencherá a matriz com a mesma instância desse objeto (pode-se esperar instâncias distintas)
Andy Hin
Ok, mas também resolve a questão do OP, sabendo que o array está preenchido com o mesmo objeto de instância, você terá que lidar com isso, sem ofensa.
Carlos.V
5

Por enquanto, semanticamente mais próximo seria uma tupla com número fixo 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)

Mas isso é (1) muito desconfortável de usar e (2) o layout da memória é indefinido. (pelo menos desconhecido para mim)

eonil
fonte
5

Swift 4

Você pode pensar nisso de alguma forma como uma matriz de objetos versus uma matriz de referências.

  • [SKSpriteNode] deve conter objetos reais
  • [SKSpriteNode?] pode conter referências a objetos ou nil

Exemplos

  1. Criação de uma matriz com 64 padrão SKSpriteNode :

    var sprites = [SKSpriteNode](repeatElement(SKSpriteNode(texture: nil),
                                               count: 64))
    
  2. Criação de uma matriz com 64 slots vazios (também conhecidos como opcionais ):

    var optionalSprites = [SKSpriteNode?](repeatElement(nil,
                                          count: 64))
    
  3. Convertendo uma matriz de opcionais em uma matriz de objetos (recolhendo [SKSpriteNode?]em [SKSpriteNode]):

    let flatSprites = optionalSprites.flatMap { $0 }

    O countresultado do resultado flatSpritesdepende da contagem de objetos em optionalSprites: opcionais vazios serão ignorados, ou seja, ignorados.

SwiftArchitect
fonte
flatMapestá obsoleto, deve ser atualizado para, compactMapse possível. (Não consigo editar esta resposta)
HaloZero,
1

Se o que você deseja é um array de tamanho fixo e inicializa-o com nilvalores, você pode usar um UnsafeMutableBufferPointer, alocar memória para 64 nós com ele e, em seguida, ler / gravar de / para a memória subscrevendo a instância do tipo de ponteiro. Isso também tem a vantagem de evitar verificar se a memória deve ser realocada, o que é Arrayverdade. No entanto, ficaria surpreso se o compilador não otimizasse isso para matrizes que não têm mais chamadas para métodos que podem exigir redimensionamento, exceto no local de criação.

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

No entanto, isso não é muito amigável. Então, vamos fazer um invólucro!

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

Agora, esta é uma classe, não uma estrutura, portanto, há alguma sobrecarga de contagem de referência incorrida aqui. Você pode alterá-lo para um struct, mas como o Swift não oferece a capacidade de usar inicializadores de cópia e deinitem estruturas, você precisará de um método de desalocação ( func release() { memory.deallocate() }) e todas as instâncias copiadas da estrutura farão referência à mesma memória.

Agora, essa aula pode ser boa o suficiente. Seu uso é simples:

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

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

for sprite in sprites {
    print(sprite!)
}

Para obter mais protocolos para implementar a conformidade, consulte a documentação do Array (role até Relacionamentos ).

Andreas
fonte
-3

Uma coisa que você poderia fazer seria criar um dicionário. Pode ser um pouco desleixado, considerando que você está procurando 64 elementos, mas dá conta do recado. Não tenho certeza se é a "maneira preferida" de fazer isso, mas funcionou para mim usando uma série de estruturas.

var tasks = [0:[forTasks](),1:[forTasks](),2:[forTasks](),3:[forTasks](),4:[forTasks](),5:[forTasks](),6:[forTasks]()]
Craig
fonte
2
Como isso é melhor do que o array? Para mim, é um hack que nem mesmo resolve o problema: você poderia muito bem fazer um tasks[65] = fooneste caso e no caso de um array da questão.
LaX de