Cara membuat larik objek berukuran tetap

102

Di Swift, saya mencoba membuat array 64 SKSpriteNode. Saya ingin menginisialisasi kosong terlebih dahulu, lalu saya akan meletakkan Sprite di 16 sel pertama, dan 16 sel terakhir (mensimulasikan permainan catur).

Dari apa yang saya pahami di dokumen, saya mengharapkan sesuatu seperti:

var sprites = SKSpriteNode()[64];

atau

var sprites4 : SKSpriteNode[64];

Tapi itu tidak berhasil. Dalam kasus kedua, saya mendapatkan pesan kesalahan yang mengatakan: "Larik dengan panjang tetap belum didukung". Apakah itu nyata? Bagi saya itu terdengar seperti fitur dasar. Saya perlu mengakses elemen secara langsung melalui indeksnya.

Henri Lapierre
sumber

Jawaban:

148

Array dengan panjang tetap belum didukung. Apa sebenarnya artinya itu? Bukan berarti Anda tidak dapat membuat larik berisi nbanyak hal - jelas Anda bisa melakukannya let a = [ 1, 2, 3 ]untuk mendapatkan larik tiga Intdetik. Ini berarti bahwa ukuran array bukanlah sesuatu yang dapat Anda deklarasikan sebagai informasi tipe .

Jika Anda menginginkan larik nils, pertama-tama Anda memerlukan larik berjenis opsional - [SKSpriteNode?], bukan [SKSpriteNode]- jika Anda mendeklarasikan variabel berjenis non-opsional, apakah itu larik atau nilai tunggal, tidak boleh nil. (Perhatikan juga bahwa [SKSpriteNode?]berbeda dari [SKSpriteNode]?... Anda menginginkan larik opsional, bukan larik opsional.)

Swift sangat eksplisit dengan desain tentang mewajibkan variabel tersebut diinisialisasi, karena asumsi tentang konten referensi yang tidak diinisialisasi adalah salah satu cara program dalam C (dan beberapa bahasa lain) dapat menjadi buggy. Jadi, Anda perlu secara eksplisit meminta [SKSpriteNode?]array yang berisi 64 nils:

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

Ini sebenarnya mengembalikan a [SKSpriteNode?]?, meskipun: array opsional sprite opsional. (Agak aneh, karena init(count:,repeatedValue:)seharusnya tidak dapat mengembalikan nol.) Untuk bekerja dengan array, Anda harus membukanya. Ada beberapa cara untuk melakukan itu, tetapi dalam kasus ini saya lebih menyukai sintaks pengikatan opsional:

if var sprites = [SKSpriteNode?](repeating: nil, count: 64){
    sprites[0] = pawnSprite
}
rickster
sumber
Terima kasih, saya sudah mencoba yang itu, tetapi saya lupa "?". Namun, saya masih tidak dapat mengubah nilainya? Saya mencoba keduanya: 1) sprites [0] = spritePawn dan 2) sprites.insert (spritePawn, atIndex: 0).
Henri Lapierre
1
Mengherankan! Cmd-klik spritesdi editor / taman bermain Anda untuk melihat jenis kesimpulannya - sebenarnya SKSpriteNode?[]?: array opsional sprite opsional. Anda tidak dapat berlangganan opsional, jadi Anda harus membukanya ... lihat jawaban yang diedit.
rickster
Itu memang sangat aneh. Seperti yang Anda sebutkan, saya tidak berpikir array harus opsional, karena kita secara eksplisit mendefinisikannya sebagai? [] Dan bukan? [] ?. Agak menjengkelkan karena harus membukanya setiap kali saya membutuhkannya. Dalam kasus apapun, ini sepertinya berhasil: var sprites = SKSpriteNode? [] (Count: 64, repeatValue: nil); jika var unwrappedSprite = sprite {unwrappedSprite [0] = spritePawn; }
Henri Lapierre
Sintaks telah berubah untuk Swift 3 dan 4, silakan lihat jawaban lain di bawah ini
Crashalot
61

Hal terbaik yang dapat Anda lakukan untuk saat ini adalah membuat array dengan jumlah awal yang berulang nihil:

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

Anda kemudian dapat mengisi nilai apa pun yang Anda inginkan.


Di Swift 3.0 :

var sprites = [SKSpriteNode?](repeating: nil, count: 64)
menggambar
sumber
5
apakah ada cara untuk mendeklarasikan array dengan ukuran tetap?
ア レ ッ ク ス
2
@AlexanderSupertramp tidak, tidak ada cara untuk mendeklarasikan ukuran untuk sebuah array
drewag
1
@ ア レ ッ ク ス Tidak ada cara untuk mendeklarasikan ukuran tetap untuk sebuah array, tapi Anda pasti dapat membuat struct Anda sendiri yang membungkus sebuah array yang memaksakan ukuran tetap.
menggambar tanggal
10

Pertanyaan ini telah dijawab, tetapi untuk beberapa informasi tambahan pada saat Swift 4:

Dalam hal kinerja, Anda harus mencadangkan memori untuk larik, jika membuatnya secara dinamis, seperti menambahkan elemen dengan Array.append() .

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

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

Jika Anda mengetahui jumlah minimum elemen yang akan Anda tambahkan, tetapi bukan jumlah maksimumnya, Anda sebaiknya menggunakan array.reserveCapacity(minimumCapacity: 64).

Andreas
sumber
6

Deklarasikan SKSpriteNode kosong, sehingga tidak perlu dibuka

var sprites = [SKSpriteNode](count: 64, repeatedValue: SKSpriteNode())
Carlos.V
sumber
7
Hati-hati dengan ini. Ini akan mengisi array dengan contoh yang sama dari objek itu (orang mungkin mengharapkan contoh yang berbeda)
Andy Hin
Ok, tapi itu memecahkan pertanyaan OP, juga, mengetahui array diisi dengan objek contoh yang sama maka Anda harus menghadapinya, jangan tersinggung.
Carlos.V
5

Untuk saat ini, yang terdekat secara semantik adalah tupel dengan jumlah elemen tetap.

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)

Tetapi ini (1) sangat tidak nyaman digunakan dan (2) tata letak memori tidak ditentukan. (setidaknya tidak saya ketahui)

eonil
sumber
5

Cepat 4

Anda bisa membayangkannya sebagai larik objek vs. larik referensi.

  • [SKSpriteNode] harus berisi objek yang sebenarnya
  • [SKSpriteNode?] dapat berisi referensi ke objek, atau nil

Contoh

  1. Membuat array dengan 64 default SKSpriteNode :

    var sprites = [SKSpriteNode](repeatElement(SKSpriteNode(texture: nil),
                                               count: 64))
  2. Membuat array dengan 64 slot kosong (alias opsional ):

    var optionalSprites = [SKSpriteNode?](repeatElement(nil,
                                          count: 64))
  3. Mengonversi larik opsional menjadi larik objek (diciutkan [SKSpriteNode?]menjadi [SKSpriteNode]):

    let flatSprites = optionalSprites.flatMap { $0 }

    The countdari yang dihasilkan tersebut flatSpritestergantung pada hitungan objek dalam optionalSprites: optionals kosong akan diabaikan, yaitu dilewati.

SwiftArchitect
sumber
flatMapsudah tidak digunakan lagi, harus diperbarui menjadi compactMapjika memungkinkan. (Saya tidak dapat mengedit jawaban ini)
HaloZero
1

Jika yang Anda inginkan adalah larik ukuran tetap, dan menginisialisasinya dengan nilnilai, Anda dapat menggunakan UnsafeMutableBufferPointer, mengalokasikan memori untuk 64 node dengannya, dan kemudian membaca / menulis dari / ke memori dengan memasukkan contoh tipe penunjuk. Ini juga memiliki keuntungan menghindari pemeriksaan apakah memori harus dialokasikan kembali, dan Arraymemang demikian. Namun saya akan terkejut jika kompiler tidak mengoptimalkannya untuk array yang tidak memiliki panggilan lagi ke metode yang mungkin memerlukan pengubahan ukuran, selain di situs pembuatan.

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

Namun ini tidak terlalu ramah pengguna. Jadi, ayo buat pembungkusnya!

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

Sekarang, ini adalah kelas, dan bukan struktur, jadi ada beberapa referensi penghitungan overhead yang terjadi di sini. Anda dapat mengubahnya menjadi a struct, tetapi karena Swift tidak memberi Anda kemampuan untuk menggunakan penginisialisasi salinan dan deinitpada struktur, Anda memerlukan metode deallocation (func release() { memory.deallocate() } ), dan semua contoh struktur yang disalin akan mereferensikan memori yang sama.

Sekarang, kelas ini mungkin sudah cukup bagus. Penggunaannya sederhana:

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

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

for sprite in sprites {
    print(sprite!)
}

Untuk protokol lainnya untuk mengimplementasikan kesesuaian, lihat dokumentasi Array (gulir ke Hubungan ).

Andreas
sumber
-3

Satu hal yang dapat Anda lakukan adalah membuat kamus. Mungkin sedikit ceroboh mengingat Anda mencari 64 elemen tetapi itu menyelesaikan pekerjaan. Saya tidak yakin apakah ini adalah "cara yang disukai" untuk melakukannya tetapi bekerja untuk saya menggunakan array struct.

var tasks = [0:[forTasks](),1:[forTasks](),2:[forTasks](),3:[forTasks](),4:[forTasks](),5:[forTasks](),6:[forTasks]()]
Craig
sumber
2
Bagaimana itu lebih baik daripada array? Bagi saya ini adalah peretasan yang bahkan tidak menyelesaikan masalah: Anda bisa melakukan dengan sangat baik tasks[65] = foodalam kedua kasus ini dan kasus array dari pertanyaan.
LaX