Skip to content

Commit 98e7ed5

Browse files
committed
Fix memory leaks
1 parent 3f25053 commit 98e7ed5

File tree

9 files changed

+92
-34
lines changed

9 files changed

+92
-34
lines changed

Package.resolved

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Sources/LiveViewNative/Coordinators/LiveSessionCoordinator.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public class LiveSessionCoordinator<R: RootRegistry>: ObservableObject {
5050
var socket: LiveViewNativeCore.Socket?
5151

5252
private var liveReloadChannel: LiveViewNativeCore.LiveChannel?
53-
private var liveReloadListener: AsyncThrowingStream<LiveViewNativeCore.EventPayload, any Error>?
53+
private var liveReloadListener: Channel.EventStream?
5454
private var liveReloadListenerLoop: Task<(), any Error>?
5555

5656
private var cancellables = Set<AnyCancellable>()

Sources/LiveViewNative/Coordinators/LiveViewCoordinator.swift

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,9 @@ public class LiveViewCoordinator<R: RootRegistry>: ObservableObject {
5454
private(set) internal var eventSubject = PassthroughSubject<(String, Payload), Never>()
5555
private(set) internal var eventHandlers = Set<AnyCancellable>()
5656

57-
private var eventListener: AsyncThrowingStream<LiveViewNativeCore.EventPayload, any Error>?
57+
private var eventListener: Channel.EventStream?
5858
private var eventListenerLoop: Task<(), any Error>?
59+
private var statusListener: Channel.StatusStream?
5960
private var statusListenerLoop: Task<(), any Error>?
6061

6162
private(set) internal var liveViewModel = LiveViewModel()
@@ -289,10 +290,11 @@ public class LiveViewCoordinator<R: RootRegistry>: ObservableObject {
289290
let channel = liveChannel.channel()
290291
self.channel = channel
291292

293+
let statusListener = channel.statusStream()
294+
self.statusListener = statusListener
292295
statusListenerLoop = Task { @MainActor [weak self] in
293-
for try await status in channel.statusStream() {
294-
guard let self else { return }
295-
self.internalState = switch status {
296+
for try await status in statusListener {
297+
self?.internalState = switch status {
296298
case .joined:
297299
.connected
298300
case .joining, .waitingForSocketToConnect, .waitingToJoin:

Sources/LiveViewNative/Environment.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ struct CoordinatorEnvironment {
5757
private final class Storage {
5858
let pushEvent: @MainActor (String, String, Any, Int?) async throws -> [String:Any]?
5959
let elementChanged: @MainActor (NodeRef) -> ObservableObjectPublisher
60-
let document: Document
60+
weak var document: Document?
6161

6262
init<R: CustomRegistry>(_ coordinator: LiveViewCoordinator<R>, document: Document) {
6363
self.pushEvent = coordinator.pushEvent
@@ -73,7 +73,7 @@ struct CoordinatorEnvironment {
7373
var elementChanged: @MainActor (NodeRef) -> ObservableObjectPublisher {
7474
storage.elementChanged
7575
}
76-
var document: Document {
76+
var document: Document? {
7777
storage.document
7878
}
7979

Sources/LiveViewNative/Live/LiveView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ public struct LiveView<
231231
.environment(\.stylesheet, session.stylesheet ?? .init(content: [], classes: [:]))
232232
.environment(\.reconnectLiveView, .init(baseURL: session.url, action: session.reconnect))
233233
.environmentObject(session)
234-
.task(priority: .userInitiated) {
234+
.task {
235235
await session.connect()
236236
}
237237
.onChange(of: scenePhase) { newValue in

Sources/LiveViewNative/Property Wrappers/Event.swift

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -145,30 +145,38 @@ public struct Event: @preconcurrency DynamicProperty, @preconcurrency Decodable
145145

146146
init() {}
147147

148+
deinit {
149+
self.handlerTask?.cancel()
150+
}
151+
148152
func update(coordinator: CoordinatorEnvironment?, debounce: Double?, throttle: Double?) {
149153
guard handlerTask == nil || debounce != self.debounce || throttle != self.throttle
150154
else { return }
151155
handlerTask?.cancel()
152156
self.debounce = debounce
153157
self.throttle = throttle
158+
let pushEvent = coordinator?.pushEvent
154159
if let debounce = debounce {
155-
handlerTask = Task { [weak didSendSubject] in
160+
handlerTask = Task { [weak channel, weak didSendSubject, pushEvent] in
161+
guard let channel else { return }
156162
for await event in channel.debounce(for: .milliseconds(debounce)) {
157-
_ = try await coordinator?.pushEvent(event.type, event.event, event.payload, event.target)
163+
_ = try await pushEvent?(event.type, event.event, event.payload, event.target)
158164
didSendSubject?.send()
159165
}
160166
}
161167
} else if let throttle = throttle {
162-
handlerTask = Task { @MainActor [weak didSendSubject] in
163-
for await event in channel.throttle(for: .milliseconds(throttle)) {
164-
_ = try await coordinator?.pushEvent(event.type, event.event, event.payload, event.target)
168+
handlerTask = Task { @MainActor [weak channel, weak didSendSubject, pushEvent] in
169+
guard let channel else { return }
170+
for await event in channel._throttle(for: .milliseconds(throttle)) {
171+
_ = try await pushEvent?(event.type, event.event, event.payload, event.target)
165172
didSendSubject?.send()
166173
}
167174
}
168175
} else {
169-
handlerTask = Task { @MainActor [weak didSendSubject] in
176+
handlerTask = Task { @MainActor [weak channel, weak didSendSubject, pushEvent] in
177+
guard let channel else { return }
170178
for await event in channel {
171-
_ = try await coordinator?.pushEvent(event.type, event.event, event.payload, event.target)
179+
_ = try await pushEvent?(event.type, event.event, event.payload, event.target)
172180
didSendSubject?.send()
173181
}
174182
}

Sources/LiveViewNative/Property Wrappers/ObservedElement.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ extension ObservedElement {
140140
observeChildren: Bool
141141
) {
142142
guard cancellable == nil || (observeChildren && self.observedChildIDs != self.resolvedChildIDs) else { return }
143-
self.resolvedElement = context.document[id].asElement()
143+
self.resolvedElement = context.document?[id].asElement()
144144
self.resolvedChildren = Array(self.resolvedElement.children())
145145
self._resolvedChildIDs = nil
146146

@@ -161,7 +161,7 @@ extension ObservedElement {
161161
cancellable = self.elementChangedPublisher
162162
.sink { [weak self] _ in
163163
guard let self else { return }
164-
self.resolvedElement = context.document[id].asElement()
164+
self.resolvedElement = context.document?[id].asElement()
165165
self.resolvedChildren = Array(self.resolvedElement.children())
166166
self._resolvedChildIDs = nil
167167
self.objectWillChange.send()

Sources/LiveViewNative/Views/Images/ImageView.swift

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,14 +108,28 @@ struct ImageView<Root: RootRegistry>: View {
108108
}
109109
})
110110
}
111+
112+
var fileUploadImage: SwiftUI.Image? {
113+
guard let phxUploadRef
114+
else { return nil }
115+
#if os(macOS)
116+
return liveViewModel
117+
.fileUpload(id: phxUploadRef)
118+
.flatMap({ NSImage(data: $0.data) })
119+
.flatMap(Image.init(nsImage:))
120+
#else
121+
return liveViewModel
122+
.fileUpload(id: phxUploadRef)
123+
.flatMap({ UIImage(data: $0.data) })
124+
.flatMap(Image.init(uiImage:))
125+
#endif
126+
}
111127

112128
var image: SwiftUI.Image? {
113129
if let overrideImage {
114130
return overrideImage
115-
} else if let phxUploadRef,
116-
let image = liveViewModel.fileUpload(id: phxUploadRef).flatMap({ UIImage(data: $0.data) })
117-
{
118-
return SwiftUI.Image(uiImage: image)
131+
} else if let fileUploadImage {
132+
return fileUploadImage
119133
} else if let systemName {
120134
return SwiftUI.Image(systemName: systemName, variableValue: variableValue)
121135
} else if let name {

Sources/LiveViewNative/__CoreExtensions.swift

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -59,18 +59,52 @@ extension Node: Identifiable {
5959
}
6060

6161
extension Channel {
62-
public func eventStream() -> AsyncThrowingStream<EventPayload, any Error> {
63-
let events = self.events()
64-
return AsyncThrowingStream(unfolding: {
65-
return try await events.event()
66-
})
62+
struct EventStream: AsyncSequence {
63+
let events: Events
64+
65+
init(for channel: Channel) {
66+
self.events = channel.events()
67+
}
68+
69+
func makeAsyncIterator() -> AsyncIterator {
70+
.init(events: events)
71+
}
72+
73+
struct AsyncIterator: AsyncIteratorProtocol {
74+
let events: Events
75+
76+
func next() async throws -> EventPayload? {
77+
try await events.event()
78+
}
79+
}
80+
}
81+
82+
func eventStream() -> EventStream {
83+
return EventStream(for: self)
84+
}
85+
86+
final class StatusStream: AsyncSequence {
87+
let statuses: ChannelStatuses
88+
89+
init(for channel: Channel) {
90+
self.statuses = channel.statuses()
91+
}
92+
93+
func makeAsyncIterator() -> AsyncIterator {
94+
.init(statuses: statuses)
95+
}
96+
97+
struct AsyncIterator: AsyncIteratorProtocol {
98+
let statuses: ChannelStatuses
99+
100+
func next() async throws -> ChannelStatus? {
101+
try await statuses.status()
102+
}
103+
}
67104
}
68105

69-
public func statusStream() -> AsyncThrowingStream<ChannelStatus, any Error> {
70-
let statuses = self.statuses()
71-
return AsyncThrowingStream(unfolding: {
72-
return try await statuses.status()
73-
})
106+
func statusStream() -> StatusStream {
107+
StatusStream(for: self)
74108
}
75109
}
76110

0 commit comments

Comments
 (0)