ExistentialAudio/SwiftOSC

How to avoid retain cycles when using OSCServer

jcurtis-cc opened this issue · 15 comments

Hi Devin,

Swift isn't my main language, I'm hoping you could make this clearer for me.

I'm instantiating an OSCServer inside a VC, which I only need while I'm inside that VC. I want to close that connection and deallocate the server when I leave the VC and return to the parent VC.

By making the server.delegate = self I cause a retain cycle even if I try setting server.delegate = nil in viewWilDisappear()

I've tried declaring my server as weak and unowned

What am I missing?

In OSCServer.swift,

open class OSCServer {
    // ...

    open var delegate: OSCServerDelegate?

    // ...
}

delegate is holding a strong reference here.

open var delegate should be open weak var delegate

Thanks for the speedy response @orchetect - I had this suspicion, nice to have that confirmed! I'm still getting a retain cycle after adding weak there. I can't understand what I'm doing wrong here :|

There could still be retain cycles elsewhere in SwiftOSC or in your code.

That was just the most obvious one I spotted.

Thanks @orchetect - think i've tried every variation of weak and unowned in every reference to the delegate I can. Removing the instantiation of the OSCServer in this view controller stops the memory leak. I can't figure out how to deallocate it. The VC is deiniting but the OSCServer stays allocated even when setting it to nil - so every time the VC is pushed, a new OSCServer ends up on the stack

As a workaround I'm instantiating the OSCServer at the top level in appDelegate like one of Devin's examples and setting that VC to the delegate (using self). I can't deallocate it but I can stop it and start it. I'm working on an app that's sensitive to memory so I'm hoping for a fix!

I'm sure this is something very obvious to Devin that I just can't see :)

Don't rule out the possibility that I screwed up somewhere.

In OSCServer.swift line 44.

    func run() {
        DispatchQueue.global().async{
            while true {
                let (data,_,_) = self.server.recv(9216)
                if let data = data {
                    if self.running {
                        let data = Data(data)
                        self.decodePacket(data)
                    }
                }
            }
        }
    }

Probably shouldn't be while true. Try changing that to while self.running. Then open func start() needs to have run() in it .Or something like that.

I don't have time to dig into this right now.

Wow. That was some bad programming.

Awesome, thanks for the quick response Devin - I'll test this and make a PR if it works

It might not completely solve the issue. But there are definitely issues with that block.

Also be aware that async { } closure is holding a strong reference to self.

I'd start by adding [weak self] to the closure.

Maybe I should just pitch @orchetect 's OSC library. https://github.com/orchetect/OSCKit

He doesn't include the network layer so it's not plug and play but if you're good with that stuff it should be pretty easy. I know he's done some pretty extensive testing and uses it daily.

Also, If you check out the dev branch, I was working on using a different Swift Network framework. Maybe try giving that a spin. Full disclosure it's been a while and I'm not sure how well it works.

Pretty much the entire OSCServer is completely different.