Skip to content

Commit 984537a

Browse files
committed
Fix memory leaks
1 parent b9e4d43 commit 984537a

File tree

10 files changed

+96
-38
lines changed

10 files changed

+96
-38
lines changed

Package.resolved

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

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ let package = Package(
2626
// Dependencies declare other packages that this package depends on.
2727
.package(url: "https://github.com/scinfu/SwiftSoup.git", from: "2.3.2"),
2828
.package(url: "https://github.com/davidstump/SwiftPhoenixClient.git", .upToNextMinor(from: "5.3.2")),
29-
.package(url: "https://github.com/apple/swift-async-algorithms", from: "0.1.0"),
29+
.package(url: "https://github.com/apple/swift-async-algorithms", from: "1.0.0"),
3030
.package(url: "https://github.com/liveview-native/liveview-native-core", exact: "0.4.0-alpha-11"),
3131

3232
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.5.0"),

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: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public class LiveViewCoordinator<R: RootRegistry>: ObservableObject {
3737
var url: URL
3838

3939
private(set) var liveChannel: LiveViewNativeCore.LiveChannel?
40-
private weak var channel: LiveViewNativeCore.Channel?
40+
private var channel: LiveViewNativeCore.Channel?
4141

4242
@Published var document: LiveViewNativeCore.Document?
4343
private var elementChangedSubjects = [NodeRef:ObjectWillChangePublisher]()
@@ -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()
@@ -293,10 +294,11 @@ public class LiveViewCoordinator<R: RootRegistry>: ObservableObject {
293294
let channel = liveChannel.channel()
294295
self.channel = channel
295296

297+
let statusListener = channel.statusStream()
298+
self.statusListener = statusListener
296299
statusListenerLoop = Task { @MainActor [weak self] in
297-
for try await status in channel.statusStream() {
298-
guard let self else { return }
299-
self.internalState = switch status {
300+
for try await status in statusListener {
301+
self?.internalState = switch status {
300302
case .joined:
301303
.connected
302304
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
@@ -224,7 +224,7 @@ public struct LiveView<
224224
.environment(\.stylesheet, session.stylesheet ?? .init(content: [], classes: [:]))
225225
.environment(\.reconnectLiveView, .init(baseURL: session.url, action: session.reconnect))
226226
.environmentObject(session)
227-
.task(priority: .userInitiated) {
227+
.task {
228228
await session.connect()
229229
}
230230
.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
@@ -144,30 +144,38 @@ public struct Event: @preconcurrency DynamicProperty, @preconcurrency Decodable
144144

145145
init() {}
146146

147+
deinit {
148+
self.handlerTask?.cancel()
149+
}
150+
147151
func update(coordinator: CoordinatorEnvironment?, debounce: Double?, throttle: Double?) {
148152
guard handlerTask == nil || debounce != self.debounce || throttle != self.throttle
149153
else { return }
150154
handlerTask?.cancel()
151155
self.debounce = debounce
152156
self.throttle = throttle
157+
let pushEvent = coordinator?.pushEvent
153158
if let debounce = debounce {
154-
handlerTask = Task { [weak didSendSubject] in
159+
handlerTask = Task { [weak channel, weak didSendSubject, pushEvent] in
160+
guard let channel else { return }
155161
for await event in channel.debounce(for: .milliseconds(debounce)) {
156-
_ = try await coordinator?.pushEvent(event.type, event.event, event.payload, event.target)
162+
_ = try await pushEvent?(event.type, event.event, event.payload, event.target)
157163
didSendSubject?.send()
158164
}
159165
}
160166
} else if let throttle = throttle {
161-
handlerTask = Task { @MainActor [weak didSendSubject] in
162-
for await event in channel.throttle(for: .milliseconds(throttle)) {
163-
_ = try await coordinator?.pushEvent(event.type, event.event, event.payload, event.target)
167+
handlerTask = Task { @MainActor [weak channel, weak didSendSubject, pushEvent] in
168+
guard let channel else { return }
169+
for await event in channel._throttle(for: .milliseconds(throttle)) {
170+
_ = try await pushEvent?(event.type, event.event, event.payload, event.target)
164171
didSendSubject?.send()
165172
}
166173
}
167174
} else {
168-
handlerTask = Task { @MainActor [weak didSendSubject] in
175+
handlerTask = Task { @MainActor [weak channel, weak didSendSubject, pushEvent] in
176+
guard let channel else { return }
169177
for await event in channel {
170-
_ = try await coordinator?.pushEvent(event.type, event.event, event.payload, event.target)
178+
_ = try await pushEvent?(event.type, event.event, event.payload, event.target)
171179
didSendSubject?.send()
172180
}
173181
}

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
@@ -30,18 +30,52 @@ extension Node: Identifiable {
3030
}
3131

3232
extension Channel {
33-
func eventStream() -> AsyncThrowingStream<EventPayload, any Error> {
34-
let events = self.events()
35-
return AsyncThrowingStream(unfolding: {
36-
return try await events.event()
37-
})
33+
struct EventStream: AsyncSequence {
34+
let events: Events
35+
36+
init(for channel: Channel) {
37+
self.events = channel.events()
38+
}
39+
40+
func makeAsyncIterator() -> AsyncIterator {
41+
.init(events: events)
42+
}
43+
44+
struct AsyncIterator: AsyncIteratorProtocol {
45+
let events: Events
46+
47+
func next() async throws -> EventPayload? {
48+
try await events.event()
49+
}
50+
}
51+
}
52+
53+
func eventStream() -> EventStream {
54+
return EventStream(for: self)
55+
}
56+
57+
final class StatusStream: AsyncSequence {
58+
let statuses: ChannelStatuses
59+
60+
init(for channel: Channel) {
61+
self.statuses = channel.statuses()
62+
}
63+
64+
func makeAsyncIterator() -> AsyncIterator {
65+
.init(statuses: statuses)
66+
}
67+
68+
struct AsyncIterator: AsyncIteratorProtocol {
69+
let statuses: ChannelStatuses
70+
71+
func next() async throws -> ChannelStatus? {
72+
try await statuses.status()
73+
}
74+
}
3875
}
3976

40-
func statusStream() -> AsyncThrowingStream<ChannelStatus, any Error> {
41-
let statuses = self.statuses()
42-
return AsyncThrowingStream(unfolding: {
43-
return try await statuses.status()
44-
})
77+
func statusStream() -> StatusStream {
78+
StatusStream(for: self)
4579
}
4680
}
4781

0 commit comments

Comments
 (0)