【问题标题】:How to create a fixed-size array of objects如何创建固定大小的对象数组
【发布时间】:2014-06-24 19:51:06
【问题描述】:

在 Swift 中,我正在尝试创建一个包含 64 个 SKSpriteNode 的数组。我想首先将其初始化为空,然后将 Sprites 放入前 16 个单元格和最后 16 个单元格(模拟国际象棋游戏)。

根据我在文档中的理解,我期望类似:

var sprites = SKSpriteNode()[64];

var sprites4 : SKSpriteNode[64];

但它不起作用。 在第二种情况下,我收到一条错误消息:“尚不支持固定长度数组”。那可以是真的吗?对我来说,这听起来像是一个基本功能。 我需要通过索引直接访问元素。

【问题讨论】:

    标签: swift ios8 xcode6


    【解决方案1】:

    尚不支持定长数组。这实际上是什么意思?并不是说你不能创建一个包含n 的数组很多东西——显然你可以只做let a = [ 1, 2, 3 ] 来得到一个包含三个Ints 的数组。这意味着数组大小不是您可以声明为类型信息的东西。

    如果你想要一个nils 的数组,你首先需要一个可选类型的数组——[SKSpriteNode?],而不是[SKSpriteNode]——如果你声明一个非可选类型的变量,无论它是一个数组或单个值,它不能是nil。 (另请注意,[SKSpriteNode?][SKSpriteNode]? 不同...您需要一个可选数组,而不是可选数组。)

    Swift 在设计上非常明确地要求初始化变量,因为关于未初始化引用内容的假设是 C(和其他一些语言)程序可能出现错误的方式之一。因此,您需要明确要求包含 64 个nils 的[SKSpriteNode?] 数组:

    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)。
    • 惊喜! Cmd 在你的编辑器/playground 中单击sprites 以查看其推断类型——它实际上是SKSpriteNode?[]?:可选精灵的可选数组。你不能下标一个可选的,所以你必须解开它......见编辑的答案。
    • 这确实很奇怪。正如您所提到的,我认为数组不应该是可选的,因为我们明确将其定义为 ?[] 而不是 ?[]?。每次我需要它时都不得不打开它,有点烦人。无论如何,这似乎有效: var sprites = SKSpriteNode?[](count: 64, repeatValue: nil);如果 var unwrappedSprite = sprites { unwrappedSprite[0] = spritePawn; }
    • Swift 3 和 4 的语法已更改,请参阅下面的其他答案
    【解决方案2】:

    您现在能做的最好的事情就是创建一个初始计数重复为零的数组:

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

    然后您可以填写您想要的任何值。


    Swift 3.0 中:

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

    【讨论】:

    • 有没有办法声明一个固定大小的数组?
    • @AlexanderSupertramp 不,没有办法声明数组的大小
    • @アレックス 没有办法为数组声明固定大小,但您当然可以创建自己的结构来包装强制固定大小的数组。
    【解决方案3】:

    这个问题已经回答了,但是在 Swift 4 的时候有一些额外的信息:

    为了性能,应该为数组预留内存,以防动态创建,比如用Array.append()添加元素。

    var array = [SKSpriteNode]()
    array.reserveCapacity(64)
    
    for _ in 0..<64 {
        array.append(SKSpriteNode())
    }
    

    如果您知道要添加到其中的最小元素数量,但不知道最大数量,您应该使用array.reserveCapacity(minimumCapacity: 64)

    【讨论】:

      【解决方案4】:

      声明一个空的 SKSpriteNode,因此不需要解包

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

      【讨论】:

      • 小心这个。它将用该对象的相同实例填充数组(人们可能期望不同的实例)
      • 好的,但它解决了 OP 问题,而且,知道数组中填充了相同的实例对象,那么您将不得不处理它,没有冒犯。
      【解决方案5】:

      斯威夫特 4

      您可以将其视为对象数组与引用数组。

      • [SKSpriteNode] 必须包含实际对象
      • [SKSpriteNode?] 可以包含对对象的引用,也可以包含 nil

      示例

      1. 创建一个具有 64 个默认值的数组 SKSpriteNode:

        var sprites = [SKSpriteNode](repeatElement(SKSpriteNode(texture: nil),
                                                   count: 64))
        
      2. 创建一个包含 64 个空槽的数组(又名 optionals):

        var optionalSprites = [SKSpriteNode?](repeatElement(nil,
                                              count: 64))
        
      3. 将可选数组转换为对象数组(将[SKSpriteNode?] 折叠为[SKSpriteNode]):

        let flatSprites = optionalSprites.flatMap { $0 }
        

        生成的flatSprites 中的count 取决于optionalSprites 中的对象数:空选项将被忽略,即跳过。

      【讨论】:

      • flatMap 已弃用,如果可能,应将其更新为 compactMap。 (我无法编辑此答案)
      【解决方案6】:

      目前,语义上最接近的应该是具有固定数量元素的元组。

      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)内存布局未定义。 (至少我不知道)

      【讨论】:

        【解决方案7】:

        如果你想要一个固定大小的数组,并用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!)
        }
        

        有关实现一致性的更多协议,请参阅Array documentation(滚动到关系)。

        【讨论】:

          【解决方案8】:

          您可以做的一件事是创建一个字典。考虑到您正在寻找 64 个元素,可能有点草率,但它可以完成工作。我不确定它是否是“首选方式”,但它使用结构数组对我有用。

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

          【讨论】:

          • 比数组好多少?对我来说,这是一个甚至无法解决问题的 hack:在这种情况下和问题中的数组的情况下,您都可以很好地执行 tasks[65] = foo
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2015-05-05
          • 1970-01-01
          • 2021-10-26
          • 1970-01-01
          • 1970-01-01
          • 2019-01-20
          • 1970-01-01
          相关资源
          最近更新 更多