So erstellen Sie ein Array von Objekten mit fester Größe

101

In Swift versuche ich, ein Array von 64 SKSpriteNode zu erstellen. Ich möchte es zuerst leer initialisieren, dann Sprites in die ersten 16 Zellen und die letzten 16 Zellen einfügen (Simulation eines Schachspiels).

Nach dem, was ich im Dokument verstanden habe, hätte ich etwas erwartet wie:

var sprites = SKSpriteNode()[64];

oder

var sprites4 : SKSpriteNode[64];

Aber es funktioniert nicht. Im zweiten Fall wird die Fehlermeldung "Arrays mit fester Länge werden noch nicht unterstützt" angezeigt. Kann das echt sein? Für mich klingt das nach einer Grundfunktion. Ich muss direkt über ihren Index auf das Element zugreifen.

Henri Lapierre
quelle

Antworten:

146

Arrays mit fester Länge werden noch nicht unterstützt. Was bedeutet das eigentlich? Nicht, dass Sie nicht nviele verschiedene Dinge erstellen können - natürlich können Sie nur let a = [ 1, 2, 3 ]ein Array von drei IntSekunden erstellen . Es bedeutet einfach , dass Array - Größe ist nicht etwas , dass Sie erklären können als Typinformationen .

Wenn Sie ein Array von nils möchten , benötigen Sie zunächst ein Array eines optionalen Typs - [SKSpriteNode?]nicht [SKSpriteNode]-, wenn Sie eine Variable vom nicht optionalen Typ deklarieren, unabhängig davon, ob es sich um ein Array oder einen einzelnen Wert handelt, kann dies nicht der Fall sein nil. (Beachten Sie auch, dass dies [SKSpriteNode?]anders ist als [SKSpriteNode]?... Sie möchten ein Array von Optionen, kein optionales Array.)

Swift ist von Natur aus sehr explizit in Bezug auf die Anforderung, dass Variablen initialisiert werden müssen, da Annahmen über den Inhalt nicht initialisierter Referenzen eine der Möglichkeiten sind, wie Programme in C (und einigen anderen Sprachen) fehlerhaft werden können. Sie müssen also explizit nach einem [SKSpriteNode?]Array fragen , das 64 nils enthält :

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

Dies gibt jedoch tatsächlich Folgendes zurück [SKSpriteNode?]?: ein optionales Array optionaler Sprites. (Ein bisschen seltsam, da init(count:,repeatedValue:)es nicht möglich sein sollte, null zurückzugeben.) Um mit dem Array zu arbeiten, müssen Sie es auspacken. Es gibt einige Möglichkeiten, dies zu tun, aber in diesem Fall würde ich die optionale Bindungssyntax bevorzugen:

if var sprites = [SKSpriteNode?](repeating: nil, count: 64){
    sprites[0] = pawnSprite
}
Rickster
quelle
Danke, ich habe es versucht, aber ich hatte das "?" Vergessen. Ich kann den Wert jedoch immer noch nicht ändern. Ich habe beide ausprobiert: 1) sprites [0] = spritePawn und 2) sprites.insert (spritePawn, atIndex: 0).
Henri Lapierre
1
Überraschung! Klicken spritesSie mit der Befehlstaste in Ihren Editor / Spielplatz, um den abgeleiteten Typ anzuzeigen - es handelt sich tatsächlich um SKSpriteNode?[]?ein optionales Array optionaler Sprites. Sie können eine Option nicht abonnieren, daher müssen Sie sie auspacken ... siehe bearbeitete Antwort.
Rickster
Das ist in der Tat ziemlich seltsam. Wie Sie bereits erwähnt haben, sollte das Array meines Erachtens nicht optional sein, da wir es explizit als? [] Und nicht als? []? Definiert haben. Etwas ärgerlich, wenn ich es jedes Mal auspacken muss, wenn ich es brauche. In jedem Fall scheint dies zu funktionieren: var sprites = SKSpriteNode? [] (Count: 64, repeatValue: nil); if var unwrappedSprite = sprites {unwrappedSprite [0] = spritePawn; }
Henri Lapierre
Syntax hat sich für Swift 3 und 4 geändert, siehe andere Antworten unten
Crashalot
61

Das Beste, was Sie jetzt tun können, ist, ein Array mit einer Anfangszählung zu erstellen, die Null wiederholt:

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

Sie können dann beliebige Werte eingeben.


In Swift 3.0 :

var sprites = [SKSpriteNode?](repeating: nil, count: 64)
Drawag
quelle
5
Gibt es eine Möglichkeit , ein Array mit fester Größe zu deklarieren ?
レ ッ. ス
2
@ AlexanderSupertramp nein, es gibt keine Möglichkeit, eine Größe für ein Array zu deklarieren
drawag
1
@ ア レ ッ ク ス Es gibt keine Möglichkeit, eine feste Größe für ein Array zu deklarieren, aber Sie können sicherlich eine eigene Struktur erstellen, die ein Array umschließt, das eine feste Größe erzwingt.
Drawag
10

Diese Frage wurde bereits beantwortet, aber für einige zusätzliche Informationen zum Zeitpunkt von Swift 4:

Im Falle einer Leistung sollten Sie Speicher für das Array reservieren, wenn Sie es dynamisch erstellen, z. B. indem Sie Elemente mit hinzufügen Array.append().

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

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

Wenn Sie die minimale Anzahl von Elementen kennen, die Sie hinzufügen, aber nicht die maximale Anzahl, sollten Sie lieber verwenden array.reserveCapacity(minimumCapacity: 64).

Andreas
quelle
6

Deklarieren Sie einen leeren SKSpriteNode, damit das Auspacken nicht erforderlich ist

var sprites = [SKSpriteNode](count: 64, repeatedValue: SKSpriteNode())
Carlos.V
quelle
7
Sei vorsichtig damit. Es wird das Array mit der gleichen Instanz dieses Objekts füllen (man könnte unterschiedliche Instanzen erwarten)
Andy Hin
Ok, aber es löst auch die OP-Frage. Wenn Sie wissen, dass das Array mit demselben Instanzobjekt gefüllt ist, müssen Sie sich damit befassen, nichts für ungut.
Carlos.V
5

Semantisch am nächsten wäre vorerst ein Tupel mit einer festen Anzahl von Elementen.

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)

Dies ist jedoch (1) sehr unangenehm zu verwenden und (2) das Speicherlayout ist undefiniert. (zumindest mir unbekannt)

eonil
quelle
5

Swift 4

Sie können es sich als Array von Objekten oder als Array von Referenzen vorstellen.

  • [SKSpriteNode] muss tatsächliche Objekte enthalten
  • [SKSpriteNode?] kann entweder Verweise auf Objekte enthalten oder nil

Beispiele

  1. Erstellen eines Arrays mit 64 Standardwerten SKSpriteNode :

    var sprites = [SKSpriteNode](repeatElement(SKSpriteNode(texture: nil),
                                               count: 64))
  2. Erstellen eines Arrays mit 64 leeren Slots (auch als Optional bezeichnet ):

    var optionalSprites = [SKSpriteNode?](repeatElement(nil,
                                          count: 64))
  3. Konvertieren eines Arrays von Optionen in ein Array von Objekten (Reduzieren [SKSpriteNode?]in [SKSpriteNode]):

    let flatSprites = optionalSprites.flatMap { $0 }

    Das countErgebnis flatSpriteshängt von der Anzahl der Objekte in ab optionalSprites: Leere Optionen werden ignoriert, dh übersprungen.

SwiftArchitect
quelle
flatMapveraltet ist, sollte es nach compactMapMöglichkeit aktualisiert werden. (Ich kann diese Antwort nicht bearbeiten)
HaloZero
1

Wenn Sie ein Array mit fester Größe wünschen und es mit nilWerten initialisieren möchten, können Sie ein Array mit UnsafeMutableBufferPointer64 Knoten damit zuweisen und dann durch Abonnieren der Zeigertypinstanz aus / in den Speicher lesen / schreiben. Dies hat auch den Vorteil, dass nicht überprüft werden muss, ob der Speicher neu zugewiesen werden Arraymuss. Es würde mich jedoch wundern, wenn der Compiler dies nicht für Arrays optimiert, die keine weiteren Aufrufe von Methoden haben, deren Größe möglicherweise geändert werden muss, außer am Erstellungsort.

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

Dies ist jedoch nicht sehr benutzerfreundlich. Also, machen wir einen Wrapper!

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

Dies ist eine Klasse und keine Struktur, daher fallen hier einige Referenzkosten an. Sie können es structstattdessen in a ändern. Da Swift Ihnen jedoch nicht die Möglichkeit bietet, Kopierinitialisierer und deinitStrukturen zu verwenden, benötigen Sie eine Freigabemethode ( func release() { memory.deallocate() }), und alle kopierten Instanzen der Struktur verweisen auf denselben Speicher.

Nun, diese Klasse kann gerade gut genug sein. Die Verwendung ist einfach:

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

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

for sprite in sprites {
    print(sprite!)
}

Weitere Protokolle zur Implementierung der Konformität finden Sie in der Array-Dokumentation (scrollen Sie zu Beziehungen ).

Andreas
quelle
-3

Eine Sache, die Sie tun könnten, wäre, ein Wörterbuch zu erstellen. Könnte ein wenig schlampig sein, wenn man bedenkt, dass man nach 64 Elementen sucht, aber es erledigt den Job. Ich bin mir nicht sicher, ob es der "bevorzugte Weg" ist, aber es hat bei mir mit einem Array von Strukturen funktioniert.

var tasks = [0:[forTasks](),1:[forTasks](),2:[forTasks](),3:[forTasks](),4:[forTasks](),5:[forTasks](),6:[forTasks]()]
Craig
quelle
2
Wie ist das besser als das Array? Für mich ist es ein Hack, der das Problem nicht einmal löst: Sie könnten tasks[65] = foosowohl in diesem Fall als auch im Fall eines Arrays aus der Frage sehr gut einen machen .
LaX