オブジェクトの固定サイズ配列を作成する方法


101

Swiftでは、64のSKSpriteNodeの配列を作成しようとしています。最初に空に初期化し、次に最初の16セルと最後の16セルにスプライトを配置します(チェスゲームのシミュレーション)。

私がドキュメントで理解したことから、私は次のようなことを期待していました:

var sprites = SKSpriteNode()[64];

または

var sprites4 : SKSpriteNode[64];

しかし、それは機能しません。2番目のケースでは、「固定長配列はまだサポートされていません」というエラーが表示されます。それは本当ですか?私にはそれは基本的な機能のように聞こえます。インデックスで要素に直接アクセスする必要があります。

回答:


147

固定長配列はまだサポートされていません。それは実際にはどういう意味ですか?n多くのものの配列を作成することはできません。もちろんlet a = [ 1, 2, 3 ]、3つIntの配列を取得するだけで十分です。これは単に、配列のサイズが型情報として宣言できるものではないことを意味します。

の配列がnil必要な場合は、オプション[SKSpriteNode?]ではない型[SKSpriteNode]の変数を宣言する場合、配列ではなく単一の値であるかどうかにかかわらず、最初にオプションの型の配列が必要になりますnil。(また[SKSpriteNode?][SKSpriteNode]?... とは異なります。オプションの配列ではなく、オプションの配列が必要です。)

初期化されていない参照の内容についての仮定は、C(および他のいくつかの言語)のプログラムがバグになる可能性のある方法の1つであるため、Swiftは設計によって変数を初期化する必要があることを非常に明示しています。したがって、[SKSpriteNode?]64を含む配列を明示的に要求する必要がありますnil

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

[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)。
アンリラピエール2014年

1
驚き!spritesエディター/プレイグラウンドでCmdキーを押しながらクリックして、推論されたタイプを確認します—実際にSKSpriteNode?[]?は、オプションのスプライトのオプションの配列です。オプションを添え字にすることはできないので、それをアンラップする必要があります...編集された回答を参照してください。
リクスター2014年

それは確かにかなり奇妙です。あなたが言ったように、私たちは明示的に?[]として定義し、?[]?ではなく、配列はオプションであるべきではないと私は思います。必要なときに毎回開梱するのが面倒です。いずれの場合でも、これは機能するようです:var sprites = SKSpriteNode?[](count:64、RepeatedValue:nil); if var unwrappedSprite = sprites {unwrappedSprite [0] = spritePawn; }
アンリラピエール2014年

Swift 3および4の構文が変更されました。以下の他の回答をご覧ください
Crashalot

61

今のところできる最善の方法は、初期カウントがnilを繰り返す配列を作成することです。

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

次に、必要な値を入力できます。


スウィフト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
これに注意してください。そのオブジェクトの同じインスタンスで配列を埋めます(個別のインスタンスが期待される場合があります)
Andy Hin

OK、しかしそれはOPの質問を解決します、そしてまた、配列が同じインスタンスオブジェクトで満たされていることを知っているなら、あなたはそれに対処しなければならず、問題はありません。
Carlos.V 2017

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. (崩壊オブジェクトの配列にoptionalsの配列を変換[SKSpriteNode?][SKSpriteNode]):

    let flatSprites = optionalSprites.flatMap { $0 }

    count結果として生じるのは、flatSprites内のオブジェクトの数に依存しますoptionalSprites:空のoptionalsが無視されます、つまりはスキップ。


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!)
}

準拠を実装するためのその他のプロトコルについては、配列のドキュメントRelationshipsにスクロール)を参照してください。


-3

あなたができることの一つは、辞書を作成することでしょう。64個の要素を探していることを考えると、少しずさんに見えるかもしれませんが、それで仕事は完了です。それを行うための「推奨される方法」であるかどうかはわかりませんが、構造体の配列を使用して私にとってはうまくいきました。

var tasks = [0:[forTasks](),1:[forTasks](),2:[forTasks](),3:[forTasks](),4:[forTasks](),5:[forTasks](),6:[forTasks]()]

2
それはアレイよりどのように優れていますか?私にとっては、問題を解決することさえできないハックです。tasks[65] = fooこの場合と質問の配列の場合の両方で非常にうまく実行できます。
LaX、2017
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.