Commit 8e13f07c by Demid Merzlyakov

Storage: optimized saving.

parent 2cfcb142
...@@ -45,49 +45,70 @@ public class LocationManager { ...@@ -45,49 +45,70 @@ public class LocationManager {
public typealias CurrentLocationCompletion = (LocationRequestResult) -> () public typealias CurrentLocationCompletion = (LocationRequestResult) -> ()
public private(set) var locations = [Location]() { private var _locations = [Location]()
didSet { private var _selectedLocationIndex: Int?
private func set(locations: [Location], selectedIndex: Int?) {
DispatchQueue.main.async { [weak self] in
self?._locations = locations
self?._selectedLocationIndex = selectedIndex
self?.handleLocationsChange(locationsChanged: true, selectedLocationChanged: true)
}
}
private func handleLocationsChange(locationsChanged: Bool, selectedLocationChanged: Bool) {
if locationsChanged {
log.info("Locations list updated: \(locations.map { $0.description }.joined(separator: ", "))") log.info("Locations list updated: \(locations.map { $0.description }.joined(separator: ", "))")
let newValue = locations
if let selectedIndex = self.selectedLocationIndex, selectedIndex >= newValue.count {
self.selectedLocationIndex = newValue.count > 0 ? 0 : nil
}
else {
updateAllWeatherIfNeeded() //TODO: the whole flow is not optimal.
self.delegates.invoke { (delegate) in
delegate.locationManager(self, changedSelectedLocation: self.selectedLocation)
}
}
self.delegates.invoke { [weak self] (delegate) in self.delegates.invoke { [weak self] (delegate) in
guard let self = self else { return } guard let self = self else { return }
delegate.locationManager(self, updatedLocationsList: self.locations) delegate.locationManager(self, updatedLocationsList: self.locations)
} }
storage.save(locations: locations, selectedIndex: selectedLocationIndex)
} }
if selectedLocationChanged {
let newLocation = self.selectedLocation
self.delegates.invoke { [weak self] (delegate) in
guard let self = self else { return }
delegate.locationManager(self, changedSelectedLocation: newLocation)
}
}
storage.save(locations: locations, selectedIndex: selectedLocationIndex)
updateAllWeatherIfNeeded()
} }
private var selectedLocationIndex: Int? { public private(set) var locations: [Location] {
didSet { get {
var oldLocation: Location? return _locations
if let oldValue = oldValue, oldValue < locations.count { }
oldLocation = locations[oldValue] set {
} let oldSelectedLocation = self.selectedLocation
var newLocation: Location?
if let newValue = selectedLocationIndex, newValue < locations.count { if let selectedLocation = self.selectedLocation {
newLocation = locations[newValue] if let newSelectedIndex = newValue.firstIndex(where: { $0 == selectedLocation }) {
_selectedLocationIndex = newSelectedIndex
}
else {
_selectedLocationIndex = nil
}
} }
if oldLocation?.description != newLocation?.description { if _selectedLocationIndex == nil && !newValue.isEmpty {
log.info("Current location changed to: \(selectedLocation?.description ?? "nil")") _selectedLocationIndex = 0
} }
log.info("Location updated.") _locations = newValue
DispatchQueue.main.async { let selectedLocationChanged = (oldSelectedLocation != self.selectedLocation)
self.delegates.invoke { [weak self] (delegate) in handleLocationsChange(locationsChanged: true, selectedLocationChanged: selectedLocationChanged)
guard let self = self else { return } }
delegate.locationManager(self, changedSelectedLocation: newLocation) }
}
private var selectedLocationIndex: Int? {
get {
return _selectedLocationIndex
}
set {
_selectedLocationIndex = newValue
if newValue == nil && !locations.isEmpty {
_selectedLocationIndex = 0
} }
updateAllWeatherIfNeeded() handleLocationsChange(locationsChanged: false, selectedLocationChanged: true)
} }
} }
...@@ -95,7 +116,12 @@ public class LocationManager { ...@@ -95,7 +116,12 @@ public class LocationManager {
get { get {
guard let index = selectedLocationIndex else { guard let index = selectedLocationIndex else {
// TODO: don't do it this way, because we won't be able to tell that no location is currently selected! // TODO: don't do it this way, because we won't be able to tell that no location is currently selected!
return defaultLocation if locations.count > 0 {
return locations.first
}
else {
return defaultLocation
}
} }
guard index < locations.count else { guard index < locations.count else {
assertionFailure("This shouldn't happen. Got to investigate.") assertionFailure("This shouldn't happen. Got to investigate.")
...@@ -137,17 +163,18 @@ public class LocationManager { ...@@ -137,17 +163,18 @@ public class LocationManager {
self.deviceLocationMonitor.delegate = self self.deviceLocationMonitor.delegate = self
storage.load { [weak self] (locations, selectedIndex, error) in storage.load { [weak self] (locations, selectedIndex, error) in
guard let self = self else { return } DispatchQueue.main.async {
guard error == nil else { guard let self = self else { return }
self.log.error("Error while loading locations: \(error!)") guard error == nil else {
return self.log.error("Error while loading locations: \(error!)")
} return
guard let locations = locations else { }
assertionFailure("Either error or locations have to be nil, not both") guard let locations = locations else {
return assertionFailure("Either error or locations have to be nil, not both")
return
}
self.set(locations: locations, selectedIndex: selectedIndex)
} }
self.locations = locations
self.selectedLocationIndex = selectedIndex
} }
} }
......
...@@ -65,6 +65,16 @@ public struct Location { ...@@ -65,6 +65,16 @@ public struct Location {
public var cityId: String { public var cityId: String {
return "\(self.countryCode ?? ""):\(self.region ?? ""):\(self.cityName ?? "")" return "\(self.countryCode ?? ""):\(self.region ?? ""):\(self.cityName ?? "")"
} }
public func equals(to other: Location, onlyCompareLocationInfo: Bool) -> Bool {
guard !onlyCompareLocationInfo else {
return self == other
}
guard self == other else { return false }
guard self.lastWeatherUpdateDate == other.lastWeatherUpdateDate else { return false }
guard self.health?.lastUpdateTime == other.health?.lastUpdateTime else { return false }
return true
}
} }
extension Location: Equatable, Hashable { extension Location: Equatable, Hashable {
......
...@@ -9,6 +9,7 @@ import Foundation ...@@ -9,6 +9,7 @@ import Foundation
import CoreData import CoreData
public class CoreDataStorage: Storage { public class CoreDataStorage: Storage {
private var lastSavedAppData: AppData? = nil
private let log = Logger(componentName: "CoreDataStorage 💾") private let log = Logger(componentName: "CoreDataStorage 💾")
private lazy var managedContext: NSManagedObjectContext? = { private lazy var managedContext: NSManagedObjectContext? = {
persistentContainer.newBackgroundContext() persistentContainer.newBackgroundContext()
...@@ -27,15 +28,20 @@ public class CoreDataStorage: Storage { ...@@ -27,15 +28,20 @@ public class CoreDataStorage: Storage {
log.info("Save: start") log.info("Save: start")
managedContext?.perform { [weak self] in managedContext?.perform { [weak self] in
guard let self = self else { return } guard let self = self else { return }
let appData = AppData(selectedIndex: selectedIndex, locations: locations)
guard appData != self.lastSavedAppData else {
self.log.info("Save: no changes, skip")
return
}
guard let context = self.managedContext else { guard let context = self.managedContext else {
return return
} }
do { do {
try self.deleteAll(in: context) try self.deleteAll(in: context)
let appData = AppData(selectedIndex: selectedIndex, locations: locations)
if let coreAppData = try CoreAppData(context: context, appModel: appData) { if let coreAppData = try CoreAppData(context: context, appModel: appData) {
context.insert(coreAppData) context.insert(coreAppData)
try self.save(context: context) try self.save(context: context)
self.lastSavedAppData = appData
} }
self.log.info("Save: success") self.log.info("Save: success")
} }
...@@ -52,31 +58,30 @@ public class CoreDataStorage: Storage { ...@@ -52,31 +58,30 @@ public class CoreDataStorage: Storage {
guard let context = self.managedContext else { guard let context = self.managedContext else {
return return
} }
let completionOnMain: StorageCompletion = { [weak self] (locations, selectedIndex, error) in let completionWithErrorHandling: StorageCompletion = { [weak self] (locations, selectedIndex, error) in
if error != nil { if error != nil {
self?.log.error("Load: error.") self?.log.error("Load: error.")
} }
else { else {
self?.log.info("Load: success. \(String(describing: locations?.count)) locations, selected: \(String(describing: selectedIndex))") self?.log.info("Load: success. \(String(describing: locations?.count)) locations, selected: \(String(describing: selectedIndex))")
} }
DispatchQueue.main.async { completion(locations, selectedIndex, error)
completion(locations, selectedIndex, error)
}
} }
do { do {
let fetchRequest: NSFetchRequest<CoreAppData> = CoreAppData.fetchRequest() let fetchRequest: NSFetchRequest<CoreAppData> = CoreAppData.fetchRequest()
fetchRequest.fetchLimit = 1 fetchRequest.fetchLimit = 1
let results = try context.fetch(fetchRequest) let results = try context.fetch(fetchRequest)
guard let coreAppData = results.first else { guard let coreAppData = results.first else {
completionOnMain([], nil, nil) completionWithErrorHandling([], nil, nil)
return return
} }
let appData: AppData = try coreAppData.toAppModel() let appData: AppData = try coreAppData.toAppModel()
completionOnMain(appData.locations, appData.selectedIndex, nil) self.lastSavedAppData = appData
completionWithErrorHandling(appData.locations, appData.selectedIndex, nil)
} }
catch { catch {
self.log.error("Error during load: \(error)") self.log.error("Error during load: \(error)")
completionOnMain(nil, nil, error) completionWithErrorHandling(nil, nil, error)
} }
} }
} }
......
...@@ -9,7 +9,19 @@ import Foundation ...@@ -9,7 +9,19 @@ import Foundation
/// A helper structure, so that we could work with CoreAppData the same way we work with everything else. /// A helper structure, so that we could work with CoreAppData the same way we work with everything else.
public struct AppData { public struct AppData: Equatable {
public let selectedIndex: Int? public let selectedIndex: Int?
public let locations: [Location] public let locations: [Location]
public static func == (lhs: Self, rhs: Self) -> Bool {
guard lhs.selectedIndex == rhs.selectedIndex else { return false }
guard lhs.locations.count == rhs.locations.count else { return false }
for (i, location) in lhs.locations.enumerated() {
let otherLocation = rhs.locations[i]
guard location.equals(to: otherLocation, onlyCompareLocationInfo: false) else {
return false
}
}
return true
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment