快速关闭:必须捕获列表是否彻底?

问题描述 投票:1回答:1

假设我有一个像这样的Swift类:

@objc final MyClass : NSObject
{
    let classPropertyString = "A class property"


    func doStuff() 
    {
        let localString = "An object local to this function"

        DispatchQueue.global(qos: .userInitiated).async { [classPropertyString] in
             // Do things with 'classPropertyString' and 'localString'
        }
    }
}

我的问题是:当我写一个捕获列表时,我是否负责EXHAUSTIVELY列出我希望闭包持有强引用的所有内容?

换句话说,如果我从我的捕获列表中省略localString(正如我在这里所做的那样),闭包是否仍会自动捕获对它的强引用或者我是在一个糟糕的时间?

swift memory memory-management closures
1个回答
2
投票

你的问题有几个小怪癖,这使得回答很清楚,但我认为我理解潜在的问题,简短的回答是“不”。但你的榜样是不可能的,所以答案是“这是不可能的”。如果可能的话,没有强有力的参考(也不需要一个),所以问题仍然是“这是不可能的”。即便如此,让我们来看看这里发生的事情。

首先,closure不能引用localString,除非它在doStuff()的评论中以某种方式重新分配。 closure被指定为localString不在范围内的级别。闭包只能捕获分配时的范围内的变量,而不是在调用它们时。但是,在编辑之前,让我们回到这个问题的原始版本。那个版本确实有你描述的情况:

@objc final myClass : NSObject
{
    let classPropertyString = "A class property"


    func doStuff() 
    {
        let localString = "An object local to this function"

        DispatchQueue.global(qos: .userInitiated).async { [classPropertyString] in // (1)
             // Do things with 'classPropertyString' and 'localString'
        }
        // (2)
    }
}

这里没有问题。 classPropertyString被复制到闭包中,避免任何保留循环。 localString由闭包引用,因此只要闭包存在它就会被保留。

因为您在捕获列表中列出了classPropertyString,所以它在第(1)点进行求值并复制到闭包中。因为您隐式捕获了localString,所以它被视为参考。请参阅Swift编程语言参考中的Capture Lists,了解一些在不同情况下如何工作的优秀示例。

在任何情况下(*),Swift都不会允许你在封闭中使用的东西的底层存储在你的背后消失。这就是为什么典型的担忧是过度保留(内存泄漏)而不是悬挂引用(崩溃)。

(*)“在任何情况下”这里都是谎言。 Swift有几种方式允许它,但几乎所有方法都涉及“不安全”,这是你的警告。主要的例外是unowned,当然还有涉及!类型的任何事情。 Swift通常不是线程安全的,所以你需要小心......

关于线程安全的最后一个评论是隐式和显式捕获之间的细微区别真正重要的地方。考虑这种情况,您可以修改两个队列上隐式捕获的值:

func doStuff() -> String
{
    var localString = "An object local to this function"

    DispatchQueue.global(qos: .userInitiated).async {
         localString = "something else"
         callFunction(localString)
    }

    localString = "even more changes"
    return localString
}

在那种情况下会发生什么?好悲伤,永远不要那样做。我相信它是未定义的行为,localString可能是任何包括损坏的内存,至少在最一般的情况下(它可能被定义为调用.async的行为;我不确定)。但是不要这样做。

但是对于正常情况,没有理由明确捕获局部变量。 (我有时希望Swift采用C ++方式并说它是必需的,但事实并非如此。)

好的,隐式和显式的另一种方式是不同的,可能会带回家如何工作。考虑像这样的有状态闭包(我经常构建这些):

func incrementor() -> () -> Int {
    var n = 0
    return {
        n += 1
        return n
    }
}

let inc = incrementor()
inc()  // 1
inc()  // 2
inc()  // 3

let inc2 = incrementor()
inc2() // 1

看看闭包是如何捕获局部变量n的,并且可以在它超出范围后进行修改。并了解inc2如何拥有自己的局部变量版本。现在尝试使用显式捕获。

func incrementor() -> () -> Int {
    var n = 0
    return { [n] in // <---- add [n]
        n += 1      // Left side of mutating operator isn't mutable: 'n' is an immutable capture
        return n
    }
}

显式捕获是副本,它们是不可变的。隐式捕获是引用,因此具有与它们引用的内容相同的可变性。

© www.soinside.com 2019 - 2024. All rights reserved.