Как создать массив объектов фиксированного размера

102

В Swift я пытаюсь создать массив из 64 SKSpriteNode. Я хочу сначала инициализировать его пустым, затем я бы поместил спрайты в первые 16 ячеек и последние 16 ячеек (имитируя шахматную партию).

Из того, что я понял в документе, я ожидал чего-то вроде:

var sprites = SKSpriteNode()[64];

или

var sprites4 : SKSpriteNode[64];

Но не работает. Во втором случае я получаю сообщение об ошибке: «Массивы фиксированной длины еще не поддерживаются». Это может быть реально? Для меня это звучит как основная функция. Мне нужно получить доступ к элементу напрямую по их индексу.

Анри Лапьер
источник

Ответы:

150

Массивы фиксированной длины пока не поддерживаются. Что это на самом деле означает? Не то чтобы вы не могли создать массив из nмногих вещей - очевидно, вы можете просто сделать, let a = [ 1, 2, 3 ]чтобы получить массив из трех Ints. Это просто означает, что размер массива не является чем-то, что вы можете объявить как информацию о типе .

Если вам нужен массив nils, вам сначала понадобится массив необязательного типа - [SKSpriteNode?], а не [SKSpriteNode]- если вы объявляете переменную необязательного типа, будь то массив или одно значение, этого не может быть nil. (Также обратите внимание, что [SKSpriteNode?]это отличается от [SKSpriteNode]?... вам нужен массив дополнительных элементов, а не дополнительный массив.)

Swift очень явно требует инициализации переменных, потому что предположения о содержании неинициализированных ссылок - один из способов, с помощью которых программы на C (и некоторых других языках) могут вызывать ошибки. Итак, вам нужно явно запросить [SKSpriteNode?]массив, содержащий 64 nilс:

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

На самом деле это возвращает a [SKSpriteNode?]?: необязательный массив необязательных спрайтов. (Немного странно, так как init(count:,repeatedValue:)не должно возвращать nil.) Для работы с массивом вам нужно его развернуть. Есть несколько способов сделать это, но в данном случае я бы предпочел необязательный синтаксис привязки:

if var sprites = [SKSpriteNode?](repeating: nil, count: 64){
    sprites[0] = pawnSprite
}
рикстер
источник
Спасибо, я попробовал, но забыл знак "?". Однако я все еще не могу изменить значение? Я пробовал оба: 1) sprites [0] = spritePawn и 2) sprites.insert (spritePawn, atIndex: 0).
Henri Lapierre
1
Сюрприз! Cmd-щелкните spritesв редакторе / игровой площадке, чтобы увидеть его предполагаемый тип - на самом деле SKSpriteNode?[]?это необязательный массив необязательных спрайтов. Вы не можете подписать необязательный индекс, поэтому вам нужно его развернуть ... см. Отредактированный ответ.
rickster
Это действительно довольно странно. Как вы упомянули, я не думаю, что массив должен быть необязательным, поскольку мы явно определили его как? [], А не? [] ?. Раздражает необходимость разворачивать его каждый раз, когда мне это нужно. В любом случае, похоже, это работает: var sprites = SKSpriteNode? [] (Count: 64, duplicValue: nil); если var unwrappedSprite = спрайты {unwrappedSprite [0] = spritePawn; }
Анри Лапьер,
Синтаксис изменился для 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 04
1
@ ア レ ッ ク ス Невозможно объявить фиксированный размер для массива, но вы, безусловно, можете создать свою собственную структуру, которая обертывает массив, который обеспечивает фиксированный размер.
drewag
10

На этот вопрос уже был дан ответ, но для получения дополнительной информации во время 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
Будьте осторожны с этим. Он заполнит массив одним и тем же экземпляром этого объекта (можно было бы ожидать разные экземпляры)
Энди Хин,
Хорошо, но он решает вопрос 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

Swift 4

Вы можете думать об этом как о массиве объектов и массивов ссылок.

  • [SKSpriteNode] должен содержать реальные объекты
  • [SKSpriteNode?] может содержать либо ссылки на объекты, либо nil

Примеры

  1. Создание массива с 64 по умолчанию SKSpriteNode :

    var sprites = [SKSpriteNode](repeatElement(SKSpriteNode(texture: nil),
                                               count: 64))
  2. Создание массива с 64 пустыми слотами (также известными как опции ):

    var optionalSprites = [SKSpriteNode?](repeatElement(nil,
                                          count: 64))
  3. Преобразование массива опций в массив объектов (сворачивание [SKSpriteNode?]в[SKSpriteNode] ):

    let flatSprites = optionalSprites.flatMap { $0 }

    countИз получившихся flatSpritesзависит от кол - объектов optionalSprites: пустые опции будут игнорироваться, то есть пропускается.

SwiftАрхитектор
источник
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;
    }
}

Теперь это класс, а не структура, поэтому здесь возникают некоторые накладные расходы на подсчет ссылок. structВместо этого вы можете изменить его на a , но поскольку Swift не предоставляет вам возможности использовать инициализаторы копирования и deinitструктуры, вам понадобится метод освобождения (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!)
}

Дополнительные протоколы для реализации соответствия см. В документации по массивам (перейдите к разделу « Связи» ).

Андреас
источник
-3

Вы могли бы создать словарь. Может быть, это немного небрежно, учитывая, что вы ищете 64 элемента, но он выполняет свою работу. Я не уверен, что это «предпочтительный способ» сделать это, но у меня он работал с использованием массива структур.

var tasks = [0:[forTasks](),1:[forTasks](),2:[forTasks](),3:[forTasks](),4:[forTasks](),5:[forTasks](),6:[forTasks]()]
Крейг
источник
2
Чем это лучше, чем массив? Для меня это хак, который даже не решает проблему: вы вполне можете сделать a tasks[65] = fooкак в этом случае, так и в случае массива из вопроса.
LaX