0

I am creating an object where I call it to run a function in a Boolean loop controlled by a button. Once the object is created I call two functions of it so that I may get a return value and pass them to the next function. Once the loop completes the object is still referenced so that it is not set to zero by ARC and thus I created a memory leak. I am trying to wrap my head around using the weak reference type but when I attempt the object continues to be referenced. I am going to paste the code that leaks without any alterations to the variable types or creating an optional operators so that you can see where I started from.

self.buttonpress = true

DispatchQueue.global().async{
    while buttonpress == true{
        let varfind = find()
        let b = varfind.build()
        let yes = varfind.isInside(index: b,xaxis: locationViewModel.userXaxis, yaxis: locationViewModel.userYaxis)
        self.final = String(Int(locationViewModel.userZaxis)-yes)
    }
}

The question is, how do i have what is inside the while loop execute and dereference the object and its pointers so that it does not increasingly take up memory and crash using weak variable types?

I understand that I need to use let varfind : find? = find(), then add a deinit inside the find class in order to set everything to nil however, I think there is a misunderstanding when it comes to settings the params inside var yes to nil as index for example is a [String] so it can only accept an empty array?

0

2 Answers 2

1

A few observations:

  1. Before we get to the objects being created inside the while loop, we should recognize that you have strong reference cycle between the object that dispatched this code to the global queue and code being dispatched. One would use [weak self] pattern in the closure to break that cycle. You can either use the pattern suggested by Duncan, or just use nil-coalescing operator:

    DispatchQueue.global().async { [weak self] in
        while self?.buttonPressed == true {
            let foo = Foo()
            foo.doSomething()
        }
    }
    
  2. Now, let us turn to the objects created in the while loop. You hypothesized:

    Once the loop completes the object is still referenced

    That is extremely unlikely. And even if it was the issue, the fact that it is a local variable within while loop is definitely not the problem. Your objects might not get released if you accidentally introduced a strong reference cycle somewhere (see point 3, below). Or it might not be your class at all, but rather autorelease objects that your functions (or API that your functions call) introduced (see point 4, below). It could be cacheing provided by the underlying API. It could be any of a number of things.

  3. But let say, for a second, that you wanted to confirm whether your own objects were getting released or not. The way you determine whether there is a strong reference cycle is the “Debug Memory Graph”. To illustrate the technique, let me create an example that introduces a strong reference cycle:

    DispatchQueue.global().async { [weak self] in
        while self?.buttonPressed == true {
            let foo = Foo()
            foo.bar(with: foo)
        }
    }
    

    Where

    class Foo {
        var foo: Foo?                       // bad: this introduces strong reference cycle
    
        func bar(with foo: Foo) {
            self.foo = foo
        }
    }
    

    Then I can look at the memory graph by running my app tapping on the “Debug Memory Graph” button in the bottom bar:

    enter image description here

    Look for objects in the panel on the left which you believe should no longer be in memory. Even better, see if any have the runtime error triangle next to them.

    In this example, I can see many Foo instances with the runtime error triangle next to them. When I tap on one, I can visually see the strong reference cycle in the main panel. And, because I turned on “Product” » “Scheme” » “Edit Scheme...” » “Run” » “Diagnostics” » “Malloc Stack Logging”, I can even see where the strong references were established in the right panel.

    So, run your app, use debug memory graph, and see if your instances are there in the panel on the left, and if so, how many. Is it really one instance not getting released for every iteration of the while loop? Is it that the while loop isn't stopping (see point 1, above)? Or are you not seeing your objects at all, but rather there is something else that is consuming the memory (see following points)?

  4. Having shown you how to examine your memory graph to confirm whether objects are not getting released (e.g. by strong reference cycles), there is a good chance that this is not the problem at all. (In the absence of a MCVE, we cannot be certain.)

    For example, another candidate problem might be autorelease objects. For example, consider:

    DispatchQueue.global().async { [weak self] in
        while self?.buttonPressed == true {
            let foo = Foo()
            foo.bar()
        }
    }
    

    where

    class Foo {
        var foo: Foo?
    
        func bar() {
            let string = NSString(format: "%d", Int.random(in: 0..<1_000_000))
            print(string)
        }
    }
    

    When you run it, it will yield a memory graph like so:

    enter image description here

    The problem here is not Foo, but rather that the bar method is creating an autorelease object. This is solved by putting an autoreleasepool inside the while loop:

    DispatchQueue.global().async { [weak self] in
        while self?.buttonPressed == true {
            autoreleasepool {
                let foo = Foo()
                foo.bar()
            }
        }
    }
    

    It is worth noting that this autoreleasepool issue only applies if you, or the API that you are calling, are actually creating autorelease objects. In Swift, this is increasingly uncommon, but sometimes we are calling third party libraries that use autorelease objects under-the-hood.

  5. There are lots of other patterns that can cause unbridled memory growth. For example, there might be URLSession instances that are never invalidated. Or perhaps there is some cacheing that is taking place (either explicitly by you or buried within whatever API you are calling). Or perhaps you have some code with some CoreFoundation leaks or other unmanaged objects.

    It is impossible to say what the problem is without a reproducible example of the problem. The Instruments’ “Allocations” tool is an excellent, if exceedingly complicated, method to identify what precisely is getting leaked or abandoned, but it will likely just be easier if you could show us a reproducible example of the problem and we can point you in the right direction.

Bottom line, the problem is not caused by the local variable within the while loop. Either you have some strong reference cycle or other leaked, abandoned, or cached memory. Using “Debug Memory Graph” will help identify whether it is actually your objects that are causing the memory graph or whether it is some more subtle memory issue.


Unrelated to the question at hand, but this code sample, which accesses buttonpress and final from multiple threads, is not thread-safe. The thread sanitizer (TSAN) might help identify these issues. You should use a lock or other synchronization mechanism when accessing these properties from multiple threads. Or implement patterns that eliminate these data races entirely. But that is beyond the scope of this question. Solve your memory issue first, and then tackle the thread-safety concerns.

Sign up to request clarification or add additional context in comments.

Comments

0

You have some misconceptions.

Your current code:

  • Runs a block of code on a background thread.
  • Inside that thread, repeats a loop using a while statement.
  • Inside the while statement, you create a new find object (Assuming that find() is an initializer. The name should really be Find to follow Swift naming conventions...)

Here's the thing:

On each pass through your while loop, you create a new find object, invoke several methods on that find object, and then get a String result and install that in self.final.

Once each pass through your while loop completes, your local variables go out of scope, and any objects those variables hold get released. Thus you should not have memory issues.

You said "I understand that I need to use let varfind : find? = find(), then add a deinit inside the find class in order to set everything to nil however". That's not right. Since you create a find object inside a loop, each find will get released when that pass through the loop completes. Further, when the find object gets released, and objects it owns will also get released.

A gotcha in your code is that the block captures self. You will prevent the object that calls this code from being released as long as the while loop runs. That may or may not be a problem. To fix that you should add a "capture list" that passes a weak reference to self.

Another point: I gather that buttonPress is an instance variable of the view controller? If so, you need to add self? to that while statement:

DispatchQueue.global().async{ [weak self] in //"[weak self]" creates a capture list
    while let self = self, // Make sure self isn't nil 
          self.buttonpress == true { 
        let varfind = find()
        let b = varfind.build()
        let yes = varfind.isInside(index: b,xaxis: locationViewModel.userXaxis, yaxis: locationViewModel.userYaxis)
        self.final = String(Int(locationViewModel.userZaxis)-yes)
    }
}

1 Comment

if you read the OPs question carefully, they say "...then add a deinit inside the find class in order to set everything to nil however" So yes, it sounds like it's a class.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.