Skip to content

Commit 9d77501

Browse files
authored
Fix memory leaks in the navigators and publication server (#49)
1 parent 4dd57fc commit 9d77501

File tree

7 files changed

+76
-67
lines changed

7 files changed

+76
-67
lines changed

CHANGELOG.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,18 @@ All notable changes to this project will be documented in this file. Take a look
44

55
**Warning:** Features marked as *alpha* may change or be removed in a future release without notice. Use with caution.
66

7-
<!--## [Unreleased]-->
7+
## [Unreleased]
8+
9+
### Fixed
10+
11+
#### Navigator
12+
13+
* Fixed memory leaks in the EPUB and PDF navigators.
14+
15+
#### Streamer
16+
17+
* Fixed memory leak in the `PublicationServer`.
18+
819

920
## [2.3.0]
1021

Sources/Navigator/PDF/PDFTapGestureController.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ final class PDFTapGestureController: NSObject {
2222
private let tapAction: TargetAction
2323
private var tapRecognizer: UITapGestureRecognizer!
2424

25-
init(pdfView: PDFView, target: Any, action: Selector) {
25+
init(pdfView: PDFView, target: AnyObject, action: Selector) {
2626
assert(pdfView.superview != nil, "The PDFView must be in the view hierarchy")
2727

2828
self.pdfView = pdfView

Sources/Navigator/Toolkit/PaginationView.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -211,9 +211,11 @@ final class PaginationView: UIView, Loggable {
211211
}
212212
}
213213

214-
loadNextPage {
215-
self.delegate?.paginationViewDidUpdateViews(self)
216-
completion()
214+
loadNextPage { [weak self] in
215+
if let self = self {
216+
self.delegate?.paginationViewDidUpdateViews(self)
217+
completion()
218+
}
217219
}
218220
}
219221

Sources/Navigator/Toolkit/TargetAction.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,18 @@ import UIKit
1515
/// Represents a couple (`target`, `action`) which can be invoked from a `sender`.
1616
final class TargetAction {
1717

18-
private let target: Any
18+
private weak var target: AnyObject?
1919
private let action: Selector
2020

21-
init(target: Any, action: Selector) {
21+
init(target: AnyObject, action: Selector) {
2222
self.target = target
2323
self.action = action
2424
}
2525

2626
func invoke(from sender: Any?) {
27-
UIApplication.shared.sendAction(action, to: target, from: sender, for: nil)
27+
if let target = target {
28+
UIApplication.shared.sendAction(action, to: target, from: sender, for: nil)
29+
}
2830
}
2931

3032
}

Sources/Streamer/Server/PublicationServer.swift

Lines changed: 32 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -211,52 +211,50 @@ public class PublicationServer: ResourcesServer {
211211
}
212212

213213
fileprivate func addResourcesHandler(for publication: Publication, at endpoint: String) throws {
214-
/// Webserver HTTP GET ressources request handler.
215-
func resourcesHandler(request: GCDWebServerRequest?) -> GCDWebServerResponse? {
216-
guard let request = request else {
217-
log(.error, "The request received is nil.")
218-
return GCDWebServerErrorResponse(statusCode: 500)
219-
}
220-
221-
// Remove the prefix from the URI.
222-
var href = request.url.absoluteString
223-
if let range = href.range(of: endpoint) {
224-
href = String(href[range.upperBound...])
225-
href = href.removingPercentEncoding ?? href
226-
}
227-
228-
let resource = publication.get(href.removingPercentEncoding ?? href)
229-
let range = request.hasByteRange() ? request.byteRange : nil
230-
return WebServerResourceResponse(
231-
resource: resource,
232-
range: range,
233-
contentType: resource.link.type ?? MediaType.binary.string
234-
)
235-
}
236-
237214
webServer.addHandler(
238215
forMethod: "GET",
239216
pathRegex: "/\(endpoint)/.*",
240217
request: GCDWebServerRequest.self,
241-
processBlock: resourcesHandler
218+
processBlock: { [weak self, weak publication] request in
219+
guard let publication = publication else {
220+
self?.log(.error, "The publication was deallocated.")
221+
return GCDWebServerErrorResponse(statusCode: 500)
222+
}
223+
224+
// Remove the prefix from the URI.
225+
var href = request.url.absoluteString
226+
if let range = href.range(of: endpoint) {
227+
href = String(href[range.upperBound...])
228+
href = href.removingPercentEncoding ?? href
229+
}
230+
231+
let resource = publication.get(href.removingPercentEncoding ?? href)
232+
let range = request.hasByteRange() ? request.byteRange : nil
233+
return WebServerResourceResponse(
234+
resource: resource,
235+
range: range,
236+
contentType: resource.link.type ?? MediaType.binary.string
237+
)
238+
}
242239
)
243240
}
244241

245242
fileprivate func addManifestHandler(for publication: Publication, at endpoint: String) {
246-
/// The webserver handler to process the HTTP GET
247-
func manifestHandler(request: GCDWebServerRequest?) -> GCDWebServerResponse? {
248-
guard let manifestData = publication.jsonManifest?.data(using: .utf8) else {
249-
return GCDWebServerResponse(statusCode: 404)
250-
}
251-
let type = "\(MediaType.readiumWebPubManifest.string); charset=utf-8"
252-
return GCDWebServerDataResponse(data: manifestData, contentType: type)
253-
}
254-
255243
webServer.addHandler(
256244
forMethod: "GET",
257245
pathRegex: "/\(endpoint)/manifest.json",
258246
request: GCDWebServerRequest.self,
259-
processBlock: manifestHandler
247+
processBlock: { [weak self, weak publication] request in
248+
guard let publication = publication else {
249+
self?.log(.error, "The publication was deallocated.")
250+
return GCDWebServerErrorResponse(statusCode: 500)
251+
}
252+
guard let manifestData = publication.jsonManifest?.data(using: .utf8) else {
253+
return GCDWebServerResponse(statusCode: 404)
254+
}
255+
let type = "\(MediaType.readiumWebPubManifest.string); charset=utf-8"
256+
return GCDWebServerDataResponse(data: manifestData, contentType: type)
257+
}
260258
)
261259
}
262260

TestApp/Sources/Reader/Common/ReaderViewController.swift

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ class ReaderViewController: UIViewController, Loggable {
101101
navigator.didMove(toParent: self)
102102

103103
stackView.addArrangedSubview(accessibilityToolbar)
104-
104+
105105
positionLabel.translatesAutoresizingMaskIntoConstraints = false
106106
positionLabel.font = .systemFont(ofSize: 12)
107107
positionLabel.textColor = .darkGray
@@ -181,9 +181,9 @@ class ReaderViewController: UIViewController, Loggable {
181181
}
182182

183183
locatorPublisher
184-
.sink(receiveValue: { locator in
185-
self.navigator.go(to: locator, animated: false) {
186-
self.dismiss(animated: true)
184+
.sink(receiveValue: { [weak self] locator in
185+
self?.navigator.go(to: locator, animated: false) {
186+
self?.dismiss(animated: true)
187187
}
188188
})
189189
.store(in: &subscriptions)
@@ -218,9 +218,9 @@ class ReaderViewController: UIViewController, Loggable {
218218
@objc func showSearchUI() {
219219
if searchViewModel == nil {
220220
searchViewModel = SearchViewModel(publication: publication)
221-
searchViewModel?.$selectedLocator.sink(receiveValue: { locator in
222-
self.searchViewController?.dismiss(animated: true, completion: nil)
223-
if let locator = locator {
221+
searchViewModel?.$selectedLocator.sink(receiveValue: { [weak self] locator in
222+
self?.searchViewController?.dismiss(animated: true, completion: nil)
223+
if let self = self, let locator = locator {
224224
self.navigator.go(to: locator, animated: true) {
225225
if let decorator = self.navigator as? DecorableNavigator {
226226
let decoration = Decoration(id: "selectedSearchResult", locator: locator, style: Decoration.Style.highlight(tint: .yellow, isActive: false))
@@ -244,8 +244,8 @@ class ReaderViewController: UIViewController, Loggable {
244244
if highlights == nil { return }
245245

246246
if let decorator = self.navigator as? DecorableNavigator {
247-
decorator.observeDecorationInteractions(inGroup: highlightDecorationGroup) { event in
248-
self.activateDecoration(event)
247+
decorator.observeDecorationInteractions(inGroup: highlightDecorationGroup) { [weak self] event in
248+
self?.activateDecoration(event)
249249
}
250250
}
251251
}
@@ -255,8 +255,8 @@ class ReaderViewController: UIViewController, Loggable {
255255

256256
highlights.all(for: bookId)
257257
.assertNoFailure()
258-
.sink { highlights in
259-
if let decorator = self.navigator as? DecorableNavigator {
258+
.sink { [weak self] highlights in
259+
if let self = self, let decorator = self.navigator as? DecorableNavigator {
260260
let decorations = highlights.map { Decoration(id: $0.id, locator: $0.locator, style: .highlight(tint: $0.color.uiColor, isActive: false)) }
261261
decorator.apply(decorations: decorations, in: self.highlightDecorationGroup)
262262
}
@@ -283,17 +283,17 @@ class ReaderViewController: UIViewController, Loggable {
283283
systemFontSize: 20,
284284
colorScheme: colorScheme)
285285

286-
menuView.selectedColorPublisher.sink { color in
287-
self.currentHighlightCancellable?.cancel()
288-
self.updateHighlight(event.decoration.id, withColor: color)
289-
self.highlightContextMenu?.dismiss(animated: true, completion: nil)
286+
menuView.selectedColorPublisher.sink { [weak self] color in
287+
self?.currentHighlightCancellable?.cancel()
288+
self?.updateHighlight(event.decoration.id, withColor: color)
289+
self?.highlightContextMenu?.dismiss(animated: true, completion: nil)
290290
}
291291
.store(in: &subscriptions)
292292

293-
menuView.selectedDeletePublisher.sink { _ in
294-
self.currentHighlightCancellable?.cancel()
295-
self.deleteHighlight(event.decoration.id)
296-
self.highlightContextMenu?.dismiss(animated: true, completion: nil)
293+
menuView.selectedDeletePublisher.sink { [weak self] _ in
294+
self?.currentHighlightCancellable?.cancel()
295+
self?.deleteHighlight(event.decoration.id)
296+
self?.highlightContextMenu?.dismiss(animated: true, completion: nil)
297297
}
298298
.store(in: &subscriptions)
299299

@@ -385,8 +385,8 @@ extension ReaderViewController: NavigatorDelegate {
385385

386386
func navigator(_ navigator: Navigator, locationDidChange locator: Locator) {
387387
books.saveProgress(for: bookId, locator: locator)
388-
.sink { completion in
389-
if case .failure(let error) = completion {
388+
.sink { [weak self] completion in
389+
if let self = self, case .failure(let error) = completion {
390390
self.moduleDelegate?.presentError(error, from: self)
391391
}
392392
} receiveValue: { _ in }

TestApp/Sources/Reader/PDF/PDFViewController.swift

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,6 @@ final class PDFViewController: ReaderViewController {
2626
navigator.delegate = self
2727
}
2828

29-
override func viewDidLoad() {
30-
super.viewDidLoad()
31-
}
32-
3329
override var currentBookmark: Bookmark? {
3430
guard let locator = navigator.currentLocation else {
3531
return nil

0 commit comments

Comments
 (0)