Swift:如何通过引用传递数组?

151

我想通过引用传递我的 Swift Array account.chatschatsViewController.chats (这样当我向 account.chats 添加一个聊天时,chatsViewController.chats 仍然指向 account.chats)。也就是说,当 account.chats 的长度发生变化时,我不希望 Swift 将这两个数组分开。


3
我最终只是将account作为全局变量,并将ChatsViewControllerchats属性定义为:var chats: [Chat] { return account.chats } - ma11hew28
ma11hew28,你的解决方案值得成为一个答案。你还可以添加一个setter来使聊天更新。var chats:[Chat] {get {return account.chats} set {account.chats = newValue}} - jk7
9个回答

158
函数参数操作符:

使用let(默认的运算符,因此可以省略let)将参数设置为常量(这意味着我们甚至不能修改本地副本);

使用var将其设置为变量(我们可以在本地修改它,但不会影响传递给函数的外部变量);以及

使用inout将其设置为输入输出参数。 输入输出即实际上通过引用而非值传递变量。 它不仅需要通过引用接受值,还需要通过引用传递值,因此使用&进行传递 - foo(&myVar),而不是只使用foo(myVar)

因此,应按以下方式执行:
var arr = [1, 2, 3]

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

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

确切地说,它不仅仅是一个引用,而是外部变量的真实别名,因此您可以对任何变量类型使用这种技巧,例如整数(可以分配新值),尽管这可能不是一个好习惯并且修改原始数据类型可能会很困惑。



14
这并没有真正解释如何将数组用作被引用而不是复制的实例变量。 - Matej Ukmar
2
我认为inout使用getter和setter将数组复制到临时位置,然后在函数退出时重置它 - 即它会复制。 - dumbledad
3
实际上,in-out 使用的是 copy-in copy-out 或者 call by value result。然而,作为一种优化,它可能会使用按引用传递。"作为一种优化,当参数是存储在内存中物理地址的值时,在函数体内外都使用同一内存位置。这种优化行为被称为按引用传递;它满足了 copy-in copy-out 模型的所有要求,同时消除了复制的开销。" - Tod Cunningham
13
在Swift 3中,“inout”位置已更改,即“func addItem(localArr: inout [Int])”。意思是函数参数中的“inout”关键字位置发生了变化。 - elquimista
5
同时,var不再可用作函数参数属性。 - elquimista
显示剩余3条评论

88

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


2
在Swift中,数组不是通过值复制/传递的 - 它与常规结构具有非常不同的行为。请参见https://dev59.com/SGAf5IYBdhLWcg3woj3N#24454610 - Boon
16
@Boon Array 仍然是按值复制/传递的,但是经过优化以使用COW - eonil
4
我不建议使用 NSArray,因为它和 Swift 数组有微妙的语义差异(例如引用类型),这可能会导致更多的错误。 - eonil
2
这真的让我崩溃了。我一直在想为什么事情没有按照预期工作。 - khunshan
2
如果在结构体中使用 inout 会怎样? - Alston
哇,谢谢,这对我帮助很大。在 Swift 中,结构体是按值传递的。 - SSH This

29

定义一个名为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 }
  }
}

您可以在任何需要使用Array的地方使用BoxedArray。赋值BoxedArray将通过引用进行,因为它是一个类,因此通过Array接口对存储属性的更改将对所有引用可见。


有点可怕的解决方案 :) - 不是很优雅 - 但似乎可以工作。 - Matej Ukmar
嗯,这肯定比回退到“使用NSArray”来获得“按引用传递语义”要好! - GoZoner
18
我个人认为将Array定义为结构体而非类是一种语言设计上的错误。 - Matej Ukmar
1
我同意。还有一种可怕的情况,即StringAny的子类型,但如果你import Foundation,那么String就成为AnyObject的子类型。 - GoZoner

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

另一种选择是让数组的使用者根据需要向所有者请求。例如,可以采用以下方式:

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类内部尽情修改数组。请注意,如果您还想从视图控制器类中修改数组,则这并没有帮助到您。


3

类似以下这样的内容

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

???


4
我认为这个问题是在寻找一种方法,使得两个不同对象的属性指向同一个数组。如果是这样的话,Kaan 的回答是正确的:必须要将数组封装成一个类,或者使用 NSArray。 - Wes Campaigne
1
right, inout仅在函数体的生命周期内起作用(没有闭包行为) - Christian Dietrich
小问题:应该是 func test(b: inout [Int]) ... 或许这是旧语法;我只在2016年开始接触Swift,而这个答案是来自2014年,所以也许当时的情况不同了? - Ray Toal

1

使用 inout 是一种解决方案,但是对我来说并不感觉很符合 Swift 的风格,因为数组是值类型。在样式上,我个人更喜欢返回一个已经改变的副本:

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
我 respectfully disagree。在这种情况下,调用点的可读性通常比性能损失更重要。首先使用不可变代码,然后再通过使其可变来进行优化。对于大型数组的情况,你是正确的,但在大多数应用程序中,这只是一个0.01%的边缘情况。 - ToddH
1
我不知道你对什么持反对意见。复制数组会有性能损失,较低性能的操作会使用更多的CPU,因此会消耗更多的电池。我认为你假设我在说这是更好的解决方案,但事实并非如此。我只是确保人们拥有所有可用的信息,以便做出明智的选择。 - David Rector
1
是的,你说得对,毫无疑问inout更有效率。我以为你在暗示inout普遍更好,因为它可以节省电池电量。对此,我要说这种解决方案的可读性、不可变性和线程安全性是普遍更好的,而inout只应在那些使用情况需要时作为优化来使用。 - ToddH

1
使用NSMutableArray或者NSArray类,这样你就不需要实现任何包装器并且可以使用内置的桥接。
open class NSArray : NSObject, NSCopying, NSMutableCopying, NSSecureCoding, NSFastEnumeration

-1

基于 GoZoner 的 BoxedArray 答案,我创建了下面的类对我很有效。 我喜欢像我使用的其他语言那样传递数组引用的自由。

class MArray<T> : MutableCollection {
    
    var array : Array<T> = Array()
    var count: Int { return array.count }   // getter (without, index func below will be called repeatedly to determine)
    
    func add(_ value: T) {
        array.append(value)
    }

    // MutableCollection requires:
    subscript (index: Int) -> T {
      get { return array[index] }
      set(value) { array[index] = value }
    }
    var startIndex: Int {
        return 0
    }
    var endIndex: Int {
        return array.count
    }
    func index(after i: Int) -> Int {
        return i+1
    }
}

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