Jak utworzyć tablicę obiektów o stałym rozmiarze

102

W języku Swift próbuję utworzyć tablicę 64 SKSpriteNode. Chciałbym najpierw zainicjować go jako pusty, następnie umieścić Sprites w pierwszych 16 komórkach, a następnie w ostatnich 16 komórkach (symulując grę w szachy).

Z tego, co zrozumiałem w dokumencie, spodziewałbym się czegoś takiego:

var sprites = SKSpriteNode()[64];

lub

var sprites4 : SKSpriteNode[64];

Ale to nie działa. W drugim przypadku pojawia się komunikat o błędzie: „Tablice o stałej długości nie są jeszcze obsługiwane”. Czy to może być prawdziwe? Dla mnie brzmi to jak podstawowa funkcja. Muszę uzyskać dostęp do elementu bezpośrednio przez ich indeks.

Henri Lapierre
źródło

Odpowiedzi:

150

Tablice o stałej długości nie są jeszcze obsługiwane. Co to właściwie oznacza? Nie nznaczy let a = [ 1, 2, 3 ]to, że nie możesz utworzyć tablicy wielu rzeczy - oczywiście możesz po prostu zrobić, aby uzyskać tablicę trzech Intsekund. Oznacza to po prostu, że rozmiar tablicy nie jest czymś, co można zadeklarować jako informację o typie .

Jeśli chcesz mieć tablicę nils, najpierw będziesz potrzebować tablicy typu opcjonalnego - a [SKSpriteNode?]nie [SKSpriteNode]- jeśli deklarujesz zmienną typu nie opcjonalnego, niezależnie od tego, czy jest to tablica, czy pojedyncza wartość, nie może być nil. (Zauważ też, że [SKSpriteNode?]różni się od [SKSpriteNode]?... potrzebujesz tablicy opcji, a nie opcjonalnej tablicy).

Swift z założenia bardzo wyraźnie mówi o wymaganiu inicjalizacji zmiennych, ponieważ założenia dotyczące zawartości niezainicjowanych referencji są jednym ze sposobów, w jakie programy w C (i niektórych innych językach) mogą stać się błędne. Musisz więc jawnie poprosić o [SKSpriteNode?]tablicę zawierającą 64 nils:

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

W rzeczywistości zwraca to [SKSpriteNode?]?jednak: opcjonalną tablicę opcjonalnych sprite'ów. (Trochę dziwne, ponieważ init(count:,repeatedValue:)nie powinno być w stanie zwrócić nil). Aby pracować z tablicą, musisz ją rozpakować. Jest na to kilka sposobów, ale w tym przypadku wolałbym opcjonalną składnię powiązań:

if var sprites = [SKSpriteNode?](repeating: nil, count: 64){
    sprites[0] = pawnSprite
}
rickster
źródło
Dzięki, próbowałem tego, ale zapomniałem „?”. Jednak nadal nie mogę zmienić wartości? Wypróbowałem oba: 1) sprites [0] = spritePawn i 2) sprites.insert (spritePawn, atIndex: 0).
Henri Lapierre
1
Niespodzianka! Kliknij Cmd spritesw swoim edytorze / placu zabaw, aby zobaczyć jego wywnioskowany typ - w rzeczywistości jest to SKSpriteNode?[]?: opcjonalna tablica opcjonalnych sprite'ów. Nie możesz indeksować opcjonalnej, więc musisz ją rozpakować ... zobacz edytowaną odpowiedź.
rickster
To naprawdę dziwne. Jak wspomniałeś, nie sądzę, aby tablica była opcjonalna, ponieważ wyraźnie zdefiniowaliśmy ją jako? [], A nie? [] ?. Trochę irytujące, że muszę go rozpakowywać za każdym razem, gdy go potrzebuję. W każdym razie wydaje się to działać: var sprites = SKSpriteNode? [] (Count: 64, repeatValue: nil); if var unrappedSprite = sprites {unrappedSprite [0] = spritePawn; }
Henri Lapierre
Składnia zmieniła się dla Swift 3 i 4, zobacz inne odpowiedzi poniżej
Crashalot
62

Najlepsze, co możesz teraz zrobić, to utworzyć tablicę z początkową liczbą powtarzającą się zerem:

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

Następnie możesz wpisać dowolne wartości.


W Swift 3.0 :

var sprites = [SKSpriteNode?](repeating: nil, count: 64)
drewag
źródło
5
czy istnieje sposób zadeklarowania tablicy o stałym rozmiarze?
ア レ ッ ク ス
2
@AlexanderSupertramp nie, nie ma możliwości zadeklarowania rozmiaru tablicy
drewag
1
@ ア レ ッ ク ス Nie ma możliwości zadeklarowania stałego rozmiaru tablicy, ale z pewnością można stworzyć własną strukturę, która otacza tablicę, która wymusza stały rozmiar.
drewag
10

Odpowiedź na to pytanie została już udzielona, ​​ale dodatkowe informacje w wersji Swift 4:

W przypadku wydajności należy zarezerwować pamięć na tablicę, w przypadku jej dynamicznego tworzenia, np. Dodawania elementów za pomocą Array.append().

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

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

Jeśli znasz minimalną ilość elementów, które dodasz do niego, ale nie maksymalną, powinieneś raczej użyć array.reserveCapacity(minimumCapacity: 64).

Andreas
źródło
6

Zadeklaruj pusty SKSpriteNode, aby nie było potrzeby rozpakowywania

var sprites = [SKSpriteNode](count: 64, repeatedValue: SKSpriteNode())
Carlos V.
źródło
7
Ostrożnie z tym. Wypełni tablicę tą samą instancją tego obiektu (można się spodziewać różnych instancji)
Andy Hin
Ok, ale rozwiązuje to pytanie OP, również wiedząc, że tablica jest wypełniona tym samym obiektem instancji, będziesz musiał sobie z tym poradzić, bez obrazy.
Carlos V,
5

Na razie najbliższa semantycznie byłaby krotka ze stałą liczbą elementów.

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)

Ale jest to (1) bardzo niewygodne w użyciu, a (2) układ pamięci jest niezdefiniowany. (przynajmniej mi nieznane)

eonil
źródło
5

Szybki 4

Możesz w pewnym sensie myśleć o tym jako o tablicy obiektów w porównaniu z tablicą odniesień.

  • [SKSpriteNode] musi zawierać rzeczywiste obiekty
  • [SKSpriteNode?] może zawierać odwołania do obiektów lub nil

Przykłady

  1. Tworzenie tablicy z 64 domyślnymi SKSpriteNode :

    var sprites = [SKSpriteNode](repeatElement(SKSpriteNode(texture: nil),
                                               count: 64))
    
  2. Tworzenie tablicy z 64 pustymi gniazdami (inaczej opcjonalnymi ):

    var optionalSprites = [SKSpriteNode?](repeatElement(nil,
                                          count: 64))
    
  3. Konwersja tablicy opcji na tablicę obiektów (zwijanie [SKSpriteNode?]do [SKSpriteNode]):

    let flatSprites = optionalSprites.flatMap { $0 }

    Wynik countwynikowy flatSpriteszależy od liczby obiektów w optionalSprites: puste opcje będą ignorowane, czyli pomijane.

SwiftArchitect
źródło
flatMapjest przestarzały, należy go zaktualizować, compactMapjeśli to możliwe. (Nie mogę edytować tej odpowiedzi)
HaloZero
1

Jeśli chcesz mieć tablicę o stałym rozmiarze i zainicjować ją nilwartościami, możesz użyć UnsafeMutableBufferPointer, przydzielić jej pamięć 64 węzłom, a następnie odczytywać / zapisywać z / do pamięci przez indeksowanie instancji typu wskaźnika. Ma to również tę zaletę, że pozwala uniknąć sprawdzania, czy pamięć musi zostać ponownie przydzielona Array. Byłbym jednak zaskoczony, gdyby kompilator nie zoptymalizował tego dla tablic, które nie mają więcej wywołań metod, które mogą wymagać zmiany rozmiaru, poza miejscem tworzenia.

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

Nie jest to jednak zbyt przyjazne dla użytkownika. Zróbmy więc opakowanie!

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

To jest klasa, a nie struktura, więc istnieje tutaj pewien koszt zliczania odniesień. Zamiast tego możesz zmienić to na structa, ale ponieważ Swift nie zapewnia możliwości używania inicjatorów kopiowania i deinitstruktur, będziesz potrzebować metody deallocation ( func release() { memory.deallocate() }), a wszystkie skopiowane wystąpienia struktury będą odwoływać się do tej samej pamięci.

Teraz ta klasa może być wystarczająco dobra. Jego użycie jest proste:

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

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

for sprite in sprites {
    print(sprite!)
}

Aby uzyskać więcej protokołów do zaimplementowania zgodności, zobacz dokumentację tablicy (przewiń do sekcji Relacje ).

Andreas
źródło
-3

Jedną z rzeczy, które możesz zrobić, byłoby utworzenie słownika. Może być trochę niechlujny, biorąc pod uwagę, że szukasz 64 elementów, ale spełnia swoje zadanie. Nie jestem pewien, czy jest to „preferowany sposób”, ale zadziałał przy użyciu tablicy struktur.

var tasks = [0:[forTasks](),1:[forTasks](),2:[forTasks](),3:[forTasks](),4:[forTasks](),5:[forTasks](),6:[forTasks]()]
Craig
źródło
2
Jak to jest lepsze niż tablica? Dla mnie to hack, który nawet nie rozwiązuje problemu: możesz bardzo dobrze zrobić tasks[65] = foozarówno w tym przypadku, jak iw przypadku tablicy z pytania.
LaX