0
点赞
收藏
分享

微信扫一扫

关于函数In-Out关键字

暮晨夜雪 2021-09-29 阅读 109

对于Swift的值类型而言,当程序进行变量赋值、参数传递时,函数内部对值类型变量进行了拷贝。也就是说在函数体内无论对参数进行哪些修改,参数的本身都不会产生变化。

1 传入值类型

假如你需要通过函数直接交换两个变量的值,通常会这么做:

func swapTwoInts(_ a: inout Int, _ b: inout Int) {
    let tempA = a
    a = b
    b = tempA
}

var a = 10, b = -10
swapTwoInts(&a, &b)

print(a,b) // a=-10 b=10

我们之所以需要添加inout关键字,就是因为传入函数的变量是值类型,那么假如我传入的变量是个引用类型不就好了吗?

2 传入引用类型

与值类型相同,给函数赋值时同样是传入参数的副本,不同的是值类型所传递的值本身的副本,而引用类型传递的是引用的副本。

class Data {
    var a = 0
    var b = 0
    init(a: Int, b: Int) {
        self.a = a
        self.b = b
    }
}

func swap(data: Data) {
    swapTwoInts(&data.a, &data.b)
}

var d = Data(a: 6, b: 9)
swap(data: d)

上面的代码同样可以交换a,b的值,因为对于Swift的引用类型,程序传递的是引用,因此在函数体中对参数的修改会同时影响参数本身。

也就是说,上面Data(a: 6, b: 9)对象的实例d和函数中形参data引用的是同一个对象。因此当函数执行,交换形参中a,b两个成员变量的值时,主程序中d对象对应的a,b也改变了。

说明单纯修改类的属性值时,函数不需要inout关键字修饰。

3 引申

继续思考下面这段代码:

func change(data: inout Data!) { // 这里隐式解包因为只有可选类型才能被设置为nil
    data = nil
}

此时主程序中的d实例被修改成nil,这一点可以通过print(Unmanaged.passUnretained(d).toOpaque())打印变量的内存地址来验证。说明修改类本身时,函数需要inout关键字修饰。

那么,假如程序做如下修改,会发生什么:

func change(data: inout Data!) { // 这里隐式解包因为只有可选类型才能被设置为nil
    let newData = data
    data = nil
    swapTwoInts(& newData.a, & newData.b) // 这里a,b的值交换成功
}

var d: Data? = Data(a: 6, b: 9)
change(data: &d)

上面的代码正常运行,在执行完data = nil后,swapTwoInts(& newData.a, & newData.b)依然可以正常执行,且函数执行完毕之后,主程序中的实例d也是nil

这里就产生了一个疑问,既然函数体中的参数是引用的副本,那么datad指向的是堆中同一个实例,然后let newData = data又给原有实例添加了一个新的引用。此时执行data = nil,那么不论是主程序中的d还是后创建的newData不都应该指向nil吗?为什么程序还能正常运行呢?

解决这个问题,就要讲到inout参数的传递过程,也就是说在函数体中的这个修改何时生效?我们用一个值类型属性观察者看一下(也可以用kvo观察引用类型)。

// ViewController中
var value: Int = 0 {
    didSet {
      print("value已修改") // 1
    }
}

override func viewDidLoad() {
  change(value: &value)
  print("\(value)") // 2
}

func change(value o: inout Int) {
    o = 10
    print(o) // 3
}

最终上面代码中的三个print 方法调用顺序为:3->1->2。也就是说change函数中执行o = 10时,其实主程序中的value没发生改变。而是在函数返回之后,才调用了value的属性观察者。

所以,可以总结以下三点:
1 函数被调用,参数值被拷贝
2 在函数体内,被拷贝的参数修改
3 函数返回时,被拷贝的参数值被赋值给原有的变量

现在可以解释为什么上面引用类型已经指向nil,程序还能正常运行。因为在函数返回前对参数的修改还没有生效。


总结:

上面的内容其实没什么营养,我最早看到这个问题的时候觉得很有意思,因为涉及了值类型和引用类型的内存分布、In-Out参数等几个容易出错的知识点。但是写到最后好像并没有把我最初想表达的东西表现出来。
其实此类问题可以引申的方向有很多,OC中block和基本数据类型、对象类型的关系,还有Swift中block的捕获等。

最后,本文的内容仅代表自己的理解和参考了一些网络资料,如有错误请及时留言。谢谢

举报

相关推荐

0 条评论