Skip to content

Commit ea47303

Browse files
committed
Fix memory leak when closing the EPUB navigator before the content is loaded
1 parent 420809e commit ea47303

File tree

3 files changed

+38
-9
lines changed

3 files changed

+38
-9
lines changed

Sources/Navigator/EPUB/EPUBReflowableSpreadView.swift

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,16 @@ final class EPUBReflowableSpreadView: EPUBSpreadView {
3333
)
3434
}
3535

36+
override func clear() {
37+
super.clear()
38+
39+
// Clean up go to continuations.
40+
for continuation in goToContinuations {
41+
continuation.resume()
42+
}
43+
goToContinuations.removeAll()
44+
}
45+
3646
override func setupWebView() {
3747
super.setupWebView()
3848

@@ -193,7 +203,6 @@ final class EPUBReflowableSpreadView: EPUBSpreadView {
193203
// Location to scroll to in the resource once the page is loaded.
194204
private var pendingLocation: PageLocation = .start
195205

196-
@MainActor
197206
override func go(to location: PageLocation) async {
198207
guard isSpreadLoaded else {
199208
// Delays moving to the location until the document is loaded.
@@ -215,22 +224,19 @@ final class EPUBReflowableSpreadView: EPUBSpreadView {
215224
didCompleteGoTo()
216225
}
217226

218-
@MainActor
219227
private func waitGoToCompletion() async {
220228
await withCheckedContinuation { continuation in
221229
goToContinuations.append(continuation)
222230
}
223231
}
224232

225-
@MainActor
226233
private func didCompleteGoTo() {
227234
for cont in goToContinuations {
228235
cont.resume()
229236
}
230237
goToContinuations.removeAll()
231238
}
232239

233-
@MainActor
234240
private var goToContinuations: [CheckedContinuation<Void, Never>] = []
235241

236242
@discardableResult

Sources/Navigator/EPUB/EPUBSpreadView.swift

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,13 @@ class EPUBSpreadView: UIView, Loggable, PageView {
9595

9696
deinit {
9797
NotificationCenter.default.removeObserver(self)
98+
clear()
99+
}
100+
101+
/// Called when the spread view is removed from the view hierarchy, to
102+
/// clear pending operations and retain cycles.
103+
func clear() {
104+
// Disable JS messages to break WKUserContentController reference.
98105
disableJSMessages()
99106
}
100107

@@ -126,14 +133,18 @@ class EPUBSpreadView: UIView, Loggable, PageView {
126133
webView.scrollView
127134
}
128135

136+
override func willMove(toSuperview newSuperview: UIView?) {
137+
super.willMove(toSuperview: newSuperview)
138+
139+
if newSuperview == nil {
140+
clear()
141+
}
142+
}
143+
129144
override func didMoveToSuperview() {
130145
super.didMoveToSuperview()
131146

132-
if superview == nil {
133-
disableJSMessages()
134-
// Fixing an iOS 9 bug by explicitly clearing scrollView.delegate before deinitialization
135-
scrollView.delegate = nil
136-
} else {
147+
if superview != nil {
137148
enableJSMessages()
138149
scrollView.delegate = self
139150
}

Sources/Navigator/Toolkit/PaginationView.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,18 @@ final class PaginationView: UIView, Loggable {
150150
scrollView.contentOffset.x = xOffsetForIndex(currentIndex)
151151
}
152152

153+
override func willMove(toSuperview newSuperview: UIView?) {
154+
super.willMove(toSuperview: newSuperview)
155+
156+
if newSuperview == nil {
157+
// Remove all spread views to break retain cycles
158+
for (_, view) in loadedViews {
159+
view.removeFromSuperview()
160+
}
161+
loadedViews.removeAll()
162+
}
163+
}
164+
153165
override func didMoveToWindow() {
154166
super.didMoveToWindow()
155167

0 commit comments

Comments
 (0)