Swift 结构体内存泄漏

47
我们正在尝试在可以使用的情况下使用Swift结构体。我们还使用RxSwift,它具有需要接受闭包的方法。当我们有一个创建引用了self的闭包的结构体时,就会创建一个强引用循环
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(),就没有问题。

Variable是一个类。因此,我可以使用Xcode的Instruments' Allocations并过滤Variable<String>来查看这个内存问题。

按变量过滤

输入图片说明

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

someVariable.subscribeNext { [weak self] person in

编译器错误是"weak cannot be applied to non-class type"。

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

如何解决这个内存问题,同时保持DoesItLeak为结构体?

谢谢你的帮助。


2
坚持使用结构体可能会违反“在类和结构体之间进行选择”指南:“当您分配或传递该结构体的实例时,合理地期望封装的值将被复制而不是引用。”-- https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ClassesAndStructures.html - finneycanhelp
3
我认为你没有内存泄漏——至少不是由闭包使用self引用结构体所引起的。据我所知,当你在闭包中使用self来引用结构体时,那只是对结构体进行了复制——它并没有创建对结构体的引用。强引用循环必须仅涉及类和闭包,而不涉及结构体。 - Marc Khadpe
4
Variable 类型是一个类还是结构体? - Marc Khadpe
3
好的。看起来你想要一种指定结构体中 someVariable 属性被弱引用捕获的方法,但我不知道有这样的方法。 - Marc Khadpe
6
无法完成。在C或C++中应如何实现?我们希望DoesItLeak是一个值类型,这意味着它只能存在于堆栈帧上。那么我们将什么作为闭包的“self”参数传递?我们会传递指向堆栈帧上对象的指针,然后希望这个堆栈帧在闭包执行时仍然存在。你实际上可以在Swift中这样做,但这是完全不安全的。我们希望DoesItLeak是一个值类型,但一旦它被传递给引用计数对象的闭包,它就必须成为引用计数的值类型。因此,DoesItLeak不能是一个结构体。 - Darren
显示剩余9条评论
4个回答

18

正如Darren在评论中所说:" DoesItLeak不能是结构体"我们不能将DoesItLeak设置为结构体并安全地解决强引用循环问题。

值类型,例如结构体,存在于堆栈帧上。闭包和类是引用类型。

正如闭包的强引用循环部分所述:

  

这种强引用循环发生是因为闭包与类一样,都是引用类型。

由于结构体具有Variableclass ,并且使用subscribeNext将引用self的闭包存储到 Variable 类中,它创建了强引用循环。请参见苹果文档中的解决闭包的强引用循环


3
像结构体这样的值类型存在于堆栈帧上。但这并不是普遍适用的。被逃逸闭包引用的值类型将需要移动到堆上。堆和栈应该对Swift程序员完全抽象化。 - Berik
1
因此,这个答案基本上是在说:"你必须使用类来消除内存泄漏"? - Sajjon
@Sajjon 不,你可以使用这种方法 https://dev59.com/01sW5IYBdhLWcg3w8rD2#59075355 - Nikaaner

14

针对仍然遇到这个问题的人:

  1. [weak self] 不可能,因为Struct是一个值类型,而不是一个引用类型,所以没有指针。

  2. 这里泄漏的主要问题是您尝试在完成块内访问Struct属性 self.someState = something,这将在赋值时创建一个新的结构副本。

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


7
你可以通过在闭包中创建一个对该对象的弱引用来解决这个问题。
下面是没有内存泄漏的示例:
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)
    }
}

1
现在禁止在可写上下文中通过逃逸闭包捕获自身的模式。Swift编译器将会报错“闭包不能隐式捕获一个可变的self参数”。如果上下文是只读的,self的值可以被复制或共享,在任何情况下都不会有引用循环。

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