Skip to content

Commit

Permalink
Fix ditto instance retain cycle in PresenceViewer (#167)
Browse files Browse the repository at this point in the history
Working from Konstantine's fix in [monorepo:](https://github.com/getditto/ditto/pull/10152/files#diff-e9c0cbddf72b763b74653917aa980f0d7c229f172d85250b57ea9bdb17a5da77)
with changes for the current DittoSwiftTools version
  • Loading branch information
magicwave authored Dec 20, 2024
1 parent fbf428d commit 70b923a
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 31 deletions.
49 changes: 38 additions & 11 deletions Sources/DittoPresenceViewer/DittoPresenceView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,25 @@ public class DittoPresenceView: PlatformView {
The `DittoPresenceView` will not display any presence information
until the `ditto` property has been set.
*/
public var ditto: Ditto? {
public weak var ditto: Ditto? {
willSet {
if (ditto !== newValue) {
self.stopObservingPresence()
}
}

didSet {
observePresence()
if (oldValue !== ditto) {
self.startObservingPresence()
}
}
}

deinit {
stopObservingPresence()
webView.removeFromSuperview()
_vc = nil
}

/**
Returns a `UIViewController` containing this view.
Expand All @@ -73,7 +87,17 @@ public class DittoPresenceView: PlatformView {
// The inversion will likely trip up more experienced native iOS
// developers, but they're also the group likely to be designing their
// own `UIViewControllers` which renders this a non-issue in most cases.
return _vc
//
// Also note that we can't hold the view controller strongly here because
// this would lead to a retain cycle. Therefore, we'll hold it weakly
// and create a new one whenever the previous one is dealloced:
if let vc = self._vc {
return vc
}

let vc = DittoPresenceViewController(view: self)
self._vc = vc
return vc
}

// MARK: Private Properties
Expand All @@ -82,7 +106,7 @@ public class DittoPresenceView: PlatformView {

private var webView = VisJSWebView()

private lazy var _vc: PlatformViewController! = DittoPresenceViewController(view: self)
private weak var _vc: DittoPresenceViewController?

// MARK: Initializer

Expand All @@ -95,9 +119,7 @@ public class DittoPresenceView: PlatformView {
public convenience init(ditto: Ditto) {
self.init(frame: .zero)
self.ditto = ditto

setup()
observePresence()
startObservingPresence()
}

public override init(frame: CGRect) {
Expand All @@ -109,10 +131,12 @@ public class DittoPresenceView: PlatformView {
super.init(coder: coder)
setup()
}



// MARK: Private Functions

private func setup() {

#if canImport(UIKit)
backgroundColor = .clear
#endif
Expand All @@ -127,9 +151,9 @@ public class DittoPresenceView: PlatformView {
])
}

private func observePresence() {
private func startObservingPresence() {
if let ditto = ditto {
peersObserver = ditto.presence.observe { presenceGraph in
self.peersObserver = ditto.presence.observe { presenceGraph in
let encoder = JSONEncoder()
encoder.dataEncodingStrategy = .custom({ data, enc in
var container = enc.unkeyedContainer()
Expand All @@ -144,7 +168,6 @@ public class DittoPresenceView: PlatformView {
}
}
}

// // Comment out the ditto observer above and toggle following to test presence with
// // fake data. Several different mock drivers exist:
// // - runFrequentConnectionChanges()
Expand All @@ -156,4 +179,8 @@ public class DittoPresenceView: PlatformView {
// }
}

private func stopObservingPresence() {
peersObserver?.stop()
peersObserver = nil
}
}
34 changes: 14 additions & 20 deletions Sources/DittoPresenceViewer/PresenceView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,47 +22,41 @@ import AppKit
#if canImport(WebKit)
@available(macOS 10.15, *)
public struct PresenceView: View {
public var ditto: Ditto
private var ditto: Ditto

public init(ditto: Ditto) {
self.ditto = ditto
}

public var body: some View {
PresenceView(ditto: ditto)
DittoPresenceViewRepresentable(ditto: ditto)
}
}
#endif

// MARK: - UIViewRepresentable
#if os(iOS)
extension PresenceView: UIViewRepresentable {
public typealias Body = Never
public typealias UIViewType = UIView
private struct DittoPresenceViewRepresentable: UIViewRepresentable {
let ditto: Ditto

public func makeUIView(context: Self.Context) -> Self.UIViewType {
return DittoPresenceView(ditto: self.ditto)
func makeUIView(context: Context) -> UIView {
return DittoPresenceView(ditto: ditto)
}

public func updateUIView(_: Self.UIViewType, context: Self.Context) {
return
func updateUIView(_ uiView: UIView, context: Context) {
}
}
#endif

// MARK: - NSViewRepresentable
#if os(macOS)
@available(macOS 10.15, *)
extension PresenceView: NSViewRepresentable {
public typealias Body = Never
public typealias NSViewType = NSView
#elseif os(macOS)
private struct DittoPresenceViewRepresentable: NSViewRepresentable {
let ditto: Ditto

public func makeNSView(context: Self.Context) -> Self.NSViewType {
return DittoPresenceView(ditto: self.ditto)
func makeNSView(context: Context) -> NSView {
return DittoPresenceView(ditto: ditto)
}

public func updateNSView(_: Self.NSViewType, context: Self.Context) {
return
func updateNSView(_ nsView: NSView, context: Context) {
}
}
#endif
#endif

0 comments on commit 70b923a

Please sign in to comment.