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


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: пустые опции будут игнорироваться, то есть пропускается.


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
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.