Skip to content

Commit b3c6100

Browse files
authored
Merge pull request #1 from eaceto/feature/add-livespan-to-cache
Add lifespan to WebViews
2 parents 520dc6d + 959f68b commit b3c6100

File tree

6 files changed

+110
-34
lines changed

6 files changed

+110
-34
lines changed

Package.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ import PackageDescription
55

66
let package = Package(
77
name: "WKWebView_WarmUp",
8+
platforms: [
9+
.iOS(.v9),
10+
.macOS(.v10_12),
11+
],
812
products: [
913
.library(
1014
name: "WKWebView_WarmUp",

README.md

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
[![Swift CI](https://github.com/eaceto/WKWebView_WarmUp/actions/workflows/swift-ci.yml/badge.svg)](https://github.com/eaceto/WKWebView_WarmUp/actions/workflows/swift-ci.yml)
1010
[![Swift Docs](https://github.com/eaceto/WKWebView_WarmUp/actions/workflows/swift-docs.yml/badge.svg?branch=main)](https://github.com/eaceto/WKWebView_WarmUp/actions/workflows/swift-docs.yml)
1111

12-
[![Latest release](https://img.shields.io/badge/Latest_release-1.0.0-blue.svg)](https://github.com/eaceto/WKWebView/releases/1.0.0)
12+
[![Latest release](https://img.shields.io/badge/Latest_release-1.1.0-blue.svg)](https://github.com/eaceto/WKWebView/releases/1.1.0)
1313

1414
A library that speeds up the loading of Web Pages when using WKWebView.
1515

@@ -26,14 +26,15 @@ A library that speeds up the loading of Web Pages when using WKWebView.
2626
* [Cocoapods](#cocoapods)
2727
* [Swift Package Manager](#swift-package-manager)
2828
4. [Usage](#usage)
29-
5. [Author](#author)
29+
5. [Notes](#notes)
30+
6. [Author](#author)
3031

3132
### Requirements
3233

3334
| Platform | Minimun Swift Version | Installation | Status |
3435
| --- | --- | --- | --- |
3536
| iOS 9.0+ | 5.3 | [Cocoapods](#cocoapods), [Swift Package Manager](#swift-package-manager) | Fully Tested |
36-
| macOS 10.10+ | 5.3 | [Cocoapods](#cocoapods), [Swift Package Manager](#swift-package-manager) | Fully Tested |
37+
| macOS 10.12+ | 5.3 | [Cocoapods](#cocoapods), [Swift Package Manager](#swift-package-manager) | Fully Tested |
3738

3839
### Installation
3940
#### Cocoapods
@@ -75,6 +76,17 @@ Then, when you want to retrieve the warmed-up WebView, just call
7576
let webView = WKWebViewHeater.shared.dequeue(with: url)!
7677
````
7778

79+
#### Lifespan
80+
81+
Warmed-up WebView may have a lifespan. In case the end of life of a WebView happens when it's inside the pool (not yet dequeued), the pool will reload it by calling the **warmUp** method over the WebView automatically.
82+
83+
````swift
84+
let url = URL(string: "https://duckduckgo.com")!
85+
WKWebViewHeater.shared.warmUp(with: url, lifespan: 30.0) //WebView will be automatically reloaded every 30 seconds.
86+
````
87+
88+
### Notes
89+
7890
**Remember that** this WebView's size is Zero! In order to added to your ViewController, use AutoLayout as follows:
7991

8092
````swift

Sources/WKWebView_WarmUp/URLRequestHeater.swift

Lines changed: 49 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@ public class URLRequestHeater<Object: WarmableURL> {
2121

2222
private let creationClosure: () -> Object
2323

24-
internal var pool: [String: Object ] = [:]
2524
internal let anonymousHeater: Heater<Object>
26-
25+
internal var pool: [URLRequest: Object] = [:]
26+
internal var objectsLivespan: [URLRequest: TimeInterval] = [:]
27+
internal var livespanTimer: Timer? = nil
28+
2729
/**
2830
Initialize a **URLRequestHeater** with **creationClosure** as the block of code to init its objects
2931

@@ -39,27 +41,51 @@ public class URLRequestHeater<Object: WarmableURL> {
3941
self.creationClosure = creationClosure
4042
self.anonymousHeater = Heater<Object>(creationClosure: creationClosure)
4143
}
44+
45+
private func checkLivespanTimer() {
46+
if livespanTimer != nil { return }
47+
if let livespanTimer = livespanTimer, livespanTimer.isValid {
48+
return
49+
}
50+
livespanTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { [weak self] _ in
51+
guard let self = self else { return }
52+
let now = Date().timeIntervalSinceReferenceDate
53+
self.objectsLivespan.forEach { (urlRequest, livespan) in
54+
if now > livespan, let object = self.pool[urlRequest] {
55+
DispatchQueue.main.async {
56+
object.warmUp(with: urlRequest)
57+
}
58+
}
59+
}
60+
})
61+
}
4262
}
4363

4464
/// Extension for (URL) named objects pool
4565
public extension URLRequestHeater {
4666

4767
/// starts warming up a new **Object** identified by an **URL**
4868
/// - Parameter url: an URL that will be sent as param in the creation closure.
69+
/// - Parameter livespan: number of seconds of life of this warmed up object. It reloads the cache when the livespan ends.
4970
/// Object is identified by this url
50-
func warmUp(with url: URL) {
71+
func warmUp(with url: URL, livespan: TimeInterval? = nil) {
5172
let urlRequest = URLRequest(url: url)
52-
warmUp(with: urlRequest)
73+
warmUp(with: urlRequest, livespan: livespan)
5374
}
5475

5576
/// starts warming up a new **Object** identified by an **urlString**
5677
/// - Parameter request: an URL Request that will be sent as param in the creation closure.
78+
/// - Parameter livespan: number of seconds of life of this warmed up object. It reloads the cache when the livespan ends.
5779
/// Object is identified by its absolute URL String.
58-
func warmUp(with request: URLRequest) {
59-
guard let url = request.url else { return }
80+
func warmUp(with request: URLRequest, livespan: TimeInterval? = nil) {
6081
let object = creationClosure()
6182
object.warmUp(with: request)
62-
pool[url.absoluteString] = object
83+
pool[request] = object
84+
if let livespan = livespan {
85+
let endsDate = Date().timeIntervalSinceReferenceDate + livespan
86+
objectsLivespan[request] = endsDate
87+
checkLivespanTimer()
88+
}
6389
}
6490
}
6591

@@ -73,25 +99,27 @@ public extension URLRequestHeater {
7399

74100
/// Dequeues a named object if available
75101
/// - Parameter url: an URL that identifies the warmed-up object
76-
/// - Returns: an **Object** if exists in the **URLRequestHeater** pool of objects, **nil** otherwise
77-
func dequeue(with url: URL) -> Object? {
78-
let urlString = url.absoluteString
79-
guard let warmedUpObject = pool[urlString] else {
80-
return nil
81-
}
82-
pool.removeValue(forKey: urlString)
83-
return warmedUpObject
102+
/// - Returns: a warmed up **Object** if exists in the **URLRequestHeater** pool of objects, or a new one.
103+
func dequeue(with url: URL) -> Object {
104+
let urlRequest = URLRequest(url: url)
105+
return dequeue(with: urlRequest)
84106
}
85107

86108
/// Dequeues a named object if available
87109
/// - Parameter request: an URL Request, which absolute URL identifies the warmed-up object
88-
/// - Returns: an **Object** if exists in the **URLRequestHeater** pool of objects, **nil** otherwise
89-
func dequeue(with request: URLRequest) -> Object? {
90-
guard let url = request.url,
91-
let warmedUpObject = pool[url.absoluteString] else {
92-
return nil
110+
/// - Returns: a warmed up **Object** if exists in the **URLRequestHeater** pool of objects, or a new one.
111+
func dequeue(with request: URLRequest) -> Object {
112+
guard let warmedUpObject = pool[request] else {
113+
let object = dequeue()
114+
object.warmUp(with: request)
115+
return object
93116
}
94-
pool.removeValue(forKey: url.absoluteString)
117+
pool.removeValue(forKey: request)
118+
objectsLivespan.removeValue(forKey: request)
119+
if objectsLivespan.isEmpty {
120+
livespanTimer?.invalidate()
121+
livespanTimer = nil
122+
}
95123
return warmedUpObject
96124
}
97125
}

Sources/WKWebView_WarmUp/WKWebView+Extensions.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ extension WKWebView: WarmableURL {
2525
/// Warms up the WKWebView, its engine, and prefetches the **URLRequest**
2626
/// - Parameter urlRequest: a valid URL Request to prefetch
2727
public func warmUp(with urlRequest: URLRequest) {
28+
if let url = url, url.absoluteString == urlRequest.url?.absoluteString {
29+
self.reload()
30+
return
31+
}
2832
_ = self.load(urlRequest)
2933
}
3034

Tests/WKWebView_WarmUpTests/WKWebViewHeaterTests.swift

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,7 @@ final class WKWebViewHeaterTests: XCTestCase {
8686
)
8787

8888
DispatchQueue.global().async {
89-
guard let webView = WKWebViewHeater.shared.dequeue(with: url) else {
90-
XCTFail("WebView identified by \(url) does not exists")
91-
return
92-
}
89+
let webView = WKWebViewHeater.shared.dequeue(with: url)
9390

9491
repeat {
9592
Thread.sleep(forTimeInterval: 0.05) // Give WKWebViewHeater time to load
@@ -114,10 +111,7 @@ final class WKWebViewHeaterTests: XCTestCase {
114111
)
115112

116113
DispatchQueue.global().async {
117-
guard let webView = WKWebViewHeater.shared.dequeue(with: initialUrl) else {
118-
XCTFail("WebView identified by \(initialUrl) does not exists")
119-
return
120-
}
114+
let webView = WKWebViewHeater.shared.dequeue(with: initialUrl)
121115

122116
repeat {
123117
Thread.sleep(forTimeInterval: 0.05) // Give WKWebViewHeater time to load
@@ -131,4 +125,38 @@ final class WKWebViewHeaterTests: XCTestCase {
131125

132126
wait(for: [expectation], timeout: 30.0) // Give this test 30 seconds max to complete
133127
}
128+
129+
func testWarmUpAWebViewByURLWithLifespan() {
130+
let url = URL(string: "https://duckduckgo.com/")!
131+
WKWebViewHeater.shared.warmUp(with: url, livespan: 5.0)
132+
XCTAssert(!WKWebViewHeater.shared.objectsLivespan.isEmpty)
133+
134+
let expectation = XCTestExpectation(
135+
description: "expect to have a WebView warmed-up at URL: \(url)"
136+
)
137+
138+
DispatchQueue.global().async {
139+
Thread.sleep(forTimeInterval: 7.0) // Give WKWebViewHeater time to expire and reload
140+
141+
XCTAssert(WKWebViewHeater.shared.livespanTimer != nil)
142+
XCTAssert(WKWebViewHeater.shared.livespanTimer?.isValid ?? false)
143+
144+
let webView = WKWebViewHeater.shared.dequeue(with: url)
145+
146+
repeat {
147+
Thread.sleep(forTimeInterval: 5.0) // Give WKWebViewHeater time to load
148+
} while (webView.isLoading)
149+
150+
XCTAssert(webView.url?.absoluteString == url.absoluteString)
151+
XCTAssert(webView.configuration.processPool == WKWebViewSharedManager.default.processPool)
152+
XCTAssert(!webView.isLoading)
153+
154+
XCTAssert(WKWebViewHeater.shared.objectsLivespan.isEmpty)
155+
XCTAssert(WKWebViewHeater.shared.livespanTimer == nil)
156+
157+
expectation.fulfill()
158+
}
159+
160+
wait(for: [expectation], timeout: 30.0) // Give this test 30 seconds max to complete
161+
}
134162
}

WKWebView_WarmUp.podspec

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
libraryVersion = "1.0.0"
1+
libraryVersion = "1.1.0"
22

33
Pod::Spec.new do |s|
44
s.name = "WKWebView_WarmUp"
@@ -15,7 +15,7 @@ Pod::Spec.new do |s|
1515
}
1616

1717
s.ios.deployment_target = "9.0"
18-
s.macos.deployment_target = "10.10"
18+
s.macos.deployment_target = "10.12"
1919

2020
s.swift_versions = ['5.3', '5.4', '5.5', '5.6']
2121

0 commit comments

Comments
 (0)