iOS面试

关于In-Out关键字

2019-02-09  本文已影响6人  YYYYYY25

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

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

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关键字,就是因为传入函数的变量是值类型,那么假如我传入的变量是个引用类型不就好了吗?

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也改变了。

继续思考下面这段代码:

func change(data: inout Data) {
    data = Data(a: 0, b: 1)
}

假如在上述函数中给data添加inout关键字修饰,然后在函数体创建另一个Data对象,会发生什么?

答案显而易见,主程序中的d实例被改变了,这一点可以通过print(Unmanaged.passUnretained(d).toOpaque())打印变量的内存地址来验证。

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

func swap(data: inout Data!) {
    data = nil
    swapTwoInts(&data.a, &data.b)
}

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

答案是在swap(data: &d)这一行报错。因为主程序中的对象也会被修改为nil,导致程序运行时崩溃。

最后,如果你用过早期的Swift版本,可能会见过变量形参这个东西,也就是在形参前使用var关键字修饰。那么如果代码改成下面这样,又会是什么结果呢?

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

这段代码的运行是没有任何问题的,函数内部的data被赋值为nil,等于切断了data对堆中内容的指向,不形象外部变量。

但是这种写法很容易误解,所以在之后的Swift版本中,移除了变量形参这个概念,其实他实际只是在函数内部创建一个新的变量而已。

// 变量形参的实质
func swap(data: Data) {
    var newData = data
    data = nil
    swapTwoInts(& newData.a, & newData.b)
}

你可能会疑问,引用类型传递到函数内,形参data和外部变量不是指向同一个对象吗?那么data=nil时为什么外部变量不发生变化呢?
答:其实,方法内部data=nil只是将data变量的指针又原来的内存地址指向了nil,跟外部变量根本没关系。如果想要影响,则要添加inout关键字。

总结:

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

上一篇下一篇

猜你喜欢

热点阅读