Як створити масив об’єктів фіксованого розміру


102

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

З того, що я зрозумів у документі, я міг би очікувати приблизно такого:

var sprites = SKSpriteNode()[64];

або

var sprites4 : SKSpriteNode[64];

Але це не працює. У другому випадку я отримую помилку: "Масиви фіксованої довжини ще не підтримуються". Чи може це бути реально? Для мене це звучить як основна особливість. Мені потрібно отримати доступ до елемента безпосередньо за їх індексом.

Відповіді:


148

Масиви фіксованої довжини ще не підтримуються. Що це насправді означає? Не те, що ви не можете створити масив з 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:)не може повертати нуль.) Для роботи з масивом вам потрібно буде розгорнути його. Є кілька способів це зробити, але в цьому випадку я б віддав перевагу необов’язковому синтаксису прив’язки:

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

Дякую, я спробував це, але забув "?". Однак я все ще не можу змінити значення? Я спробував обидва: 1) спрайти [0] = spritePawn і 2) sprites.insert (spritePawn, atIndex: 0).
Анрі Лап'єр,

1
Сюрприз! Клацніть Cmd spritesу вашому редакторі / дитячому майданчику, щоб побачити виведений тип - це насправді SKSpriteNode?[]?: необов’язковий масив необов’язкових спрайтів. Ви не можете індексувати необов’язковий, тому вам доведеться розгортати його ... див. Відредаговану відповідь.
рикстер

Це справді дивно. Як ви вже згадували, я не думаю, що масив не повинен бути необов’язковим, оскільки ми прямо визначали його як? [], А не? [] ?. Набридливо доводиться розгортати його кожного разу, коли мені це потрібно. У будь-якому випадку це, здається, працює: var sprites = SKSpriteNode? [] (Кількість: 64, повторенняValue: нуль); якщо вар unrappedSprite = sprites {unrappedSprite [0] = spritePawn; }
Анрі Лап’єр,

Синтаксис змінено для Swift 3 та 4, будь ласка, дивіться інші відповіді нижче
Crashalot

61

Найкраще, що ви зараз зможете зробити, це створити масив з початковим підрахунком, що повторює нуль:

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

Потім ви можете заповнити будь-які значення, які хочете.


У Swift 3.0 :

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

5
чи є спосіб оголосити масив фіксованого розміру?
ア レ ッ ク ス

2
@AlexanderSupertramp ні, немає можливості оголосити розмір масиву
drewag

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

7
Будьте обережні з цим. Він заповнить масив тим самим екземпляром цього об'єкта (можна очікувати окремих екземплярів)
Енді Хін,

Гаразд, але це вирішує питання 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 порожніми слотами (вони ж необов’язкові ):

    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замість, але оскільки 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
Чим це краще за масив? Для мене це хакерство, яке навіть не вирішує проблему: ви цілком можете зробити це tasks[65] = fooяк у цьому випадку, так і у випадку масиву із запитання.
LaX
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.