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


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.