125

In Swift, I am trying to create an array of 64 SKSpriteNode. I want first to initialize it empty, then I would put Sprites in the first 16 cells, and the last 16 cells (simulating an chess game).

From what I understood in the doc, I would have expect something like:

var sprites = SKSpriteNode()[64];

or

var sprites4 : SKSpriteNode[64];

But it doesn't work. In the second case, I get an error saying: "Fixed-length arrays are not yet supported". Can that be real? To me that sounds like a basic feature. I need to access the element directly by their index.

HLP
  • 2,142
  • 5
  • 19
  • 20

8 Answers8

181

Fixed-length arrays are not yet supported. What does that actually mean? Not that you can't create an array of n many things — obviously you can just do let a = [ 1, 2, 3 ] to get an array of three Ints. It means simply that array size is not something that you can declare as type information.

If you want an array of nils, you'll first need an array of an optional type — [SKSpriteNode?], not [SKSpriteNode] — if you declare a variable of non-optional type, whether it's an array or a single value, it cannot be nil. (Also note that [SKSpriteNode?] is different from [SKSpriteNode]?... you want an array of optionals, not an optional array.)

Swift is very explicit by design about requiring that variables be initialized, because assumptions about the content of uninitialized references are one of the ways that programs in C (and some other languages) can become buggy. So, you need to explicitly ask for an [SKSpriteNode?] array that contains 64 nils:

var sprites = [SKSpriteNode?](repeating: nil, count: 64)
rickster
  • 124,678
  • 26
  • 272
  • 326
  • Thanks, I did try that one, but I had forget the "?". However, I am still unable to change the value? I tried both: 1) sprites[0] = spritePawn and 2) sprites.insert(spritePawn, atIndex:0). – HLP Jun 24 '14 at 20:23
  • 1
    Surprise! Cmd-click `sprites` in your editor/playground to see its inferred type — it's actually `SKSpriteNode?[]?`: an optional array of optional sprites. You can't subscript an optional, so you have to unwrap it... see edited answer. – rickster Jun 24 '14 at 20:49
  • That's quite odd indeed. As you mentioned, I don't think the array should be optional, as we explicitly defined it as ?[] and not ?[]?. Kind of annoying having to unwrap it every time I need it. In any case, this seem to work: var sprites = SKSpriteNode?[](count: 64, repeatedValue: nil); if var unwrappedSprite = sprites { unwrappedSprite[0] = spritePawn; } – HLP Jun 24 '14 at 21:25
  • Syntax has changed for Swift 3 and 4, please see other answers below – Crashalot May 21 '18 at 00:49
70

The best you are going to be able to do for now is create an array with an initial count repeating nil:

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

You can then fill in whatever values you want.


In Swift 3.0 :

var sprites = [SKSpriteNode?](repeating: nil, count: 64)
drewag
  • 93,393
  • 28
  • 139
  • 128
  • 7
    is there any way to **declare** an array of the fixed size? – Incerteza Mar 04 '15 at 15:11
  • 3
    @AlexanderSupertramp no, there is no way to declare a size for an array – drewag Mar 04 '15 at 15:32
  • 2
    @アレックス There is no way to declare a fixed size for an array, but you can certainly create your own struct that wraps an array that enforces a fixed size. – drewag Aug 23 '16 at 05:30
17

This question has already been answered, but for some extra information at the time of Swift 4:

In case of performance, you should reserve memory for the array, in case of dynamically creating it, such as adding elements with Array.append().

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

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

If you know the minimum amount of elements you'll add to it, but not the maximum amount, you should rather use array.reserveCapacity(minimumCapacity: 64).

  • I'm kinda a newbie with Swift but I think minimumCapacity is the only parameter to reserveCapacity. So, I think, reserveCapacity(64) is the same as reserveCapacity(minimumCapacity: 64). ... Actually, xcode says the second form is not valid ... which is confusing to me, but can't argue with a compiler – steve Jan 23 '23 at 17:08
  • @steve Swift has been evolving since this answer was written. Its ABI wasn't even stable back then. I haven't used the language in a few years, so I'm no longer updated on this. You might very well be correct about your observation. – Andreas is moving to Codidact Mar 19 '23 at 13:38
9

Declare an empty SKSpriteNode, so there won't be needing for unwraping

var sprites = [SKSpriteNode](count: 64, repeatedValue: SKSpriteNode())
Carlos.V
  • 409
  • 6
  • 13
  • 13
    Be careful with this. It will fill the array with the same instance of that object (one might expect distinct instances) – Andy Hin Oct 14 '16 at 05:04
  • Ok, but it solves the OP question, also, knowing the array is filled with the same instance object then you will have to deal with it, no offense. – Carlos.V Jul 19 '17 at 22:18
7

For now, semantically closest one would be a tuple with fixed number of elements.

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)

But this is (1) very uncomfortable to use and (2) memory layout is undefined. (at least unknown to me)

eonil
  • 83,476
  • 81
  • 317
  • 516
7

Swift 4

You can somewhat think about it as array of object vs. array of references.

  • [SKSpriteNode] must contain actual objects
  • [SKSpriteNode?] can contain either references to objects, or nil

Examples

  1. Creating an array with 64 default SKSpriteNode:

    var sprites = [SKSpriteNode](repeatElement(SKSpriteNode(texture: nil),
                                               count: 64))
    
  2. Creating an array with 64 empty slots (a.k.a optionals):

    var optionalSprites = [SKSpriteNode?](repeatElement(nil,
                                          count: 64))
    
  3. Converting an array of optionals into an array of objects (collapsing [SKSpriteNode?] into [SKSpriteNode]):

    let flatSprites = optionalSprites.flatMap { $0 }
    

    The count of the resulting flatSprites depends on the count of objects in optionalSprites: empty optionals will be ignored, i.e. skipped.

SwiftArchitect
  • 47,376
  • 28
  • 140
  • 179
  • `flatMap` is deprecated, it should be updated to `compactMap` if possible. (I can't edit this answer) – HaloZero Apr 09 '20 at 22:35
6

If what you want is a fixed size array, and initialize it with nil values, you can use an UnsafeMutableBufferPointer, allocate memory for 64 nodes with it, and then read/write from/to the memory by subscripting the pointer type instance. This also has the benefit of avoiding checking if the memory must be reallocated, which Array does. I would however be surprised if the compiler doesn't optimize that away for arrays that don't have any more calls to methods that may require resizing, other than at the creation site.

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

This is however not very user friendly. So, let's make a 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;
    }
}

Now, this is a class, and not a structure, so there's some reference counting overhead incurred here. You can change it to a struct instead, but because Swift doesn't provide you with an ability to use copy initializers and deinit on structures, you'll need a deallocation method (func release() { memory.deallocate() }), and all copied instances of the structure will reference the same memory.

Now, this class may just be good enough. Its use is simple:

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

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

for sprite in sprites {
    print(sprite!)
}

For more protocols to implement conformance to, see the Array documentation (scroll to Relationships).

-3

One thing you could do would be to create a dictionary. Might be a little sloppy considering your looking for 64 elements but it gets the job done. Im not sure if its the "preferred way" to do it but it worked for me using an array of structs.

var tasks = [0:[forTasks](),1:[forTasks](),2:[forTasks](),3:[forTasks](),4:[forTasks](),5:[forTasks](),6:[forTasks]()]
Craig
  • 553
  • 7
  • 13
  • 2
    How is that better than the array? To me it's a hack that doesn't even solve the problem: you could very well do a `tasks[65] = foo` in both this case and the case of an array from the question. – LaX Aug 25 '17 at 15:49