Commit a760d66e by Demid Merzlyakov

Storage: CoreData: CoreDataStorage implementation.

parent ea42db8c
...@@ -150,6 +150,7 @@ ...@@ -150,6 +150,7 @@
CE9F01BB261B31A6009BA500 /* CoreDataError.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE9F01BA261B31A6009BA500 /* CoreDataError.swift */; }; CE9F01BB261B31A6009BA500 /* CoreDataError.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE9F01BA261B31A6009BA500 /* CoreDataError.swift */; };
CE9F01BE261B34C0009BA500 /* CoreDataAppModelConvertable.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE9F01BD261B34C0009BA500 /* CoreDataAppModelConvertable.swift */; }; CE9F01BE261B34C0009BA500 /* CoreDataAppModelConvertable.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE9F01BD261B34C0009BA500 /* CoreDataAppModelConvertable.swift */; };
CE9F01C1261B3776009BA500 /* CoreDataUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE9F01C0261B3776009BA500 /* CoreDataUtils.swift */; }; CE9F01C1261B3776009BA500 /* CoreDataUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE9F01C0261B3776009BA500 /* CoreDataUtils.swift */; };
CE9F01CC261C9A6E009BA500 /* AppData.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE9F01CB261C9A6D009BA500 /* AppData.swift */; };
CEAD00A12577B2D5003596AD /* StuffThatIsPresentInTheMainProject.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEAD00A02577B2D5003596AD /* StuffThatIsPresentInTheMainProject.swift */; }; CEAD00A12577B2D5003596AD /* StuffThatIsPresentInTheMainProject.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEAD00A02577B2D5003596AD /* StuffThatIsPresentInTheMainProject.swift */; };
CEAFF08325DFC67F00DF4EBF /* Location.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEAFF08225DFC67F00DF4EBF /* Location.swift */; }; CEAFF08325DFC67F00DF4EBF /* Location.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEAFF08225DFC67F00DF4EBF /* Location.swift */; };
CEAFF08925DFC6B200DF4EBF /* CurrentWeather.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEAFF08825DFC6B200DF4EBF /* CurrentWeather.swift */; }; CEAFF08925DFC6B200DF4EBF /* CurrentWeather.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEAFF08825DFC6B200DF4EBF /* CurrentWeather.swift */; };
...@@ -332,6 +333,7 @@ ...@@ -332,6 +333,7 @@
CE9F01BA261B31A6009BA500 /* CoreDataError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataError.swift; sourceTree = "<group>"; }; CE9F01BA261B31A6009BA500 /* CoreDataError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataError.swift; sourceTree = "<group>"; };
CE9F01BD261B34C0009BA500 /* CoreDataAppModelConvertable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataAppModelConvertable.swift; sourceTree = "<group>"; }; CE9F01BD261B34C0009BA500 /* CoreDataAppModelConvertable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataAppModelConvertable.swift; sourceTree = "<group>"; };
CE9F01C0261B3776009BA500 /* CoreDataUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataUtils.swift; sourceTree = "<group>"; }; CE9F01C0261B3776009BA500 /* CoreDataUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataUtils.swift; sourceTree = "<group>"; };
CE9F01CB261C9A6D009BA500 /* AppData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppData.swift; sourceTree = "<group>"; };
CEAD00A02577B2D5003596AD /* StuffThatIsPresentInTheMainProject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StuffThatIsPresentInTheMainProject.swift; sourceTree = "<group>"; }; CEAD00A02577B2D5003596AD /* StuffThatIsPresentInTheMainProject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StuffThatIsPresentInTheMainProject.swift; sourceTree = "<group>"; };
CEAFF08225DFC67F00DF4EBF /* Location.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Location.swift; sourceTree = "<group>"; }; CEAFF08225DFC67F00DF4EBF /* Location.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Location.swift; sourceTree = "<group>"; };
CEAFF08825DFC6B200DF4EBF /* CurrentWeather.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentWeather.swift; sourceTree = "<group>"; }; CEAFF08825DFC6B200DF4EBF /* CurrentWeather.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentWeather.swift; sourceTree = "<group>"; };
...@@ -860,6 +862,7 @@ ...@@ -860,6 +862,7 @@
CE89628F26175DF400CA274A /* Objects */ = { CE89628F26175DF400CA274A /* Objects */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
CE9F01CB261C9A6D009BA500 /* AppData.swift */,
CE89629926175DF500CA274A /* Human */, CE89629926175DF500CA274A /* Human */,
CE89629026175DF400CA274A /* Machine */, CE89629026175DF400CA274A /* Machine */,
); );
...@@ -1227,6 +1230,7 @@ ...@@ -1227,6 +1230,7 @@
CD593BDC2608CDF100C93428 /* Date+Now.swift in Sources */, CD593BDC2608CDF100C93428 /* Date+Now.swift in Sources */,
CD17C60225D15C8500EE884E /* CoordinatorProtocol.swift in Sources */, CD17C60225D15C8500EE884E /* CoordinatorProtocol.swift in Sources */,
CDA5542825EF734200A2E08C /* TodayCellFactory.swift in Sources */, CDA5542825EF734200A2E08C /* TodayCellFactory.swift in Sources */,
CE9F01CC261C9A6E009BA500 /* AppData.swift in Sources */,
CEF959742600C3A400975FAA /* FlurryAnalyticsService.swift in Sources */, CEF959742600C3A400975FAA /* FlurryAnalyticsService.swift in Sources */,
CE2847602615A8AD006C8DC5 /* BlendHealthSource.swift in Sources */, CE2847602615A8AD006C8DC5 /* BlendHealthSource.swift in Sources */,
CD86C22225F0DCCB00F38A16 /* PrecipitationView.swift in Sources */, CD86C22225F0DCCB00F38A16 /* PrecipitationView.swift in Sources */,
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
<relationship name="health" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="CoreHealth" inverseName="airQuality" inverseEntity="CoreHealth"/> <relationship name="health" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="CoreHealth" inverseName="airQuality" inverseEntity="CoreHealth"/>
</entity> </entity>
<entity name="CoreAppData" representedClassName="CoreAppData" syncable="YES"> <entity name="CoreAppData" representedClassName="CoreAppData" syncable="YES">
<attribute name="selectedIndex" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/> <attribute name="selectedIndex" optional="YES" attributeType="Decimal" defaultValueString="0"/>
<relationship name="locations" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="CoreLocation" inverseName="appData" inverseEntity="CoreLocation"/> <relationship name="locations" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="CoreLocation" inverseName="appData" inverseEntity="CoreLocation"/>
</entity> </entity>
<entity name="CoreCurrentWeather" representedClassName="CoreCurrentWeather" syncable="YES"> <entity name="CoreCurrentWeather" representedClassName="CoreCurrentWeather" syncable="YES">
......
...@@ -29,34 +29,63 @@ public class CoreDataStorage: Storage { ...@@ -29,34 +29,63 @@ public class CoreDataStorage: Storage {
guard let context = self.managedContext else { guard let context = self.managedContext else {
return return
} }
self.deleteAll(in: context) do {
try self.deleteAll(in: context)
let appData = AppData(selectedIndex: selectedIndex, locations: locations)
if let coreAppData = try CoreAppData(context: context, appModel: appData) {
context.insert(coreAppData)
try self.save(context: context)
}
}
catch {
self.log.error("Error during saving: \(error)")
}
} }
} }
public func load(completion: StorageCompletion) { public func load(completion: @escaping StorageCompletion) {
managedContext?.perform { managedContext?.perform { [weak self] in
guard let self = self else { return }
guard let context = self.managedContext else {
return
}
let completionOnMain: StorageCompletion = { (locations, selectedIndex, error) in
DispatchQueue.main.async {
completion(locations, selectedIndex, error)
}
}
do {
let fetchRequest: NSFetchRequest<CoreAppData> = CoreAppData.fetchRequest()
fetchRequest.fetchLimit = 1
let results = try context.fetch(fetchRequest)
guard let coreAppData = results.first else {
completionOnMain([], nil, nil)
return
}
let appData: AppData = try coreAppData.toAppModel()
completionOnMain(appData.locations, appData.selectedIndex, nil)
}
catch {
self.log.error("Error during load: \(error)")
completionOnMain(nil, nil, error)
}
} }
} }
private func deleteAll(in context: NSManagedObjectContext) { private func deleteAll(in context: NSManagedObjectContext) throws {
#warning("Not implemented!") let fetchRequest: NSFetchRequest<CoreAppData> = CoreAppData.fetchRequest()
//TODO: implement let appDataObjects = try context.fetch(fetchRequest)
if appDataObjects.count > 1 {
log.warning("Somehow we ended up with more than 1 CoreAppData objects in the DB... deleting them all.")
}
for appData in appDataObjects {
context.delete(appData)
}
} }
private func saveContext() { private func save(context: NSManagedObjectContext) throws {
guard let context = managedContext else {
log.warning("saveContext: no context.")
return
}
if context.hasChanges { if context.hasChanges {
do { try context.save()
try context.save()
} catch {
let nserror = error as NSError
log.error("Error saving: \(nserror), \(nserror.userInfo)")
}
} }
} }
} }
//
// AppData.swift
// 1Weather
//
// Created by Demid Merzlyakov on 06.04.2021.
//
import Foundation
/// A helper structure, so that we could work with CoreAppData the same way we work with everything else.
public struct AppData {
public let selectedIndex: Int?
public let locations: [Location]
}
import Foundation import Foundation
import CoreData
@objc(CoreAppData) @objc(CoreAppData)
open class CoreAppData: _CoreAppData { open class CoreAppData: _CoreAppData, CoreDataAppModelConvertable {
typealias AppModel = AppData
func toAppModel() throws -> AppData {
var appModelLocations = [Location]()
try CoreDataUtils.foreach(in: self.locations, of: self, attributeName: "locations") { (coreLocation: CoreLocation) in
appModelLocations.append(try coreLocation.toAppModel())
}
let result = AppModel(selectedIndex: self.selectedIndex?.intValue, locations: appModelLocations)
return result
}
/// This is here just so that we could inherit the generated init(managedObjectContext) convenience initializer.
public override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?) {
super.init(entity: entity, insertInto: context)
}
required public init?(context: NSManagedObjectContext, appModel: AppData?) throws {
guard let appModel = appModel else {
return nil
}
self.init(managedObjectContext: context)
if let selectedIndex = appModel.selectedIndex {
self.selectedIndex = NSDecimalNumber(value: selectedIndex)
}
self.locations = NSOrderedSet(array: try appModel.locations.compactMap { try CoreLocation(context: context, appModel: $0)})
}
} }
...@@ -43,7 +43,7 @@ open class _CoreAppData: NSManagedObject { ...@@ -43,7 +43,7 @@ open class _CoreAppData: NSManagedObject {
// MARK: - Properties // MARK: - Properties
@NSManaged open @NSManaged open
var selectedIndex: Int16 // Optional scalars not supported var selectedIndex: NSDecimalNumber?
// MARK: - Relationships // MARK: - Relationships
......
...@@ -7,9 +7,9 @@ ...@@ -7,9 +7,9 @@
import Foundation import Foundation
public typealias StorageCompletion = ([Location]?, selectedIndex: Int, Error?) public typealias StorageCompletion = ([Location]?, Int?, Error?) -> ()
public protocol Storage { public protocol Storage {
func save(locations: [Location], selectedIndex: Int) func save(locations: [Location], selectedIndex: Int)
func load(completion: StorageCompletion) func load(completion: @escaping StorageCompletion)
} }
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