Swift:通过引用传递数组?


125

我想将Swift传递Array account.chatschatsViewController.chats引用(这样,当我向添加聊天时account.chatschatsViewController.chats仍指向account.chats)。即,当account.chats更改长度时,我不希望Swift将两个数组分开。


1
我最后只是制作account了一个全局变量并定义了as 的chats属性。ChatsViewControllervar chats: [Chat] { return account.chats }
ma11hew28

Answers:


72

Swift中的结构按值传递,但是您可以使用inout修饰符来修改数组(请参见下面的答案)。类通过引用传递。ArrayDictionary在Swift中实现为结构。


2
在Swift中,数组不是按值复制/传递的-与常规结构相比,它在Swift中的行为非常不同。见stackoverflow.com/questions/24450284/...

14
@Boon Array仍在语义上被复制/按值传递,但仅进行了优化以使用COW
2014年

4
而且我不建议使用of,NSArray因为NSArray和Swift数组在语义上有细微的差别(例如引用类型),这可能会导致您遇到更多错误。
2014年

1
这严重杀了我。我was着脑袋为什么事情不起作用。
khunshan

2
如果inout与Structs一起使用怎么办?
Alston

137

对于函数参数运算符,我们使用:

let(它是默认运算符,因此我们可以省略let)来使参数恒定(这意味着我们甚至不能修改本地副本);

var使其可变(我们可以在本地对其进行修改,但不会影响已传递给函数的外部变量);和

inout使其成为in-out参数。实际上,输入输出意味着通过引用而不是通过值传递变量。它不仅需要通过参考由也接受值,按引用传递,所以与通过它 - foo(&myVar)而不是只foo(myVar)

所以这样做:

var arr = [1, 2, 3]

func addItem(inout localArr: [Int]) {
    localArr.append(4)
}

addItem(&arr)    
println(arr) // it will print [1, 2, 3, 4]

确切地说,它不仅是引用,而且是外部变量的真实别名,因此您可以对任何变量类型(例如,使用整数(可以为其分配新值))进行这种技巧,尽管它可能不是好的做法,这样修改原始数据类型可能会造成混淆。


11
这并没有真正解释如何将数组用作引用实例(未复制)。
Matej Ukmar

2
我以为INOUT使用的getter和setter才能阵列复制到一个临时然后复位它的出路的功能-即它的副本
dumbledad

2
实际上,输入输出使用输入复制输出或按值调用结果。但是,作为优化,可以参考使用。“作为一种优化,当参数是存储在内存中物理地址上的值时,在函数体内部和外部都使用相同的存储位置。优化的行为称为按引用调用;它满足了所有的要求。复制入复制出模型,同时消除了复制的开销。”
Tod Cunningham

11
在Swift 3中,inout位置已更改,即func addItem(localArr: inout [Int])
elquimista '16

3
而且,var对于功能参数属性不再可用。
elquimista '16

23

给自己定义一个 BoxedArray<T>,它实现Array接口,但是将所有功能委托给存储的属性。因此

class BoxedArray<T> : MutableCollection, Reflectable, ... {
  var array : Array<T>

  // ...

  subscript (index: Int) -> T { 
    get { return array[index] }
    set(newValue) { array[index] = newValue }
  }
}

使用BoxedArray任何你想使用Array。将BoxedArray通过引用进行分配,它是一个类,因此通过Array接口对存储属性的更改将对所有引用可见。


有点吓人的解决方案:)-并不十分优雅-但似乎可行。
Matej Ukmar,2015年

好吧,这肯定比回到“使用NSArray”获得“按引用语义传递”更好!
GoZoner

13
我只是感觉将Array定义为struct而不是类是语言设计错误。
Matej Ukmar

我同意。如果您随后成为BUT String的子类型,Any那么也存在可憎之处。import FoundationStringAnyObject
GoZoner 2015年

19

对于Swift版本3-4(XCode 8-9),请使用

var arr = [1, 2, 3]

func addItem(_ localArr: inout [Int]) {
    localArr.append(4)
}

addItem(&arr)
print(arr)

3

就像是

var a : Int[] = []
func test(inout b : Int[]) {
    b += [1,2,3,4,5]
}
test(&a)
println(a)

???


3
我认为问题是在寻求一种方法,使两个不同对象的属性指向同一数组。如果是这样,Kaan的答案是正确的:必须将数组包装在一个类中,或者使用NSArray。
Wes Campaigne 2014年

1
是的,inout仅在功能体的整个生命周期内起作用(无关闭行为)
Christian Dietrich

次要问题:是func test(b: inout [Int])……也许这是一种古老的语法;我只是在2016年才加入Swift,这个答案是从2014年开始的,所以以前可能有所不同吗?
雷·托尔

2

另一种选择是让阵列的使用者根据需要向所有者询问。例如,类似以下内容的东西:

class Account {
    var chats : [String]!
    var chatsViewController : ChatsViewController!

    func InitViewController() {
        chatsViewController.getChats = { return self.chats }
    }

}

class ChatsViewController {
    var getChats: (() -> ([String]))!

    func doSomethingWithChats() {
        let chats = getChats()
        // use it as needed
    }
}

然后,您可以在Account类中随意修改数组。请注意,如果您还想从视图控制器类中修改数组,那么这对您没有帮助。


0

使用inout是一种解决方案,但由于数组是值类型,因此对我来说并不算很快。从风格上讲,我个人更喜欢返回变异的副本:

func doSomething(to arr: [Int]) -> [Int] {
    var arr = arr
    arr.append(3) // or likely some more complex operation
    return arr
}

var ids = [1, 2]
ids = doSomething(to: ids)
print(ids) // [1,2,3]

1
这种事情在性能上有缺点。它使用少一点的手机电池来修改原始阵列:)
David Rector

我谨不同意。在这种情况下,呼叫站点的可读性通常会胜过性能下降。从不可变的代码开始,然后通过使其变得可变来进行优化。在某些情况下,使用大型阵列是正确的,但在大多数应用中,这种情况占0.01%。
6

1
我不知道你不同意什么。复制阵列会降低性能,而性能较低的操作会占用更多的CPU,因此会消耗更多的电池。我认为您以为我是在说这是一个更好的解决方案,但事实并非如此。我确保人们拥有所有可用信息,以做出明智的选择。
David Rector

1
是的,您是对的,毫无疑问inout会更有效率。我以为您建议这样做总体上inout更好,因为它可以节省电池。我要说的是,此解决方案的可读性,不变性和线程安全性普遍更好,并且inout仅应在用例需要的情况下用作优化。
6

0

使用a NSMutableArray或aNSArray,它们是类

这样,您无需使用任何包装程序,就可以在桥接中使用构建

open class NSArray : NSObject, NSCopying, NSMutableCopying, NSSecureCoding, NSFastEnumeration
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.