47

We're trying to use Swift structs where we can. We are also using RxSwift which has methods which take closures. When we have a struct that creates a closure that refers to self, that creates a 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)
    }
}

How do I know this? If I create 100,000 DoesItLeak objects and call someFoo() on each of them, I believe I have 100,000 objects with strong reference cycles. In other words, when I get rid of the DoesItLeak array containing those objects, the objects stay in memory. If I do not call someFoo(), there is no problem.

Variable is a class. So, I can see this memory issue by using xcode's Instruments' Allocations and filtering in Variable< String >

Filtering By Variable

enter image description here

If I try to use [weak self] such as in the following, I get a compiler error:

someVariable.subscribeNext { [weak self] person in

The compiler error is "weak cannot be applied to non-class type"

In real/non-example code, we access methods and variables via self and it's a memory issue.

How can I resolve this memory issue while keeping the DoesItLeak a struct?

Thanks for your help.

finneycanhelp
  • 9,018
  • 12
  • 53
  • 77
  • 2
    It's possible that insisting on using a struct is a violation of this **Choosing Between Classes and Structures** guideline: "It is reasonable to expect that the encapsulated values will be copied rather than referenced when you assign or pass around an instance of that structure." -- https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ClassesAndStructures.html – finneycanhelp Dec 21 '15 at 23:50
  • 3
    I don't think you have a memory leak--at least not one caused by the closure using self to refer to a struct. From what I can tell, when you use self to refer to a struct in a closure, that merely makes a copy of the struct-- it doesn't create a reference to the struct. A strong reference cycle would have to involve only classes and closures, not structs. – Marc Khadpe Dec 22 '15 at 00:24
  • 4
    Is the `Variable` type a class or struct? – Marc Khadpe Dec 22 '15 at 00:30
  • Variable is a class. That was a good question, if I may say so. :) – finneycanhelp Dec 22 '15 at 00:32
  • 1
    Since the struct has the Variable class and the closure referring to self is stored into the Variable class using subscribeNext, it creates the strong reference cycle. – finneycanhelp Dec 22 '15 at 00:36
  • I just added to the question that "Variable is a class." Thank you, @MarcKhadpe :) – finneycanhelp Dec 22 '15 at 00:42
  • I don't understand the intention of the `self.someState = "something"` statement. Since the struct is copied, this would seem to accomplish nothing. The original struct's property will remain unchanged. Or am I missing something? – Marc Khadpe Dec 22 '15 at 00:45
  • Good question. The self.someState is being done to demonstrate a simple use of **self**. The most common cases of using self for us is either referencing a method use as self.someMethod() or changing state. A more elaborate example is in the NotesViewModel struct. https://bitbucket.org/finneycanhelp/highfivetrack/src/622b03134de653bfe7b8e9d27f877143bcfedef2/HighFiveTrack/NotesViewModel.swift?at=remove-memory-leaks&fileviewer=file-view-default – finneycanhelp Dec 22 '15 at 00:50
  • 3
    Okay. It would seem you would want a way to specify that the `someVariable` property of the struct is captured weakly, but I'm not aware of a way to do that. – Marc Khadpe Dec 22 '15 at 01:18
  • 6
    Can't be done. How would this be implemented in C or C++? We want `DoesItLeak` to be a value type which means it may exist only on a stack frame. So what do we pass as `self` to the closure? A pointer to an object on the stack frame, and then we hope like hell that the stack frame is still around when the closure executes. You can actually do that in Swift, but it's completely unsafe. We'd like `DoesItLeak` to be a value type, but as soon as it's passed to a closure on a reference-counted object then it must also be reference-counted value type. Therefore, `DoesItLeak` can't be a struct. – Darren Dec 22 '15 at 01:39
  • Sounds about right @Darren. How about making that a SO answer so I can mark it as the answer? – finneycanhelp Dec 22 '15 at 01:47
  • Have you been shown the syntax for [unowned self] ? http://stackoverflow.com/questions/24320347/shall-we-always-use-unowned-self-inside-closure-in-swift – Adam Campbell Dec 22 '15 at 05:54
  • 1
    Yes, thank you @AdamCampbell. That also gives a compiler error. – finneycanhelp Dec 22 '15 at 11:26
  • @Marc Khadpe you can create weak reference to object like `weak let weakObject = strongObject`, look at https://stackoverflow.com/a/59075355/9024807 – Nikaaner Nov 27 '19 at 17:23

4 Answers4

18

As Darren put it in the comments: "DoesItLeak can't be a struct" We cannot have the DoesItLeak be a struct and safely resolve the strong reference cycle issue.

Value types like structs exist on the stack frame. Closures and classes are reference types.

As the Strong Reference Cycles for Closures section puts it:

This strong reference cycle occurs because closures, like classes, are reference types.

Since the struct has the Variable class and the closure referring to self is stored into the Variable class using subscribeNext, it creates the strong reference cycle. See "Resolving Strong Reference Cycles for Closures" in Automatic Reference Counting Apple documentation.

Community
  • 1
  • 1
finneycanhelp
  • 9,018
  • 12
  • 53
  • 77
  • 3
    `Value types like structs exist on the stack frame.` This is not generally true. Value types that are referenced by escaping closures will have to be moved to the heap. Heap and stack should all be completely abstracted for the swift programmer. – Berik Jul 26 '18 at 14:05
  • 1
    So this answer basically says: "you MUST use class to rid the memory leak"? – Sajjon Sep 21 '18 at 20:02
  • @Sajjon No, you can use this approach https://stackoverflow.com/a/59075355/9024807 – Nikaaner Nov 27 '19 at 17:19
14

For anyone still facing this issue.

  1. [weak self] is not possible because Struct is value type and not a Reference type, so no pointer as such.

  2. The main issue of leak here is you are trying to access the Struct property self.someState = something inside the completion block which will basically create a new copy of your structure on assignment.

You should not access Struct Property inside completion block.

alekop
  • 2,838
  • 2
  • 29
  • 47
helloWorld
  • 229
  • 3
  • 7
6

You can solve the problem by creating a weak reference to the object which is captured by the closure.

Here is your example without memory leak:

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)
    }
}
Nikaaner
  • 1,022
  • 16
  • 19
1

The pattern of capturing self by an escaping closure in a writable context is now disallowed. The swift compiler will emit an error "Closure cannot implicitly capture a mutating self parameter". If the context is read-only, the value of self could be copied or shared and in either case there wouldn't be a reference cycle.

Berik
  • 7,816
  • 2
  • 32
  • 40