Swift 结构内存泄漏

Posted

技术标签:

【中文标题】Swift 结构内存泄漏【英文标题】:Swift Struct Memory Leak 【发布时间】:2016-03-28 03:33:27 【问题描述】:

我们正在尝试尽可能使用 Swift 结构。我们还使用了 RxSwift,它具有采用闭包的方法。当我们有一个结构体创建一个引用 self 的闭包时,它会创建一个strong reference cycle。

import Foundation
import RxSwift

struct DoesItLeak 

    var someState: String = "initial value"
    var someVariable: Variable<String> = Variable("some stuff")

    let bag = DisposeBag()

    mutating func someFoo() 

        someVariable.subscribeNext  person in

            self.someState = "something"
        
        .addDisposableTo(bag)
    

我怎么知道这个?如果我创建 100,000 个 DoesItLeak 对象并在每个对象上调用 someFoo(),我相信我有 100,000 个具有强引用周期的对象。换句话说,当我删除包含这些对象的DoesItLeak 数组时,这些对象会保留在内存中。如果我不调用 someFoo(),就没有问题。

变量是一个类。因此,我可以通过使用 xcode 的 Instruments' Allocations 和在 Variable

中进行过滤来查看此内存问题

如果我尝试使用如下所示的 [weak self],我会收到编译器错误:

someVariable.subscribeNext  [weak self] person in

编译错误是“weak cannot be applied to non-class type”

在真实/非示例代码中,我们通过 self 访问方法和变量,这是一个内存问题。

如何在保留 DoesItLeak 结构的同时解决此内存问题?

感谢您的帮助。

【问题讨论】:

坚持使用结构可能违反了这个在类和结构之间选择准则:“期望封装的值将被复制而不是被复制是合理的当您分配或传递该结构的实例时引用。” -- developer.apple.com/library/ios/documentation/Swift/Conceptual/… 我不认为你有内存泄漏——至少不是由使用 self 引用结构的闭包引起的。据我所知,当您使用 self 来引用闭包中的结构时,它只会复制该结构——它不会创建对该结构的引用。强引用循环必须只涉及类和闭包,而不涉及结构。 Variable 类型是类还是结构? 好的。您似乎想要一种方法来指定结构的 someVariable 属性被弱捕获,但我不知道有什么方法可以做到这一点。 无法完成。这将如何在 C 或 C++ 中实现?我们希望DoesItLeak 是一个值类型,这意味着它可能只存在于堆栈帧上。那么我们将什么作为self 传递给闭包呢?指向堆栈帧上的对象的指针,然后我们希望在闭包执行时堆栈帧仍然存在。你实际上可以在 Swift 中做到这一点,但它完全不安全。我们希望DoesItLeak 成为一个值类型,但是一旦它被传递给一个引用计数对象的闭包,那么它也必须是引用计数值类型。因此,DoesItLeak 不能是结构体。 【参考方案1】:

对于仍然面临此问题的任何人。

    [weak self] 是不可能的,因为 Struct 是 value type 而不是 Reference type,所以没有这样的指针。

    这里泄漏的主要问题是您试图访问完成块内的 Struct 属性self.someState = something,这基本上会在分配时创建结构的新副本。

您不应在完成块内访问 Struct 属性。

【讨论】:

【参考方案2】:

您可以通过创建对闭包捕获的对象的弱引用来解决此问题。

这是没有内存泄漏的示例:

import Foundation
import RxSwift

struct WithoutLeak 

    var someState: String = "initial value"
    var someVariable: Variable<String> = Variable("some stuff")

    let bag = DisposeBag()

    mutating func someFoo() 

        weak let weakSomeState = someState // <-- create a weak reference outside the closure

        someVariable.subscribeNext  person in

            weakSomeState = "something" // <-- use it in the closure
        
        .addDisposableTo(bag)
    

【讨论】:

【参考方案3】:

现在不允许在可写上下文中通过转义闭包来捕获 self 的模式。 swift 编译器将发出错误“闭包无法隐式捕获变异的自我参数”。如果上下文是只读的,则 self 的值可以被复制或共享,并且在任何一种情况下都不会有引用循环。

【讨论】:

【参考方案4】:

正如Darren 将它放在 cmets 中:“DoesItLeak can't be a struct”我们不能让DoesItLeak 成为结构并安全地解决强引用循环问题。

像结构这样的值类型存在于栈帧中。闭包和类是引用类型。

正如Strong Reference Cycles for Closures section 所说:

之所以会出现这种强引用循环,是因为闭包和类一样,都是引用类型。

由于该结构具有Variable ,并且引用self 的闭包使用Variable 存储到Variable 类中,因此它创建了强引用循环。请参阅Automatic Reference Counting Apple 文档中的“解决闭包的强引用循环”。

【讨论】:

Value types like structs exist on the stack frame. 这通常不是真的。转义闭包引用的值类型必须移到堆中。对于 swift 程序员来说,堆和栈都应该完全抽象出来。 所以这个答案基本上是说:“你必须使用类来消除内存泄漏”? @Sajjon 不,你可以使用这种方法***.com/a/59075355/9024807

以上是关于Swift 结构内存泄漏的主要内容,如果未能解决你的问题,请参考以下文章

iOS swift 3中的内存泄漏

内存泄漏,在do-catch块中。 iOS,Swift

为啥在 swift 中创建字符串时会出现内存泄漏?

Swift 3 CGContext 内存泄漏

如何在 swift 中避免由于 AFNetworking 导致的内存泄漏

Swift Quick 框架内存泄漏