在Swift中将变量分配给下划线

5
在研究了有关Swift中下划线的stackoverflow之后,我明白下划线代表a)忽略此函数和b)使用该方法时可以省略参数名。但我不明白的是,如果我们将变量赋值给下划线会发生什么?通过将下划线分配给变量,这会消除Xcode编译器警告“初始化程序的结果未使用”,但如果您没有以任何方式使用此变量,则不会显示警告。
这个变量是否仍然被创建并存储在内存中,还是完全被编译器忽略,就像一行被注释掉的代码一样?
例如:
func test_ToDoItem_TakesTitle(){

        let firstToDoItem = ToDoItem(title: "First Instance Title")

        _ = ToDoItem(title: "First ToDoItem instance")

        XCTAssertEqual(firstToDoItem.title, "First Instance Title")

    }

下面这行代码是被创建并存储在内存中,还是被忽略了呢:
 _ = ToDoItem(title: "First ToDoItem instance")

希望这个问题有意义,因为我想了解每个内存块在Xcode中的使用情况。


1
该函数仍然会执行,但由于没有引用,内存将会被ARC立即回收。这是有关此行为的提案 - Code Different
@CodeDifferent,显然不是立即执行的。它似乎要等到项目超出范围才会执行。请参见我的答案以获取演示。 - vacawama
哇哦,@CodeDifferent你提供的链接叫做"proposal",这是那个开源的GitHub仓库吗?人们在那里提出对Swift语言的建议,然后苹果公司会阅读这些建议吗?如果是这样的话,我曾经在播客中听说过这个,但只是想确认一下。 - Laurence Wingo
1
确实。那是人们提出改变Swift语言的建议的地方。讨论在邮件列表上进行,您可以在GitHub存储库中看到最终结果的反映。 - Code Different
3个回答

4
这个变量是否仍然被创建并存储在内存中,还是像被注释掉的代码一样被编译器忽略了?这完全取决于ToDoItem类的实现。
乍一看,这行代码似乎是多余的。但是想想看,如果在ToDoItem的初始化程序中有一些你想要执行的东西呢?唯一的方法是创建一个新的ToDoItem实例,但是只写ToDoItem(...)会导致警告。这就是为什么使用通配符模式来消除警告的原因。
这在CoreData中特别有用。有时候,您只想保存一个新的托管对象,而不改变任何属性。您可以这样写:
_ = MyEntity(entity: ..., insertInto: ...)

因为你只是想要初始化程序的副作用-保存一个新的托管对象。

如果ToDoItem的初始化器将self分配给其他内容,那么是的,ToDoItem会在内存中。例如,

SomeClass.someStaticProperty = self

如果你不这样做,ToDoItem将被解构化。

非常好的例子,因为您用Core Data来描述它,而我将来也想学习它。不过有一个问题?"SomeClass.someStaticProperty = self"会创建一个保留循环吗?还是只有当一个对象引用完全不同的对象时才会创建保留循环?@Sweeper - Laurence Wingo
1
@user6510422 不会有保留循环,因为 someStaticProperty 是静态的。您可以将 SomeClass.someStaticProperty 设置为 nil 以取消初始化 ToDoItem只有当一个对象引用完全不同的对象时才会创建保留循环吗? 不一定。我认为如果一个对象持有对自身的引用,也会创建保留循环,更不用说有许多种保留循环了。 - Sweeper

2

正如vacawama在他的回答中提到的那样,对象在创建后会在对象超出作用域时被销毁。

但是这只适用于在playground执行的代码。当在模拟器或设备上的应用程序中执行时,对象会立即被销毁。 请注意!

我使用了vacawama回答中的以下代码进行测试:

class ToDoItem {
    var title = ""

    init(title: String) {
        self.title = title
    }

    deinit {
        print("deinit \(title)")
    }
}

func test() {
    print("test")

    _ = ToDoItem(title: "First")
    _ = ToDoItem(title: "Second")

    print("end test")
}

func callTest() {
    print("calling test()...")
    test()
    print("back from test()")
}

callTest()

很抱歉我还不能对其他答案进行评论,但我认为这是一个重要的分享。


1
我还不知道如何抑制“未使用”警告,如果我需要该对象在作用域内保持活动状态的话,因为在这种情况下,我被迫将其分配给let temp = ... - Dmitry Plotnikov
在 Swift 8.2 和 Swift 3 中,这似乎是正确的答案。 - user3763801
谢谢Dmitry。我知道你不能对我的答案发表评论,但如果有人能这样做,我会很感激。不过,遗憾的是,有人在没有留下任何评论的情况下投了反对票,所以我才知道。既然我的答案被接受了,我会注意你的结果并链接到你的答案。 - vacawama

1
这个测试展示了当对象超出作用域时,对象被创建并销毁的过程:
class ToDoItem {
    var title = ""

    init(title: String) {
        self.title = title
    }

    deinit {
        print("deinit \(title)")
    }
}

func test() {
    print("test")

    _ = ToDoItem(title: "First")
    _ = ToDoItem(title: "Second")

    print("end test")
}

func callTest() {
    print("calling test()...")
    test()
    print("back from test()")
}

callTest()

输出:

calling test()...
test
end test
deinit Second
deinit First
back from test()

扩展测试:
func test() {
    print("test")

    _ = ToDoItem(title: "Item 1")
    for i in 2...4 {
        _ = ToDoItem(title: "Item \(i)")
    }
    _ = ToDoItem(title: "Item 5")

    print("end test")
}
calling test()...
test
deinit Item 2
deinit Item 3
deinit Item 4
end test
deinit Item 5
deinit Item 1
back from test()
请注意,第2、3和4个项目在每次循环结束时超出范围时被取消初始化。项目1和5在test()完成时超出范围时被取消初始化。

应用程序内部

正如Dmitry Plotnikov在他的答案中指出的那样,上述内容仅适用于Swift Playground。在应用程序中,结果为:

calling test()...
test
deinit Item 1
deinit Item 2
deinit Item 3
deinit Item 4
deinit Item 5
end test
back from test()

这告诉我们:

  1. 对象被创建。
  2. 它们立即被释放。

非常感谢,不确定您是否进行了编辑,但第一个示例肯定是有意义的。 - Laurence Wingo
哇,这确实更加强调了作用域中的内存。 - Laurence Wingo
对于遇到此问题并希望消除未使用变量警告的人,您可以在作用域结束时将变量分配给下划线。"_ = toDoItem" - MurderDev

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