Cách tạo mảng đối tượng có kích thước cố định


102

Trong Swift, tôi đang cố gắng tạo một mảng 64 SKSpriteNode. Trước tiên tôi muốn khởi tạo nó trống, sau đó tôi sẽ đặt Sprites vào 16 ô đầu tiên và 16 ô cuối cùng (mô phỏng một trò chơi cờ vua).

Từ những gì tôi hiểu trong tài liệu, tôi sẽ mong đợi một cái gì đó như:

var sprites = SKSpriteNode()[64];

hoặc là

var sprites4 : SKSpriteNode[64];

Nhưng nó không hoạt động. Trong trường hợp thứ hai, tôi gặp lỗi thông báo: "Các mảng có độ dài cố định chưa được hỗ trợ". Đó có thể là sự thật? Đối với tôi đó là một tính năng cơ bản. Tôi cần truy cập trực tiếp phần tử bằng chỉ mục của chúng.

Câu trả lời:


148

Mảng có độ dài cố định chưa được hỗ trợ. Điều đó thực sự có ý nghĩa gì? Không phải là bạn không thể tạo một mảng nnhiều thứ - rõ ràng là bạn chỉ có thể làm let a = [ 1, 2, 3 ]để có được một mảng ba Ints. Nó có nghĩa đơn giản là kích thước mảng không phải là thứ mà bạn có thể khai báo dưới dạng thông tin kiểu .

Nếu bạn muốn có một mảng nils, trước tiên bạn sẽ cần một mảng có kiểu tùy chọn - [SKSpriteNode?], không phải [SKSpriteNode]- nếu bạn khai báo một biến thuộc kiểu không tùy chọn, cho dù đó là một mảng hay một giá trị, nó không thể có được nil. (Cũng lưu ý rằng [SKSpriteNode?]khác với [SKSpriteNode]?... bạn muốn một mảng tùy chọn, không phải một mảng tùy chọn.)

Swift được thiết kế rất rõ ràng về việc yêu cầu các biến phải được khởi tạo, vì các giả định về nội dung của các tham chiếu chưa được khởi tạo là một trong những cách mà các chương trình trong C (và một số ngôn ngữ khác) có thể trở nên lỗi. Vì vậy, bạn cần yêu cầu rõ ràng một [SKSpriteNode?]mảng chứa 64 nils:

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

Điều này thực sự trả về một [SKSpriteNode?]?, mặc dù: một mảng tùy chọn của các sprite tùy chọn. (Hơi kỳ lạ, vì init(count:,repeatedValue:)sẽ không thể trả về nil.) Để làm việc với mảng, bạn cần phải mở nó ra. Có một số cách để làm điều đó, nhưng trong trường hợp này, tôi thích cú pháp ràng buộc tùy chọn:

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

Cảm ơn, tôi đã thử cái đó, nhưng tôi đã quên dấu "?". Tuy nhiên, tôi vẫn không thể thay đổi giá trị? Tôi đã thử cả hai: 1) sprites [0] = spritePawn và 2) sprites.insert (spritePawn, atIndex: 0).
Henri Lapierre

1
Sự ngạc nhiên! Nhấp vào Cmd spritestrong trình soạn thảo / sân chơi của bạn để xem kiểu suy luận của nó - thực ra là SKSpriteNode?[]?: một mảng tùy chọn gồm các sprite tùy chọn. Bạn không thể chỉ số dưới một tùy chọn, vì vậy bạn phải mở nó ra ... xem câu trả lời đã chỉnh sửa.
rickster

Điều đó thực sự khá kỳ lạ. Như bạn đã đề cập, tôi không nghĩ mảng nên là tùy chọn, vì chúng tôi đã định nghĩa rõ ràng nó là? [] Chứ không phải? [] ?. Loại khó chịu khi phải mở nó ra mỗi khi tôi cần nó. Trong mọi trường hợp, điều này dường như hoạt động: var sprites = SKSpriteNode? [] (Count: 64, repeatValue: nil); if var unsrappedSprite = sprites {unrappedSprite [0] = spritePawn; }
Henri Lapierre

Cú pháp đã thay đổi cho Swift 3 và 4, vui lòng xem các câu trả lời khác bên dưới
Crashalot

61

Điều tốt nhất bạn có thể làm bây giờ là tạo một mảng có số đếm ban đầu lặp lại nil:

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

Sau đó, bạn có thể điền vào bất kỳ giá trị nào bạn muốn.


Trong Swift 3.0 :

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

5
có cách nào để khai báo một mảng có kích thước cố định?
ア レ ッ ク ス

2
@AlexanderSupertramp không, không có cách nào để khai báo kích thước cho một mảng
drawag

1
@ ア レ ッ ク ス Không có cách nào để khai báo kích thước cố định cho một mảng, nhưng bạn chắc chắn có thể tạo cấu trúc của riêng mình để bao bọc một mảng thực thi kích thước cố định.
drawag

10

Câu hỏi này đã được trả lời, nhưng đối với một số thông tin bổ sung tại thời điểm Swift 4:

Trong trường hợp hiệu suất, bạn nên dành bộ nhớ cho mảng, trong trường hợp tạo động, chẳng hạn như thêm các phần tử với Array.append().

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

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

Nếu bạn biết số lượng phần tử tối thiểu bạn sẽ thêm vào nó, nhưng không phải số lượng tối đa, bạn nên sử dụng array.reserveCapacity(minimumCapacity: 64).


6

Khai báo một SKSpriteNode trống, vì vậy sẽ không cần phải mở

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

7
Hãy cẩn thận với điều này. Nó sẽ lấp đầy mảng với các trường hợp tương tự của đối tượng đó (người ta có thể hy vọng trường hợp riêng biệt)
Andy Hin

Ok, nhưng nó cũng giải quyết được câu hỏi OP, nếu biết mảng được lấp đầy bởi cùng một đối tượng thể hiện thì bạn sẽ phải giải quyết nó, không có gì vi phạm.
Carlos.V

5

Hiện tại, gần nhất về mặt ngữ nghĩa sẽ là một bộ với số phần tử cố định.

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)

Nhưng điều này (1) rất khó sử dụng và (2) bố cục bộ nhớ không xác định. (ít nhất là tôi chưa biết)


5

Swift 4

Bạn có thể phần nào nghĩ về nó như mảng đối tượng so với mảng tham chiếu.

  • [SKSpriteNode] phải chứa các đối tượng thực tế
  • [SKSpriteNode?] có thể chứa các tham chiếu đến các đối tượng hoặc nil

Ví dụ

  1. Tạo một mảng với 64 mặc định SKSpriteNode :

    var sprites = [SKSpriteNode](repeatElement(SKSpriteNode(texture: nil),
                                               count: 64))
  2. Tạo một mảng với 64 vị trí trống (còn gọi là tùy chọn ):

    var optionalSprites = [SKSpriteNode?](repeatElement(nil,
                                          count: 64))
  3. Chuyển một mảng tùy chọn thành một mảng đối tượng (thu gọn [SKSpriteNode?]thành [SKSpriteNode]):

    let flatSprites = optionalSprites.flatMap { $0 }

    Kết countquả flatSpritesphụ thuộc vào số lượng đối tượng trong optionalSprites: tùy chọn trống sẽ bị bỏ qua, tức là bị bỏ qua.


flatMapkhông được dùng nữa, nó sẽ được cập nhật compactMapnếu có thể. (Tôi không thể chỉnh sửa câu trả lời này)
HaloZero

1

Nếu những gì bạn muốn là một mảng có kích thước cố định và khởi tạo nó với nilcác giá trị, bạn có thể sử dụng một UnsafeMutableBufferPointer, cấp phát bộ nhớ cho 64 nút với nó, sau đó đọc / ghi từ / vào bộ nhớ bằng cách viết ký hiệu đối tượng kiểu con trỏ. Điều này cũng có lợi ích là tránh kiểm tra xem bộ nhớ có phải được phân bổ lại hay Arraykhông. Tuy nhiên, tôi sẽ ngạc nhiên nếu trình biên dịch không tối ưu hóa điều đó cho các mảng không có thêm bất kỳ lệnh gọi nào đến các phương thức có thể yêu cầu thay đổi kích thước, ngoài trang web tạ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()

Tuy nhiên, điều này không thân thiện với người dùng. Vì vậy, chúng ta hãy làm một 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;
    }
}

Bây giờ, đây là một lớp chứ không phải một cấu trúc, do đó, có một số tham chiếu về chi phí đếm phát sinh ở đây. Bạn có thể thay đổi nó thành một structthay thế, nhưng vì Swift không cung cấp cho bạn khả năng sử dụng các trình khởi tạo sao chép và deinittrên các cấu trúc, bạn sẽ cần một phương thức deallocation ( func release() { memory.deallocate() }) và tất cả các phiên bản được sao chép của cấu trúc sẽ tham chiếu đến cùng một bộ nhớ.

Bây giờ, lớp này có thể vừa đủ tốt. Việc sử dụng nó rất đơn giản:

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

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

for sprite in sprites {
    print(sprite!)
}

Để biết thêm các giao thức để triển khai tuân thủ, hãy xem tài liệu Mảng (cuộn đến Mối quan hệ ).


-3

Một điều bạn có thể làm là tạo một từ điển. Có thể hơi cẩu thả khi bạn đang tìm kiếm 64 yếu tố nhưng nó hoàn thành công việc. Tôi không chắc liệu nó có phải là "cách ưa thích" để làm điều đó hay không nhưng nó hoạt động với tôi bằng cách sử dụng một mảng cấu trúc.

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

2
Làm thế nào là tốt hơn so với mảng? Đối với tôi, đó là một vụ hack thậm chí không giải quyết được vấn đề: bạn rất có thể làm được tasks[65] = footrong cả trường hợp này và trường hợp một mảng từ câu hỏi.
LaX
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.