วิธีสร้างอาร์เรย์ของวัตถุขนาดคงที่


102

ใน Swift ฉันกำลังพยายามสร้างอาร์เรย์ 64 SKSpriteNode ฉันต้องการเริ่มต้นให้ว่างก่อนจากนั้นฉันจะใส่ Sprites ใน 16 เซลล์แรกและ 16 เซลล์สุดท้าย (จำลองเกมหมากรุก)

จากสิ่งที่ฉันเข้าใจในเอกสารฉันคาดหวังสิ่งต่างๆเช่น:

var sprites = SKSpriteNode()[64];

หรือ

var sprites4 : SKSpriteNode[64];

แต่มันไม่ได้ผล ในกรณีที่สองฉันได้รับข้อผิดพลาดว่า: "ยังไม่รองรับอาร์เรย์ที่มีความยาวคงที่" ที่สามารถเป็นจริง? สำหรับฉันมันดูเหมือนเป็นคุณสมบัติพื้นฐาน ฉันต้องการเข้าถึงองค์ประกอบโดยตรงจากดัชนีของพวกเขา

คำตอบ:


152

ยังไม่รองรับอาร์เรย์ที่มีความยาวคงที่ นั่นหมายความว่าอย่างไร? ไม่ใช่ว่าคุณจะสร้างอาร์เรย์ของnหลาย ๆ สิ่งไม่ได้ - เห็นได้ชัดว่าคุณทำได้let a = [ 1, 2, 3 ]เพื่อให้ได้อาร์เรย์สามIntเอส หมายความว่าขนาดอาร์เรย์ไม่ใช่สิ่งที่คุณสามารถประกาศเป็นข้อมูลประเภทได้

หากคุณต้องการอาร์เรย์ของnils อันดับแรกคุณจะต้องมีอาร์เรย์ของประเภทที่เป็นทางเลือก - [SKSpriteNode?]ไม่ใช่[SKSpriteNode]- หากคุณประกาศตัวแปรประเภทที่ไม่ใช่ทางเลือกไม่ว่าจะเป็นอาร์เรย์หรือค่าเดียวก็ไม่สามารถเป็นnilได้ (โปรดทราบว่า[SKSpriteNode?]แตกต่างจาก[SKSpriteNode]?... คุณต้องการอาร์เรย์ของตัวเลือกไม่ใช่อาร์เรย์เสริม)

Swift มีความชัดเจนมากโดยการออกแบบเกี่ยวกับการกำหนดให้ตัวแปรเริ่มต้นเนื่องจากสมมติฐานเกี่ยวกับเนื้อหาของการอ้างอิงที่ไม่ได้เริ่มต้นเป็นหนึ่งในวิธีที่โปรแกรมในภาษา C (และภาษาอื่น ๆ ) อาจกลายเป็นบั๊กได้ ดังนั้นคุณต้องขอ[SKSpriteNode?]อาร์เรย์ที่มี 64 nilวินาทีอย่างชัดเจน:

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

สิ่งนี้จะคืนค่า a [SKSpriteNode?]?แม้ว่า: อาร์เรย์ของสไปรต์ที่เป็นทางเลือก (แปลกไปหน่อยเพราะinit(count:,repeatedValue:)ไม่น่าจะคืนค่าศูนย์ได้) ในการทำงานกับอาร์เรย์คุณจะต้องแกะมันออก มีสองสามวิธีในการทำเช่นนั้น แต่ในกรณีนี้ฉันชอบไวยากรณ์การผูกเพิ่มเติม:

if var sprites = [SKSpriteNode?](repeating: nil, count: 64){
    sprites[0] = pawnSprite
}

ขอบคุณฉันลองแล้ว แต่ลืม "?" ไปแล้ว อย่างไรก็ตามฉันยังไม่สามารถเปลี่ยนค่าได้? ฉันลองทั้งสองอย่าง: 1) สไปรต์ [0] = สไปรต์โพสต์และ 2) สไปรท์. อินเซิร์ต (สไปรต์โพสต์, atIndex: 0)
Henri Lapierre

1
เซอร์ไพรส์! คลิก Cmd spritesในเครื่องมือแก้ไข / สนามเด็กเล่นเพื่อดูประเภทที่สรุปได้ - จริงๆแล้วSKSpriteNode?[]?คืออาร์เรย์ของสไปรต์ที่เป็นทางเลือก คุณไม่สามารถสมัครสมาชิกได้ดังนั้นคุณต้องแกะออก ... ดูคำตอบที่แก้ไข
rickster

มันค่อนข้างแปลกจริงๆ ดังที่คุณกล่าวมาฉันไม่คิดว่าอาร์เรย์ควรเป็นทางเลือกเนื่องจากเรากำหนดไว้อย่างชัดเจนว่า? [] และไม่ใช่? []? น่ารำคาญที่ต้องแกะมันทุกครั้งที่ต้องการ ไม่ว่าในกรณีใดสิ่งนี้ดูเหมือนจะใช้ได้: var sprites = SKSpriteNode? [] (count: 64, repeatValue: nil); ถ้า var unrappedSprite = สไปรต์ {unrappedSprite [0] = spritePawn; }
Henri Lapierre

ไวยากรณ์มีการเปลี่ยนแปลงสำหรับ Swift 3 และ 4 โปรดดูคำตอบอื่น ๆ ด้านล่าง
Crashalot

62

สิ่งที่ดีที่สุดที่คุณจะทำได้ในตอนนี้คือสร้างอาร์เรย์ที่มีการนับเริ่มต้นที่เป็นศูนย์

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

จากนั้นคุณสามารถกรอกค่าใดก็ได้ที่คุณต้องการ


ในSwift 3.0 :

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

5
มีวิธีการประกาศอาร์เรย์ขนาดคงที่หรือไม่?
アレックス

2
@AlexanderSupertramp ไม่ไม่มีวิธีประกาศขนาดสำหรับอาร์เรย์
drewag

1
@ アレックスไม่มีวิธีการประกาศขนาดคงที่สำหรับอาร์เรย์ แต่คุณสามารถสร้างโครงสร้างของคุณเองที่ห่ออาร์เรย์ที่บังคับใช้ขนาดคงที่ได้
drewag

11

คำถามนี้ได้รับคำตอบแล้ว แต่สำหรับข้อมูลเพิ่มเติมในช่วงเวลาของ Swift 4:

ในกรณีของประสิทธิภาพคุณควรสำรองหน่วยความจำสำหรับอาร์เรย์ในกรณีที่สร้างแบบไดนามิกเช่นการเพิ่มองค์ประกอบด้วยArray.append().

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

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

ถ้าคุณรู้ว่าจำนวนเงินขั้นต่ำขององค์ประกอบที่คุณจะเพิ่มไป array.reserveCapacity(minimumCapacity: 64)แต่ไม่ได้จำนวนเงินสูงสุดที่คุณควรจะค่อนข้างใช้


6

ประกาศ SKSpriteNode ที่ว่างเปล่าดังนั้นจึงไม่จำเป็นต้องทำการแกะ

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

9
ระวังเรื่องนี้ด้วย มันจะเติมอาร์เรย์ด้วยอินสแตนซ์เดียวกันของออบเจ็กต์นั้น (อาจคาดว่าอินสแตนซ์ที่แตกต่างกัน)
Andy Hin

โอเค แต่มันช่วยแก้คำถาม OP ได้เช่นกันเมื่อรู้ว่าอาร์เรย์เต็มไปด้วยวัตถุอินสแตนซ์เดียวกันคุณจะต้องจัดการกับมันโดยไม่มีความผิด
Carlos.V

5

สำหรับตอนนี้สิ่งที่ใกล้เคียงทางความหมายที่สุดคือทูเพิลที่มีจำนวนองค์ประกอบคงที่

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)

แต่นี่เป็น (1) ไม่สะดวกในการใช้งานและ (2) เค้าโครงหน่วยความจำไม่ได้กำหนดไว้ (อย่างน้อยฉันก็ไม่รู้จัก)


5

สวิฟต์ 4

คุณสามารถคิดว่ามันเป็นอาร์เรย์ของวัตถุเทียบกับอาร์เรย์ของการอ้างอิง

  • [SKSpriteNode] ต้องมีวัตถุจริง
  • [SKSpriteNode?] สามารถมีการอ้างอิงถึงวัตถุหรือ nil

ตัวอย่าง

  1. การสร้างอาร์เรย์ด้วยค่าเริ่มต้น 64 SKSpriteNode:

    var sprites = [SKSpriteNode](repeatElement(SKSpriteNode(texture: nil),
                                               count: 64))
    
  2. การสร้างอาร์เรย์ที่มีช่องว่าง 64 ช่อง (aka optionals ):

    var optionalSprites = [SKSpriteNode?](repeatElement(nil,
                                          count: 64))
    
  3. การแปลงอาร์เรย์ของตัวเลือกเป็นอาร์เรย์ของออบเจ็กต์ (ยุบ[SKSpriteNode?]เป็น[SKSpriteNode]):

    let flatSprites = optionalSprites.flatMap { $0 }
    

    countของที่เกิดflatSpritesขึ้นอยู่กับการนับจำนวนของวัตถุในoptionalSprites: optionals ที่ว่างเปล่าจะถูกละเว้นเช่นข้าม


flatMapเลิกใช้งานแล้วควรอัปเดตcompactMapหากเป็นไปได้ (ฉันไม่สามารถแก้ไขคำตอบนี้ได้)
HaloZero

1

หากสิ่งที่คุณต้องการคืออาร์เรย์ขนาดคงที่และเริ่มต้นด้วยnilค่าคุณสามารถใช้UnsafeMutableBufferPointerจัดสรรหน่วยความจำสำหรับ 64 โหนดจากนั้นอ่าน / เขียนจาก / ไปยังหน่วยความจำโดยการสมัครอินสแตนซ์ชนิดตัวชี้ นอกจากนี้ยังมีประโยชน์ในการหลีกเลี่ยงการตรวจสอบว่าต้องจัดสรรหน่วยความจำใหม่หรือArrayไม่ อย่างไรก็ตามฉันจะแปลกใจถ้าคอมไพเลอร์ไม่ปรับให้เหมาะสมที่สุดสำหรับอาร์เรย์ที่ไม่มีการเรียกใช้เมธอดที่อาจต้องมีการปรับขนาดอีกต่อไปนอกเหนือจากที่ไซต์การสร้าง

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

อย่างไรก็ตามสิ่งนี้ไม่เป็นมิตรกับผู้ใช้มากนัก มาทำกระดาษห่อกันเถอะ!

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

ตอนนี้นี่คือคลาสไม่ใช่โครงสร้างดังนั้นจึงมีค่าใช้จ่ายในการนับการอ้างอิงที่เกิดขึ้นที่นี่ คุณสามารถเปลี่ยนเป็น a structแทนได้ แต่เนื่องจาก Swift ไม่ได้ให้คุณมีความสามารถในการใช้ตัวเริ่มต้นการคัดลอกและdeinitบนโครงสร้างคุณจึงต้องใช้เมธอด deallocation ( func release() { memory.deallocate() }) และอินสแตนซ์ที่คัดลอกทั้งหมดของโครงสร้างจะอ้างอิงหน่วยความจำเดียวกัน

ตอนนี้ชั้นเรียนนี้อาจจะดีพอแล้ว การใช้งานนั้นง่ายมาก:

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

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

for sprite in sprites {
    print(sprite!)
}

สำหรับโปรโตคอลเพิ่มเติมที่จะใช้ความสอดคล้องโปรดดูเอกสาร Array (เลื่อนไปที่ความสัมพันธ์ )


-3

สิ่งหนึ่งที่คุณทำได้คือการสร้างพจนานุกรม อาจจะเลอะเทอะเล็กน้อยเมื่อพิจารณาว่าคุณกำลังมองหา 64 องค์ประกอบ แต่ก็ทำให้งานลุล่วง ฉันไม่แน่ใจว่ามันเป็น "วิธีที่ต้องการ" หรือไม่ แต่มันใช้ได้ผลสำหรับฉันโดยใช้อาร์เรย์ของโครงสร้าง

var tasks = [0:[forTasks](),1:[forTasks](),2:[forTasks](),3:[forTasks](),4:[forTasks](),5:[forTasks](),6:[forTasks]()]

2
มันดีกว่าอาร์เรย์อย่างไร? สำหรับฉันแล้วมันเป็นการแฮ็กที่ไม่สามารถแก้ปัญหาได้: คุณสามารถทำได้ดีtasks[65] = fooทั้งในกรณีนี้และกรณีของอาร์เรย์จากคำถาม
LaX
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.