Swift可选数组属性是不可变的吗?

8

我正在构建一个布尔型数组,用于存储UICollectionView中各个部分的状态。这是一个作为UIViewController属性存储的变量:

var _weekSelections : Array<Bool>!

然后,在由loadView()调用的函数中,我构建数组并为第一个索引赋值:
_weekSelections = Array<Bool>(count:_weekCount, repeatedValue:false)
_weekSelections[0] = true

索引0位置的值仍然是false! 数组已经构建好,并且有多个元素,但是我对任何索引的赋值都不会影响该索引存储的值,即使我在代码的下一行检查该值。我知道如果执行可能会改变其长度的操作,则Swift会复制一个数组,但我不认为这是需要复制的情况。唯一能让任何值发生更改的方法是手动创建副本,如下所示:

var copy = _weekSelections
copy[0] = true
_weekSelections = copy

我有点不确定是否漏掉了什么明显的东西,还是可能存在一个奇怪的 bug?

1
有趣。我可以重现这个问题,但如果我将代码从loadView()移动到init()调用中,它就可以工作了。仍在努力弄清楚发生了什么。 - Matt Gibson
它发生在所有类型的数组中,至少在全局存储和loadView()或其子类中。我在文档中找不到任何指定全局存储与本地存储数组有不同修改行为的内容,如果你所说的是真的,那么这一定是另外一种情况。 - Eric Gratta
1
这里有个奇怪的问题:我可以在playground中创建一个最小的Swift类,你的代码可以正常工作。然后如果我说它继承自NSObject,我就可以重现你的问题。这是唯一的变化。我已经将复制粘贴的代码缩减到大约12行...... 复制品:http://pastebin.com/2Xc1qrHQ - Matt Gibson
顺便问一下:你所说的“全局存储”是什么意思,确切地说?是在类之外完全的全局范围吗?(我可以通过类的存储属性来复现这个问题...) - Matt Gibson
不仅仅是数组 - 我发现当类扩展NSObject时,所有可选值类型都无法正常工作。即使在方法中检查它时,实例变量将被初始化为nil,但实际上并非如此。有时,当您尝试解包非空可选项时,它会抱怨在可选项中找到了一个nil值。 - Mark Horgan
显示剩余2条评论
2个回答

2
为了将我的代码放在SO而不是Pastebin上,这里是我的观察。当使用可选数组在从Objective C类派生的Swift类中时,这似乎是某种bug或意外行为。如果使用纯Swift类,则可以按预期工作。
class Foo {
    var weekSelections: Array<Bool>!
    func test() {
        weekSelections = Array<Bool>(count: 10, repeatedValue: false)
        weekSelections[0] = true;
        println(weekSelections[0]) // Prints "true"
    }
}

var foo = Foo()
foo.test()

然而,如果您从NSObject派生出Foo:
import Foundation

class Foo : NSObject { // This derivation is the only difference from the code above
    var weekSelections: Array<Bool>!
    func test() {
        weekSelections = Array<Bool>(count: 10, repeatedValue: false)
        weekSelections[0] = true;
        println(weekSelections[0]) // Prints "false"
    }
}

var foo = Foo()
foo.test()

即使在这种情况下,如果您在初始化器中进行weekSelections的初始化,它也可以正常工作:
class Foo : NSObject {
    var weekSelections: Array<Bool>!
    init() {
        weekSelections = Array<Bool>(count: 10, repeatedValue: false)
        weekSelections[0] = true;
        println(weekSelections[0]) // Prints "true"
    }
}

var foo = Foo()

个人而言,我认为这是一个bug。我在任何文档中都找不到任何解释从NSObject派生时行为差异的内容。

我也没有看到任何说明可选数组属性将是不可变的。考虑到“不可变”数组实际上在Swift中是可变的,这将特别奇怪,例如:

// Use "let" to declare an "immutable" array
let weekSelections = Array<Bool>(count: 10, repeatedValue: false)
weekSelections[0] = true;
println(weekSelections[0]); // Prints "true"; arrays are never really "immutable" in Swift

... 目前文档中所述的方法可以正常运行,即使看起来有点奇怪。

个人建议您使用任何解决方法,并向苹果提出错误报告,以获取他们能提供的帮助。


0

这不是一个解释,而是一个解决方法。问题不在于count:repeatedValue初始化器,而在于数组被分配给可选变量。据我所知,可选数组只能使用访问器方法而不能使用变异器方法——实际上它们是不可变的。在尝试更改其内容之前,暂时将_weekSelections分配给非可选变量(并在完成后重新分配给_weekSelections)将起作用。请注意,这似乎会在分配时创建一个新数组(具有相同的元素),因此如果数组非常大,则可能需要考虑内存问题。当然,一开始就使用非可选变量也可以。

至于为什么可选数组不可变,可能是一个错误,或者可能有一些我无法理解的玄学原因。还有其他人有一个合理的理论吗?


这是一个关于可选变量的问题,描述的解决方法很有信息价值,但我已经在问题中包含了。至于使用非可选变量,我可以这样做,但那样我就必须静态地或在对象初始化期间对其进行初始化,而在计算带有加载数据的_weekCount之前,我无法确定它的大小。我更愿意等待并使用count:repeatedValue:初始化器。 - Eric Gratta
@EricGratta:然后编辑问题的主题,以表明您理解问题不在初始化程序中。您肯定给人留下了这样的印象。 - Alvin Thompson
直到刚才我才知道这不是一个问题! - Eric Gratta
我不能确定那是否是确切的原因。OP上的评论表明扩展NSObject也与这个问题有关。 - Eric Gratta
@EricGratta:我不知道怎么做。数组要么扩展NSObject,要么不扩展。我知道删除所有导入不会改变任何东西。 - Alvin Thompson
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接