tidwall / SwiftWebSocket

Fast Websockets in Swift for iOS and OSX
MIT License
1.53k stars 246 forks source link

Memory Usage when sending a large message #149

Open jshafer817 opened 5 years ago

jshafer817 commented 5 years ago

Hi, it seems that if I send a large string to a different socket server, after sending the message, the ram is never released. The string is 5MB.

Works fine, but.. even after the message is send, and closed.. ram is not released.

ws.send (text: self.jpegPhotoData!.base64EncodedString(options: .lineLength64Characters))

Any ideas?

gacrichards commented 5 years ago

@jshafer817, I noticed this library creates some nasty retain cycles by virtue of the functional interface design of the open, close, error, and message methods on event. I did not see an obvious way to provide a weak reference to the library for those callbacks and decided the best way to stop the bleeding was to wrap the Websocket object in another object that is allocated only once. I hope the memory issues get fixed in future builds, but expect it'll require a significant change in how we interface with the library.

jshafer817 commented 5 years ago

"wrap the Websocket object in another object that is allocated only once".. Do you have an example you are willing to share? Thanks for the clues.

gacrichards commented 5 years ago

This is my wrapper object (https://gist.github.com/gacrichards/e652ac7281218dffbc5b72ce1b08c898). The basic idea here is that I take the functional bindings of a Websocket() instance and map them to a weakly held delegate owned by my wrapper object. I only ever want to create one instance of my wrapper object since it is in a strong reference cycle with it's websocket object - releasing my wrapper will cause a leak. In many of my iOS apps I have a dependency factory which is a singleton and holds on to core objects (as few as possible) that need to be injected throughout the app. I place my wrapper object in the dependency factory and that gives me pretty tight control on its life cycle. Here is an example of what that might look like (https://gist.github.com/gacrichards/fb714b34ea5304729b8fae26fec23196). The only state I care about for my application is the request that I use to open the websocket since some websocket connections need to be authorized with an access token. I use my wrapper object add authorization or revoke authorization from that request, i.e. to mutate any state. Fortunately the socket doesn't appear to hold a strong reference to the request so I'm free to change that as I need. The obvious drawback to this approach is that I'll always have a websocket and wrapper object in memory, but that is better, IMHO, than leaking the socket and any other objects that it comes into contact with. If I've gone off in a weird and unfruitful direction I'd love to be corrected, but this passes memory profiling tests for my app.

smhk commented 5 years ago

@jshafer817 , thanks, do you mean you find that

?

It seems strange there would be any difference, you know? What do you think?