Skip to content

Commit

Permalink
Implement MuxRepository.automaticFlush; isDirty logic in MultiplexFet…
Browse files Browse the repository at this point in the history
…cher to avoid repeated writes to disk
  • Loading branch information
crontab committed Sep 30, 2019
1 parent 4a11b1c commit 88ea7bc
Show file tree
Hide file tree
Showing 6 changed files with 57 additions and 8 deletions.
12 changes: 10 additions & 2 deletions Multiplexer/Multiplexer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,15 @@ public class MultiplexFetcher<T: Codable> {
public typealias OnResult = (Result<T, Error>) -> Void

internal var completions: [OnResult] = []

internal var completionTime: TimeInterval = 0
internal var previousValue: T?

internal var isDirty: Bool = false

internal var previousValue: T? {
didSet { isDirty = previousValue != nil }
}

internal var refreshFlag: Bool = false

internal func isExpired(ttl: TimeInterval) -> Bool {
Expand Down Expand Up @@ -128,8 +135,9 @@ public class MultiplexerBase<T: Codable, C: Cacher>: MultiplexFetcher<T>, MuxRep
/// Writes the previously cached object to disk using the default cacher interface. For the `Multiplexer` class the default cacher is `JSONDiskCacher`.
@discardableResult
public func flush() -> Self {
if let previousValue = previousValue {
if isDirty, let previousValue = previousValue {
C.saveToCache(previousValue, key: Self.cacheKey, domain: nil)
isDirty = false
}
return self
}
Expand Down
3 changes: 2 additions & 1 deletion Multiplexer/MultiplexerMap.swift
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,9 @@ public class MultiplexerMapBase<T: Codable, C: Cacher>: MuxRepositoryProtocol {
@discardableResult
public func flush() -> Self {
fetcherMap.forEach { (key, fetcher) in
if let previousValue = fetcher.previousValue {
if fetcher.isDirty, let previousValue = fetcher.previousValue {
C.saveToCache(previousValue, key: key, domain: Self.cacheDomain)
fetcher.isDirty = false
}
}
return self
Expand Down
39 changes: 37 additions & 2 deletions Multiplexer/MuxRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@

import Foundation

#if !NO_UIKIT && os(iOS)
import UIKit
#endif


public protocol MuxRepositoryProtocol: class {
@discardableResult
Expand All @@ -29,16 +33,47 @@ public class MuxRepository {
repo.values.forEach { $0.clear() }
}

/// Writes all memory-cached objects to disk for each of the registered Multiplexer objects. The default implementations of `Multiplexer<T>` and `MultiplexerMap<T>` use simple file-based JSON caching. `flushAll()` can be called when the app is sent to background or terminated on iOS, i.e. on `applicationWillResignActive(_:)` and `applicationWillTerminate(_:)` (both, because the former is not called in certain scenarios, such as a low battery shutdown). Note that multiplexer objects themselves never write data automatically; i.e. the objects are cached only in memory unless you explicitly call `flush()` on a multiplexer, or `flushAll()` on the global repository.
/// Writes all memory-cached objects to disk for each of the registered Multiplexer objects. The default implementations of `Multiplexer<T>` and `MultiplexerMap<T>` use simple file-based JSON caching. On iOS MuxRepository can call this method automatically when the app is sent to background if you set `MuxRepository.automaticaFlush` to `true` (presumably at program startup).
public static func flushAll() {
repo.values.forEach { $0.flush() }
repo.values.forEach {
$0.flush()
}
}

/// Free all memory-cached objects. This will force all multiplexer objects make a new fetch on the next call to `request(completion:)`. This method can be called on memory warnings coming from the OS.
public static func clearMemory() {
repo.values.forEach { $0.clearMemory() }
}


#if !NO_UIKIT && os(iOS)

/// If set to `true`, automatically calls `flushAll()` each time the app is sent to background. `flushAll()` ensures only "dirty" objects are written to disk, i.e. those that haven't been written yet.
public static var automaticFlush: Bool = false {
didSet {
let center = NotificationCenter.default
if automaticFlush {
center.addObserver(self, selector: #selector(appWillMoveToBackground), name: UIApplication.willResignActiveNotification, object: nil)
}
else {
center.removeObserver(self, name: UIApplication.willResignActiveNotification, object: nil)
}
}
}

@objc static func appWillMoveToBackground() {
guard !repo.isEmpty else { return }
flushAll()
#if DEBUG
print("Flushing \(repo.count) registered multiplexers")
#endif
}

#endif


// - - -

private static var repo: [ObjectIdentifier: MuxRepositoryProtocol] = [:]

fileprivate static func register(mux: MuxRepositoryProtocol) {
Expand Down
5 changes: 5 additions & 0 deletions MultiplexerDemo/MultiplexerDemo/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
//

import UIKit
import Multiplexer


@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
Expand All @@ -15,6 +17,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate {

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.

MuxRepository.automaticFlush = true

return true
}
}
Expand Down
4 changes: 2 additions & 2 deletions MultiplexerDemo/MultiplexerDemo/CitiesViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ class CityCell: UITableViewCell {
class CitiesViewController: UITableViewController {

// Cache weather information per location for 30 minutes. This can be helpful when e.g. re-adding a previously removed city. Pull-to-refresh though causes a refresh of data anyway.
static var fullLocationMux = MultiplexerMap<FullLocation> { (id, onResult) in
static var fullLocationMux = MultiplexerMap<FullLocation>(onKeyFetch: { (id, onResult) in
Backend.fetchWeather(locationId: id, completion: onResult)
}
}).register()


private var locations: [FullLocation] = []
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ More information on each interface and their methods can be found in the source

`MuxRepository` is a static interface that can be used for centralized operations such as `clearAll()` and `flushAll()` on all multiplexer/downloader instances in your app. You should register each instance using the `register()` method on each multiplexer or downloader instance. Note that MuxRepository retains the objects, which generally should not be a problem for singletons. Use `unregister()` in case you need to release an instance previously registered with the repository.

By default, the Multiplexer and MultiplexerMap interfaces don't store objects on disk. If you want to keep the objects to ensure they can survive app reboots, call `MuxRepository.flushAll()` in your app's `applicationWillResignActive(_:)` and `applicationWillTerminate(_:)` (both, because the former is not called in certain scenarios, such as a low battery shutdown). Make sure `flushAll()` is performed only once, since in some scenarios both - applicationWillTerminate and applicationWillResignActive - can be called by the system.
By default, the Multiplexer and MultiplexerMap interfaces don't store objects on disk. If you want to keep the objects to ensure they can survive app reboots, enable the `MuxRepository.automaticFlush` option at program startup. This will ensure all registered multiplexers write their data on disk when the app enters background (iOS only).

`MuxRepository.clearAll()` discards all memory and disk objects. This is useful when e.g. the user signs out of your system and you need to make sure no traces are left of data related to the given user in memory or disk.

Expand Down

0 comments on commit 88ea7bc

Please sign in to comment.