Commit 6ed5b084 by Demid Merzlyakov

Merge branch 'feature/storage'

parents f6ef091a 5c59605f
......@@ -128,8 +128,29 @@
CE578FE525FB415F00E8B85D /* CityCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE578FE225FB415F00E8B85D /* CityCell.swift */; };
CE578FE625FB415F00E8B85D /* LocationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE578FE325FB415F00E8B85D /* LocationViewController.swift */; };
CE578FE725FB415F00E8B85D /* LocationsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE578FE425FB415F00E8B85D /* LocationsViewModel.swift */; };
CE89628C26175D8D00CA274A /* regenerate_objects.sh in Resources */ = {isa = PBXBuildFile; fileRef = CE89628B26175D8D00CA274A /* regenerate_objects.sh */; };
CE8962A226175DF500CA274A /* _CoreAirQuality.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE89629126175DF400CA274A /* _CoreAirQuality.swift */; };
CE8962A426175DF500CA274A /* _CorePollutant.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE89629326175DF400CA274A /* _CorePollutant.swift */; };
CE8962A526175DF500CA274A /* _CoreHourlyWeather.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE89629426175DF500CA274A /* _CoreHourlyWeather.swift */; };
CE8962A626175DF500CA274A /* _CoreHealth.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE89629526175DF500CA274A /* _CoreHealth.swift */; };
CE8962A726175DF500CA274A /* _CoreLocation.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE89629626175DF500CA274A /* _CoreLocation.swift */; };
CE8962A826175DF500CA274A /* _CoreCurrentWeather.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE89629726175DF500CA274A /* _CoreCurrentWeather.swift */; };
CE8962A926175DF500CA274A /* _CoreDailyWeather.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE89629826175DF500CA274A /* _CoreDailyWeather.swift */; };
CE8962AA26175DF500CA274A /* CoreAirQuality.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE89629A26175DF500CA274A /* CoreAirQuality.swift */; };
CE8962AB26175DF500CA274A /* CorePollutant.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE89629B26175DF500CA274A /* CorePollutant.swift */; };
CE8962AC26175DF500CA274A /* CoreCurrentWeather.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE89629C26175DF500CA274A /* CoreCurrentWeather.swift */; };
CE8962AD26175DF500CA274A /* CoreHealth.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE89629D26175DF500CA274A /* CoreHealth.swift */; };
CE8962AE26175DF500CA274A /* CoreLocation.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE89629E26175DF500CA274A /* CoreLocation.swift */; };
CE8962AF26175DF500CA274A /* CoreDailyWeather.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE89629F26175DF500CA274A /* CoreDailyWeather.swift */; };
CE8962B126175DF500CA274A /* CoreHourlyWeather.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8962A126175DF500CA274A /* CoreHourlyWeather.swift */; };
CE9D181625ECB8370028D9D7 /* MulticastDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE9D181525ECB8370028D9D7 /* MulticastDelegate.swift */; };
CE9D181925ECB9A70028D9D7 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE9D181825ECB9A70028D9D7 /* Logger.swift */; };
CE9F01B5261B2DB1009BA500 /* CoreAppData.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE9F01B4261B2DB1009BA500 /* CoreAppData.swift */; };
CE9F01B8261B2DB6009BA500 /* _CoreAppData.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE9F01B7261B2DB6009BA500 /* _CoreAppData.swift */; };
CE9F01BB261B31A6009BA500 /* CoreDataError.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE9F01BA261B31A6009BA500 /* CoreDataError.swift */; };
CE9F01BE261B34C0009BA500 /* CoreDataAppModelConvertable.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE9F01BD261B34C0009BA500 /* CoreDataAppModelConvertable.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 */; };
CEAFF08325DFC67F00DF4EBF /* Location.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEAFF08225DFC67F00DF4EBF /* Location.swift */; };
CEAFF08925DFC6B200DF4EBF /* CurrentWeather.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEAFF08825DFC6B200DF4EBF /* CurrentWeather.swift */; };
......@@ -159,6 +180,9 @@
CEF959982600C88100975FAA /* AnalyticsParameter.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEF959972600C88100975FAA /* AnalyticsParameter.swift */; };
CEF9599F2601DF3300975FAA /* AdLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEF9599E2601DF3300975FAA /* AdLogger.swift */; };
CEF959A626035A2600975FAA /* DeviceLocationMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEF959A526035A2600975FAA /* DeviceLocationMonitor.swift */; };
CEFB857226174F7A00C5CDD2 /* Storage.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEFB857126174F7A00C5CDD2 /* Storage.swift */; };
CEFB85752617506100C5CDD2 /* CoreDataStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEFB85742617506100C5CDD2 /* CoreDataStorage.swift */; };
CEFB857A2617510700C5CDD2 /* 1WModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = CEFB85782617510700C5CDD2 /* 1WModel.xcdatamodeld */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
......@@ -287,8 +311,29 @@
CE578FE225FB415F00E8B85D /* CityCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CityCell.swift; sourceTree = "<group>"; };
CE578FE325FB415F00E8B85D /* LocationViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocationViewController.swift; sourceTree = "<group>"; };
CE578FE425FB415F00E8B85D /* LocationsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocationsViewModel.swift; sourceTree = "<group>"; };
CE89628B26175D8D00CA274A /* regenerate_objects.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = regenerate_objects.sh; sourceTree = "<group>"; };
CE89629126175DF400CA274A /* _CoreAirQuality.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _CoreAirQuality.swift; sourceTree = "<group>"; };
CE89629326175DF400CA274A /* _CorePollutant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _CorePollutant.swift; sourceTree = "<group>"; };
CE89629426175DF500CA274A /* _CoreHourlyWeather.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _CoreHourlyWeather.swift; sourceTree = "<group>"; };
CE89629526175DF500CA274A /* _CoreHealth.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _CoreHealth.swift; sourceTree = "<group>"; };
CE89629626175DF500CA274A /* _CoreLocation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _CoreLocation.swift; sourceTree = "<group>"; };
CE89629726175DF500CA274A /* _CoreCurrentWeather.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _CoreCurrentWeather.swift; sourceTree = "<group>"; };
CE89629826175DF500CA274A /* _CoreDailyWeather.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _CoreDailyWeather.swift; sourceTree = "<group>"; };
CE89629A26175DF500CA274A /* CoreAirQuality.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreAirQuality.swift; sourceTree = "<group>"; };
CE89629B26175DF500CA274A /* CorePollutant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CorePollutant.swift; sourceTree = "<group>"; };
CE89629C26175DF500CA274A /* CoreCurrentWeather.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreCurrentWeather.swift; sourceTree = "<group>"; };
CE89629D26175DF500CA274A /* CoreHealth.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreHealth.swift; sourceTree = "<group>"; };
CE89629E26175DF500CA274A /* CoreLocation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreLocation.swift; sourceTree = "<group>"; };
CE89629F26175DF500CA274A /* CoreDailyWeather.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreDailyWeather.swift; sourceTree = "<group>"; };
CE8962A126175DF500CA274A /* CoreHourlyWeather.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreHourlyWeather.swift; sourceTree = "<group>"; };
CE9D181525ECB8370028D9D7 /* MulticastDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MulticastDelegate.swift; sourceTree = "<group>"; };
CE9D181825ECB9A70028D9D7 /* Logger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
CE9F01B4261B2DB1009BA500 /* CoreAppData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreAppData.swift; sourceTree = "<group>"; };
CE9F01B7261B2DB6009BA500 /* _CoreAppData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _CoreAppData.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>"; };
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>"; };
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>"; };
......@@ -318,6 +363,9 @@
CEF959972600C88100975FAA /* AnalyticsParameter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsParameter.swift; sourceTree = "<group>"; };
CEF9599E2601DF3300975FAA /* AdLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdLogger.swift; sourceTree = "<group>"; };
CEF959A526035A2600975FAA /* DeviceLocationMonitor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceLocationMonitor.swift; sourceTree = "<group>"; };
CEFB857126174F7A00C5CDD2 /* Storage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Storage.swift; sourceTree = "<group>"; };
CEFB85742617506100C5CDD2 /* CoreDataStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataStorage.swift; sourceTree = "<group>"; };
CEFB85792617510700C5CDD2 /* Model.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model.xcdatamodel; sourceTree = "<group>"; };
DF826CF4702D9DCCB9A9DD71 /* Pods-1Weather.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-1Weather.release.xcconfig"; path = "Target Support Files/Pods-1Weather/Pods-1Weather.release.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
......@@ -403,6 +451,7 @@
87C171E725FF79CC00DA3464 /* Configuration */,
CE9D181425ECB8370028D9D7 /* Common */,
CEAFF09925DFC78200DF4EBF /* Network */,
CEFB856B26174F2E00C5CDD2 /* Storage */,
CEAFF08125DFC66F00DF4EBF /* Model */,
CD647D0025ED07AF0034578B /* ViewModels */,
CD17C5F925D15B5500EE884E /* Coordinators */,
......@@ -810,6 +859,46 @@
path = Cells;
sourceTree = "<group>";
};
CE89628F26175DF400CA274A /* Objects */ = {
isa = PBXGroup;
children = (
CE9F01CB261C9A6D009BA500 /* AppData.swift */,
CE89629926175DF500CA274A /* Human */,
CE89629026175DF400CA274A /* Machine */,
);
path = Objects;
sourceTree = "<group>";
};
CE89629026175DF400CA274A /* Machine */ = {
isa = PBXGroup;
children = (
CE9F01B7261B2DB6009BA500 /* _CoreAppData.swift */,
CE89629126175DF400CA274A /* _CoreAirQuality.swift */,
CE89629326175DF400CA274A /* _CorePollutant.swift */,
CE89629426175DF500CA274A /* _CoreHourlyWeather.swift */,
CE89629526175DF500CA274A /* _CoreHealth.swift */,
CE89629626175DF500CA274A /* _CoreLocation.swift */,
CE89629726175DF500CA274A /* _CoreCurrentWeather.swift */,
CE89629826175DF500CA274A /* _CoreDailyWeather.swift */,
);
path = Machine;
sourceTree = "<group>";
};
CE89629926175DF500CA274A /* Human */ = {
isa = PBXGroup;
children = (
CE9F01B4261B2DB1009BA500 /* CoreAppData.swift */,
CE89629A26175DF500CA274A /* CoreAirQuality.swift */,
CE89629B26175DF500CA274A /* CorePollutant.swift */,
CE89629C26175DF500CA274A /* CoreCurrentWeather.swift */,
CE89629D26175DF500CA274A /* CoreHealth.swift */,
CE89629E26175DF500CA274A /* CoreLocation.swift */,
CE89629F26175DF500CA274A /* CoreDailyWeather.swift */,
CE8962A126175DF500CA274A /* CoreHourlyWeather.swift */,
);
path = Human;
sourceTree = "<group>";
};
CE9D181425ECB8370028D9D7 /* Common */ = {
isa = PBXGroup;
children = (
......@@ -928,6 +1017,29 @@
path = Helpers;
sourceTree = "<group>";
};
CEFB856B26174F2E00C5CDD2 /* Storage */ = {
isa = PBXGroup;
children = (
CEFB857126174F7A00C5CDD2 /* Storage.swift */,
CEFB8577261750DF00C5CDD2 /* CoreData */,
);
path = Storage;
sourceTree = "<group>";
};
CEFB8577261750DF00C5CDD2 /* CoreData */ = {
isa = PBXGroup;
children = (
CEFB85742617506100C5CDD2 /* CoreDataStorage.swift */,
CEFB85782617510700C5CDD2 /* 1WModel.xcdatamodeld */,
CE89628B26175D8D00CA274A /* regenerate_objects.sh */,
CE89628F26175DF400CA274A /* Objects */,
CE9F01BA261B31A6009BA500 /* CoreDataError.swift */,
CE9F01BD261B34C0009BA500 /* CoreDataAppModelConvertable.swift */,
CE9F01C0261B3776009BA500 /* CoreDataUtils.swift */,
);
path = CoreData;
sourceTree = "<group>";
};
DBFD169AA2AA6A3CB5B68BB5 /* Frameworks */ = {
isa = PBXGroup;
children = (
......@@ -996,6 +1108,7 @@
buildActionMask = 2147483647;
files = (
CDD0F1E52572425200CF5017 /* SF-Pro.ttf in Resources */,
CE89628C26175D8D00CA274A /* regenerate_objects.sh in Resources */,
CDD75F0D25DE68B10099ACDB /* Localizable.strings in Resources */,
87C171ED25FF79CC00DA3464 /* LocalConfig.plist in Resources */,
CD1237CC255D5C5C00C98139 /* Assets.xcassets in Resources */,
......@@ -1054,6 +1167,7 @@
files = (
CD35DFD0260344A500F2138F /* ForecastConditionView.swift in Sources */,
CD2ABF32261489F700C1A92E /* LocationCellFactory.swift in Sources */,
CE8962B126175DF500CA274A /* CoreHourlyWeather.swift in Sources */,
CD82300325D69DE400A05501 /* TodayConditionsCell.swift in Sources */,
CEF959902600C5A800975FAA /* MoEngageAnalyticsService.swift in Sources */,
CEC526FD25E795F700DA58A5 /* WdtWeatherSource.swift in Sources */,
......@@ -1069,6 +1183,7 @@
87C171F425FF7A4000DA3464 /* PopularCitiesManager.swift in Sources */,
87C1724925FF94F400DA3464 /* ConfigManager.swift in Sources */,
CE9D181925ECB9A70028D9D7 /* Logger.swift in Sources */,
CE8962AE26175DF500CA274A /* CoreLocation.swift in Sources */,
CE578FD325F7E89400E8B85D /* DayTimeWeather.swift in Sources */,
CD593BCC2608A4F200C93428 /* ForecastDailyCell.swift in Sources */,
CEF959692600C30500975FAA /* Global.swift in Sources */,
......@@ -1078,13 +1193,17 @@
CE28474F26159857006C8DC5 /* HealthSource.swift in Sources */,
CEAFF08C25DFC6BD00DF4EBF /* DailyWeather.swift in Sources */,
CEDE4F0B25EFA3A7007457E9 /* UpdatableModelObject.swift in Sources */,
CE8962A626175DF500CA274A /* _CoreHealth.swift in Sources */,
CE28475226159A32006C8DC5 /* BlendHealthModels.swift in Sources */,
87C171EE25FF79CC00DA3464 /* AdConfigManager.swift in Sources */,
CE8962A926175DF500CA274A /* _CoreDailyWeather.swift in Sources */,
CDD0F1E82572429E00CF5017 /* AppFont.swift in Sources */,
CE8962AA26175DF500CA274A /* CoreAirQuality.swift in Sources */,
CE28475D2615A5B3006C8DC5 /* Health.swift in Sources */,
CEF9599F2601DF3300975FAA /* AdLogger.swift in Sources */,
CDC6124F25E7964700188DA7 /* TodayDayTimesCell.swift in Sources */,
CD593BC226088A5900C93428 /* TimePeriodOffsetHolder.swift in Sources */,
CE8962A226175DF500CA274A /* _CoreAirQuality.swift in Sources */,
CD17C5FB25D15B6B00EE884E /* AppCoordinator.swift in Sources */,
CD32CE0E260C770E00235081 /* MenuHeaderView.swift in Sources */,
CD15DB3D25DA6C5100024727 /* ForecastTimePeriodControl.swift in Sources */,
......@@ -1111,10 +1230,13 @@
CD593BDC2608CDF100C93428 /* Date+Now.swift in Sources */,
CD17C60225D15C8500EE884E /* CoordinatorProtocol.swift in Sources */,
CDA5542825EF734200A2E08C /* TodayCellFactory.swift in Sources */,
CE9F01CC261C9A6E009BA500 /* AppData.swift in Sources */,
CEF959742600C3A400975FAA /* FlurryAnalyticsService.swift in Sources */,
CE2847602615A8AD006C8DC5 /* BlendHealthSource.swift in Sources */,
CD86C22225F0DCCB00F38A16 /* PrecipitationView.swift in Sources */,
CE8962AD26175DF500CA274A /* CoreHealth.swift in Sources */,
CD17C5FF25D15B7C00EE884E /* TodayCoordinator.swift in Sources */,
CE9F01BE261B34C0009BA500 /* CoreDataAppModelConvertable.swift in Sources */,
CD822FF525D6817000A05501 /* TodayForecastCell.swift in Sources */,
CD2B2140260A366B00AB918A /* UIView+InterfaceStyle.swift in Sources */,
CD37D401260DF744002669D6 /* SettingsCell.swift in Sources */,
......@@ -1126,11 +1248,16 @@
CEDE4E8425EEFD56007457E9 /* WdtDailySummariesArray.swift in Sources */,
CDEE8AD725DA882200C289DE /* ForecastPeriodButton.swift in Sources */,
CDE18DD125D166F900C80ED9 /* ForecastViewController.swift in Sources */,
CE8962AC26175DF500CA274A /* CoreCurrentWeather.swift in Sources */,
CE9F01BB261B31A6009BA500 /* CoreDataError.swift in Sources */,
CD39F2F525DE9571009FE398 /* ArrowButton.swift in Sources */,
CE8962A526175DF500CA274A /* _CoreHourlyWeather.swift in Sources */,
CEDE4E8325EEFD56007457E9 /* WdtLocationResponse.swift in Sources */,
CD37D3FE260DF726002669D6 /* SettingsCellFactory.swift in Sources */,
CD8E041625F8F91B001785B6 /* ForecastCellFactory.swift in Sources */,
87C1720D25FF870600DA3464 /* GeoNamesPlace.swift in Sources */,
CE8962A826175DF500CA274A /* _CoreCurrentWeather.swift in Sources */,
CEFB857A2617510700C5CDD2 /* 1WModel.xcdatamodeld in Sources */,
CDC70833260FBFD4004A1974 /* UnitPressure+Atmosphere.swift in Sources */,
CDC6125325E79C8F00188DA7 /* DayTimeView.swift in Sources */,
CD251EDC26036E5400ED7A65 /* DayTimePrecipitationView.swift in Sources */,
......@@ -1142,6 +1269,7 @@
CE9D181625ECB8370028D9D7 /* MulticastDelegate.swift in Sources */,
CE578FE725FB415F00E8B85D /* LocationsViewModel.swift in Sources */,
CD82300A25D6B2AF00A05501 /* AppTabBarController.swift in Sources */,
CE9F01C1261B3776009BA500 /* CoreDataUtils.swift in Sources */,
CDC6126225E8DAB800188DA7 /* MoonPhaseCell.swift in Sources */,
CD37D3D6260C93B3002669D6 /* MenuCellFactory.swift in Sources */,
CD37D3FA260DF714002669D6 /* SettingsThemeCell.swift in Sources */,
......@@ -1150,16 +1278,21 @@
CD35DFCC260341B000F2138F /* Calendar+TimeZone.swift in Sources */,
CD9B6B1125DBC723001D9B80 /* CubicCurveAlgorithm.swift in Sources */,
CEC5270025E7BACB00DA58A5 /* WdtLocation.swift in Sources */,
CE8962A726175DF500CA274A /* _CoreLocation.swift in Sources */,
CD866A65260F642600E96A5C /* SettingsDetailsViewController.swift in Sources */,
CD647D0225ED07D60034578B /* TodayViewModel.swift in Sources */,
CD593BD32608BC3F00C93428 /* ForecastDayCell.swift in Sources */,
CD4742D0261200500061AC95 /* TodayAlertCell.swift in Sources */,
CD15DB4225DA806C00024727 /* TodayForecastTimePeriodCell.swift in Sources */,
CE8962AB26175DF500CA274A /* CorePollutant.swift in Sources */,
CEC5276025E92DDA00DA58A5 /* WdtHourlySummary.swift in Sources */,
CDE18DCA25D165F100C80ED9 /* UITabBarController+Append.swift in Sources */,
CE8962AF26175DF500CA274A /* CoreDailyWeather.swift in Sources */,
CDE2BF252609D9140085C930 /* ForecastWindButton.swift in Sources */,
CD32CE16260C77C600235081 /* MenuHeaderButton.swift in Sources */,
CE9F01B5261B2DB1009BA500 /* CoreAppData.swift in Sources */,
CD251ED82603633800ED7A65 /* ForecastPrecipitationCell.swift in Sources */,
CEFB85752617506100C5CDD2 /* CoreDataStorage.swift in Sources */,
CDF9BF8E26133D050037847D /* LocationSearchCoordinator.swift in Sources */,
CD86246125E662BC0097F3FB /* SunUvLineView.swift in Sources */,
CD32CE0B260C744A00235081 /* MenuCoordinator.swift in Sources */,
......@@ -1171,6 +1304,7 @@
CEF959A626035A2600975FAA /* DeviceLocationMonitor.swift in Sources */,
CD32CDFF260B2E5400235081 /* ForecastDescriptionView.swift in Sources */,
CEC5275D25E8E50B00DA58A5 /* WdtDailySummary.swift in Sources */,
CE9F01B8261B2DB6009BA500 /* _CoreAppData.swift in Sources */,
CDE18DCD25D1666700C80ED9 /* ForecastCoordinator.swift in Sources */,
CDC6126625E9085600188DA7 /* GraphLine.swift in Sources */,
CE578FE625FB415F00E8B85D /* LocationViewController.swift in Sources */,
......@@ -1178,6 +1312,7 @@
CD80917B2578E4A8003541A4 /* UIViewController+Alert.swift in Sources */,
CEF959932600C63500975FAA /* Analytics.swift in Sources */,
CEDE4F0F25EFA3B4007457E9 /* UpdatableModelObjectInTime.swift in Sources */,
CE8962A426175DF500CA274A /* _CorePollutant.swift in Sources */,
CD3F6E6925FA59D4002DB99B /* ForecastDetailPeriodButton.swift in Sources */,
CD37D405260DFFDD002669D6 /* CellFactory.swift in Sources */,
CD37D3F6260DF5BA002669D6 /* SettingsViewModel.swift in Sources */,
......@@ -1186,6 +1321,7 @@
CD86245E25E646350097F3FB /* SunUvView.swift in Sources */,
CEAFF08325DFC67F00DF4EBF /* Location.swift in Sources */,
87C171EC25FF79CC00DA3464 /* EnvironmentManager.swift in Sources */,
CEFB857226174F7A00C5CDD2 /* Storage.swift in Sources */,
CD82300725D6A73F00A05501 /* TodayConditionButton.swift in Sources */,
CDC6126A25E90C8800188DA7 /* GraphLineSettings.swift in Sources */,
CDA5542D25EF7C9700A2E08C /* ReusableCellProtocol.swift in Sources */,
......@@ -1397,6 +1533,19 @@
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCVersionGroup section */
CEFB85782617510700C5CDD2 /* 1WModel.xcdatamodeld */ = {
isa = XCVersionGroup;
children = (
CEFB85792617510700C5CDD2 /* Model.xcdatamodel */,
);
currentVersion = CEFB85792617510700C5CDD2 /* Model.xcdatamodel */;
path = 1WModel.xcdatamodeld;
sourceTree = "<group>";
versionGroupType = wrapper.xcdatamodel;
};
/* End XCVersionGroup section */
};
rootObject = CD1237B7255D5C5900C98139 /* Project object */;
}
......@@ -21,6 +21,7 @@ public class LocationManager {
private let weatherUpdateSource: WeatherSource
private let healthSource: HealthSource
private let storage: Storage
private var defaultLocation = Location(deviceLocation: false,
coordinates: .init(latitude: 37.3230, longitude: -122.0322), // Cupertino
timeZone: TimeZone(abbreviation: "PST")!) {
......@@ -44,49 +45,67 @@ public class LocationManager {
public typealias CurrentLocationCompletion = (LocationRequestResult) -> ()
public private(set) var locations = [Location]() {
didSet {
private var _locations = [Location]()
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: ", "))")
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
guard let self = self else { return }
delegate.locationManager(self, updatedLocationsList: self.locations)
}
}
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? {
didSet {
var oldLocation: Location?
if let oldValue = oldValue, oldValue < locations.count {
oldLocation = locations[oldValue]
}
var newLocation: Location?
if let newValue = selectedLocationIndex, newValue < locations.count {
newLocation = locations[newValue]
public private(set) var locations: [Location] {
get {
return _locations
}
set {
if let selectedLocation = self.selectedLocation {
if let newSelectedIndex = newValue.firstIndex(where: { $0 == selectedLocation }) {
_selectedLocationIndex = newSelectedIndex
}
else {
_selectedLocationIndex = nil
}
}
if oldLocation?.description != newLocation?.description {
log.info("Current location changed to: \(selectedLocation?.description ?? "nil")")
if _selectedLocationIndex == nil && !newValue.isEmpty {
_selectedLocationIndex = 0
}
log.info("Location updated.")
DispatchQueue.main.async {
self.delegates.invoke { [weak self] (delegate) in
guard let self = self else { return }
delegate.locationManager(self, changedSelectedLocation: newLocation)
}
_locations = newValue
handleLocationsChange(locationsChanged: true, selectedLocationChanged: true)
}
}
private var selectedLocationIndex: Int? {
get {
return _selectedLocationIndex
}
set {
_selectedLocationIndex = newValue
if newValue == nil && !locations.isEmpty {
_selectedLocationIndex = 0
}
updateAllWeatherIfNeeded()
handleLocationsChange(locationsChanged: false, selectedLocationChanged: true)
}
}
......@@ -94,7 +113,12 @@ public class LocationManager {
get {
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!
return defaultLocation
if locations.count > 0 {
return locations.first
}
else {
return defaultLocation
}
}
guard index < locations.count else {
assertionFailure("This shouldn't happen. Got to investigate.")
......@@ -125,14 +149,30 @@ public class LocationManager {
}
}
public static let shared = LocationManager(weatherUpdateSource: WdtWeatherSource(), healthSource: BlendHealthSource())
public static let shared = LocationManager(weatherUpdateSource: WdtWeatherSource(), healthSource: BlendHealthSource(), storage: CoreDataStorage())
public let maxLocationsCount = 12
public init(weatherUpdateSource: WeatherSource, healthSource: HealthSource) {
public init(weatherUpdateSource: WeatherSource, healthSource: HealthSource, storage: Storage) {
self.weatherUpdateSource = weatherUpdateSource
self.healthSource = healthSource
self.deviceLocationMonitor = DeviceLocationMonitor()
self.storage = storage
self.deviceLocationMonitor.delegate = self
storage.load { [weak self] (locations, selectedIndex, error) in
DispatchQueue.main.async {
guard let self = self else { return }
guard error == nil else {
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")
return
}
self.set(locations: locations, selectedIndex: selectedIndex)
}
}
}
public func updateAllWeatherIfNeeded() {
......@@ -218,7 +258,7 @@ public class LocationManager {
DispatchQueue.main.async {
self.log.info("Update weather finished for \(location)")
if let indexToUpdate = self.locations.firstIndex(where: { $0 == updatedLocation }) {
self.locations[indexToUpdate] = updatedLocation
self.locations[indexToUpdate] = self.locations[indexToUpdate].mergedWith(incrementalChanges: updatedLocation)
}
else if self.defaultLocation == updatedLocation {
self.defaultLocation = self.defaultLocation.mergedWith(incrementalChanges: updatedLocation)
......@@ -229,7 +269,6 @@ public class LocationManager {
}
}
}
// TODO: update weather for all locations
public func addIfNeeded(location: Location, selectLocation: Bool) {
// If the location is partially incomplete (e.g. coordinates are missing, or country / city / region), then some of this information will be returned by the weather update, but we may need to consider reverse geocoding here as well, if needed.
......
......@@ -65,6 +65,16 @@ public struct Location {
public var cityId: String {
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 {
......
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="17709" systemVersion="19H524" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="CoreAirQuality" representedClassName="CoreAirQuality" syncable="YES">
<attribute name="advice" attributeType="String"/>
<attribute name="index" attributeType="Double" defaultValueString="0.0" usesScalarValueType="YES"/>
<relationship name="health" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="CoreHealth" inverseName="airQuality" inverseEntity="CoreHealth"/>
</entity>
<entity name="CoreAppData" representedClassName="CoreAppData" syncable="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"/>
</entity>
<entity name="CoreCurrentWeather" representedClassName="CoreCurrentWeather" syncable="YES">
<attribute name="apparentTemp" optional="YES" attributeType="Binary"/>
<attribute name="approximateMoonrise" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="date" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="humidity" optional="YES" attributeType="Decimal" defaultValueString="0.0"/>
<attribute name="isDay" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="lastTimeUpdated" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="maxTemp" optional="YES" attributeType="Binary"/>
<attribute name="minTemp" optional="YES" attributeType="Binary"/>
<attribute name="moonPhase" optional="YES" attributeType="String"/>
<attribute name="moonrise" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="moonset" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="moonState" optional="YES" attributeType="String"/>
<attribute name="precipitationProbability" optional="YES" attributeType="Decimal" defaultValueString="0.0"/>
<attribute name="pressure" optional="YES" attributeType="Binary"/>
<attribute name="sunrise" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="sunset" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="sunState" optional="YES" attributeType="String"/>
<attribute name="temp" optional="YES" attributeType="Binary"/>
<attribute name="timeZone" attributeType="String"/>
<attribute name="type" attributeType="String"/>
<attribute name="visibility" optional="YES" attributeType="Binary"/>
<attribute name="weekDay" attributeType="String"/>
<attribute name="windDirection" optional="YES" attributeType="String"/>
<attribute name="windSpeed" optional="YES" attributeType="Binary"/>
<relationship name="location" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="CoreLocation" inverseName="today" inverseEntity="CoreLocation"/>
</entity>
<entity name="CoreDailyWeather" representedClassName="CoreDailyWeather" syncable="YES">
<attribute name="date" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="lastTimeUpdated" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="maxTemp" optional="YES" attributeType="Binary"/>
<attribute name="minTemp" optional="YES" attributeType="Binary"/>
<attribute name="moonPhase" optional="YES" attributeType="String"/>
<attribute name="moonrise" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="moonset" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="moonState" optional="YES" attributeType="String"/>
<attribute name="precipitationProbability" optional="YES" attributeType="Decimal" defaultValueString="0.0"/>
<attribute name="sunrise" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="sunset" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="sunState" optional="YES" attributeType="String"/>
<attribute name="timeZone" attributeType="String"/>
<attribute name="type" attributeType="String"/>
<attribute name="weekDay" attributeType="String"/>
<attribute name="windDirection" optional="YES" attributeType="String"/>
<attribute name="windSpeed" optional="YES" attributeType="Binary"/>
<relationship name="location" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="CoreLocation" inverseName="daily" inverseEntity="CoreLocation"/>
</entity>
<entity name="CoreHealth" representedClassName="CoreHealth" syncable="YES">
<attribute name="lastUpdateTime" attributeType="Date" usesScalarValueType="NO"/>
<relationship name="airQuality" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="CoreAirQuality" inverseName="health" inverseEntity="CoreAirQuality"/>
<relationship name="location" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="CoreLocation" inverseName="health" inverseEntity="CoreLocation"/>
<relationship name="pollutants" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="CorePollutant" inverseName="health" inverseEntity="CorePollutant"/>
</entity>
<entity name="CoreHourlyWeather" representedClassName="CoreHourlyWeather" syncable="YES">
<attribute name="apparentTemp" optional="YES" attributeType="Binary"/>
<attribute name="date" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="humidity" optional="YES" attributeType="Decimal" defaultValueString="0.0"/>
<attribute name="isDay" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="lastTimeUpdated" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="precipitationProbability" optional="YES" attributeType="Decimal" defaultValueString="0.0"/>
<attribute name="temp" optional="YES" attributeType="Binary"/>
<attribute name="timeZone" attributeType="String"/>
<attribute name="type" attributeType="String"/>
<attribute name="weekDay" attributeType="String"/>
<attribute name="windDirection" optional="YES" attributeType="String"/>
<attribute name="windSpeed" optional="YES" attributeType="Binary"/>
<relationship name="location" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="CoreLocation" inverseName="hourly" inverseEntity="CoreLocation"/>
</entity>
<entity name="CoreLocation" representedClassName="CoreLocation" syncable="YES">
<attribute name="cityName" optional="YES" attributeType="String"/>
<attribute name="countryCode" optional="YES" attributeType="String"/>
<attribute name="countryName" optional="YES" attributeType="String"/>
<attribute name="deviceLocation" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="fipsCode" optional="YES" attributeType="String"/>
<attribute name="imageName" optional="YES" attributeType="String"/>
<attribute name="lastWeatherUpdateDate" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="latitude" optional="YES" attributeType="Decimal"/>
<attribute name="longitude" optional="YES" attributeType="Decimal"/>
<attribute name="nickname" optional="YES" attributeType="String"/>
<attribute name="region" optional="YES" attributeType="String"/>
<attribute name="timeZone" attributeType="String"/>
<attribute name="zip" optional="YES" attributeType="String"/>
<relationship name="appData" maxCount="1" deletionRule="Nullify" destinationEntity="CoreAppData" inverseName="locations" inverseEntity="CoreAppData"/>
<relationship name="daily" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="CoreDailyWeather" inverseName="location" inverseEntity="CoreDailyWeather"/>
<relationship name="health" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="CoreHealth" inverseName="location" inverseEntity="CoreHealth"/>
<relationship name="hourly" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="CoreHourlyWeather" inverseName="location" inverseEntity="CoreHourlyWeather"/>
<relationship name="today" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="CoreCurrentWeather" inverseName="location" inverseEntity="CoreCurrentWeather"/>
</entity>
<entity name="CorePollutant" representedClassName="CorePollutant" syncable="YES">
<attribute name="name" attributeType="String"/>
<attribute name="value" attributeType="Double" defaultValueString="0.0" usesScalarValueType="YES"/>
<relationship name="health" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="CoreHealth" inverseName="pollutants" inverseEntity="CoreHealth"/>
</entity>
<elements>
<element name="CoreAppData" positionX="114.79296875" positionY="-494.5078125" width="128" height="73"/>
<element name="CoreAirQuality" positionX="438.48828125" positionY="-201.96875" width="128" height="88"/>
<element name="CoreCurrentWeather" positionX="-105.953125" positionY="-203.82421875" width="128" height="418"/>
<element name="CoreDailyWeather" positionX="270.5546875" positionY="-505.83203125" width="128" height="313"/>
<element name="CoreHealth" positionX="277.1640625" positionY="-156.77734375" width="128" height="103"/>
<element name="CoreHourlyWeather" positionX="279.15234375" positionY="-29.234375" width="128" height="238"/>
<element name="CoreLocation" positionX="113.6640625" positionY="-337.08984375" width="128" height="313"/>
<element name="CorePollutant" positionX="438.171875" positionY="-103.41015625" width="128" height="88"/>
</elements>
</model>
\ No newline at end of file
//
// CoreDataAppModelConvertable.swift
// 1Weather
//
// Created by Demid Merzlyakov on 05.04.2021.
//
import Foundation
import CoreData
protocol CoreDataAppModelConvertable {
associatedtype AppModel
func toAppModel() throws -> AppModel
init?(context: NSManagedObjectContext, appModel: AppModel?) throws
}
//
// CoreDataError.swift
// 1Weather
//
// Created by Demid Merzlyakov on 05.04.2021.
//
import Foundation
struct CoreDataError {
struct SaveAttributeError<T>: Error {
let entityName: String?
let attributeName: String
let attributeValue: T
let nestedError: Error?
init(entity: Any?, attributeName: String, value: T, nestedError: Error? = nil) {
if let entity = entity {
self.entityName = String(describing: type(of: entity))
}
else {
self.entityName = nil
}
self.attributeName = attributeName
self.attributeValue = value
self.nestedError = nestedError
}
}
struct LoadAttributeError<T>: Error {
let entityName: String?
let attributeName: String
let attributeValue: T
let nestedError: Error?
init(entity: Any?, attributeName: String, value: T, nestedError: Error? = nil) {
if let entity = entity {
self.entityName = String(describing: type(of: entity))
}
else {
self.entityName = nil
}
self.attributeName = attributeName
self.attributeValue = value
self.nestedError = nestedError
}
}
}
//
// CoreDataStorage.swift
// 1Weather
//
// Created by Demid Merzlyakov on 02.04.2021.
//
import Foundation
import CoreData
public class CoreDataStorage: Storage {
private var lastSavedAppData: AppData? = nil
private let log = Logger(componentName: "CoreDataStorage 💾")
private lazy var managedContext: NSManagedObjectContext? = {
persistentContainer.newBackgroundContext()
}()
private lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "1WModel")
container.loadPersistentStores { [weak self] (description, error) in
if let error = error {
self?.log.error("Error loading persistent stores: \(error)")
}
}
return container
}()
public func save(locations: [Location], selectedIndex: Int?) {
log.info("Save: start")
managedContext?.perform { [weak self] in
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 {
return
}
do {
try self.deleteAll(in: context)
if let coreAppData = try CoreAppData(context: context, appModel: appData) {
context.insert(coreAppData)
try self.save(context: context)
self.lastSavedAppData = appData
}
self.log.info("Save: success")
}
catch {
self.log.error("Save: error: \(error)")
}
}
}
public func load(completion: @escaping StorageCompletion) {
log.info("Load: start")
managedContext?.perform { [weak self] in
guard let self = self else { return }
guard let context = self.managedContext else {
return
}
let completionWithErrorHandling: StorageCompletion = { [weak self] (locations, selectedIndex, error) in
if error != nil {
self?.log.error("Load: error.")
}
else {
self?.log.info("Load: success. \(String(describing: locations?.count)) locations, selected: \(String(describing: selectedIndex))")
}
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 {
completionWithErrorHandling([], nil, nil)
return
}
let appData: AppData = try coreAppData.toAppModel()
self.lastSavedAppData = appData
completionWithErrorHandling(appData.locations, appData.selectedIndex, nil)
}
catch {
self.log.error("Error during load: \(error)")
completionWithErrorHandling(nil, nil, error)
}
}
}
private func deleteAll(in context: NSManagedObjectContext) throws {
log.debug("Delete: start")
let fetchRequest: NSFetchRequest<CoreAppData> = CoreAppData.fetchRequest()
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.")
}
if appDataObjects.count > 0 {
log.debug("Delete: \(appDataObjects.count) objects...")
}
for appData in appDataObjects {
context.delete(appData)
}
log.info("Delete: success")
}
private func save(context: NSManagedObjectContext) throws {
log.info("Context save: start")
if context.hasChanges {
try context.save()
log.info("Context save: success")
}
else {
log.info("Context save: no need")
}
}
}
//
// CoreDataUtils.swift
// 1Weather
//
// Created by Demid Merzlyakov on 05.04.2021.
//
import Foundation
import CoreData
struct CoreDataUtils {
public static func foreach<T>(in set: NSOrderedSet, of entity: Any, attributeName: String, do action: (T) throws -> ()) throws {
for elem in set {
guard let converted = elem as? T else {
throw CoreDataError.LoadAttributeError(entity: entity, attributeName: attributeName, value: set)
}
try action(converted)
}
}
public static func measurementToData<T>(_ measurement: Measurement<T>?, in entity: Any, attributeName: String) throws -> Data? {
guard let measurement = measurement else {
return nil
}
let encoder = JSONEncoder()
do {
let result = try encoder.encode(measurement)
return result
}
catch {
throw CoreDataError.SaveAttributeError(entity: entity, attributeName: attributeName, value: measurement, nestedError: error)
}
}
public static func measurement<T>(from data: Data?, in entity: Any, attributeName: String) throws -> Measurement<T>? {
guard let data = data else {
return nil
}
let decoder = JSONDecoder()
do {
let result = try decoder.decode(Measurement<T>.self, from: data)
return result
}
catch {
let valueStr = String(data: data, encoding: .utf8) ?? "<couldn't-turn-into-string>"
throw CoreDataError.LoadAttributeError(entity: entity, attributeName: attributeName, value: valueStr, nestedError: error)
}
}
public static func timeZone(from timeZoneString: String, in entity: Any, attributeName: String) throws -> TimeZone {
guard let timeZone = TimeZone(abbreviation: timeZoneString) else {
throw CoreDataError.LoadAttributeError(entity: entity, attributeName: attributeName, value: timeZoneString)
}
return timeZone
}
public static func timeZoneToString(_ timeZone: TimeZone, in entity: Any, attributeName: String) throws -> String {
guard let result = timeZone.abbreviation() else {
throw CoreDataError.SaveAttributeError(entity: entity, attributeName: attributeName, value: timeZone)
}
return result
}
public static func appValue<T>(name: String, value: T.RawValue, in entity: Any) throws -> T where T: RawRepresentable {
guard let result = T(rawValue: value) else {
throw CoreDataError.LoadAttributeError(entity: entity, attributeName: name, value: value)
}
return result
}
// I tried calling it just appValue, so that Swift would figure out wether to use this one or non-optional one, but it gets stuck in infinite recursion in this method occasionally.
public static func appValueOptional<T>(name: String, value: T.RawValue?, in entity: Any) throws -> T? where T: RawRepresentable {
guard let value = value else {
return nil
}
return try appValue(name: name, value: value, in: entity)
}
}
//
// 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: Equatable {
public let selectedIndex: Int?
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
}
}
import Foundation
import CoreData
@objc(CoreAirQuality)
open class CoreAirQuality: _CoreAirQuality, CoreDataAppModelConvertable {
func toAppModel() throws -> AirQuality {
let result = AirQuality(index: self.index, advice: self.advice)
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: AirQuality?) throws {
guard let appModel = appModel else {
return nil
}
self.init(managedObjectContext: context)
self.index = appModel.index
self.advice = appModel.advice
}
typealias AppModel = AirQuality
}
import Foundation
import CoreData
@objc(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)})
}
}
import Foundation
import CoreData
@objc(CoreCurrentWeather)
open class CoreCurrentWeather: _CoreCurrentWeather, CoreDataAppModelConvertable {
typealias AppModel = CurrentWeather
func toAppModel() throws -> CurrentWeather {
let timeZone = try CoreDataUtils.timeZone(from: self.timeZone, in: self, attributeName: "timeZone")
let weekDay: WeekDay = try CoreDataUtils.appValue(name: "weekDay", value: self.weekDay, in: self)
var result = CurrentWeather(lastTimeUpdated: self.lastTimeUpdated, date: self.date, timeZone: timeZone, weekDay: weekDay, isDay: self.isDay)
result.type = try CoreDataUtils.appValue(name: "type", value: self.type, in: self)
result.minTemp = try CoreDataUtils.measurement(from: self.minTemp, in: self, attributeName: "minTemp")
result.maxTemp = try CoreDataUtils.measurement(from: self.maxTemp, in: self, attributeName: "maxTemp")
result.windSpeed = try CoreDataUtils.measurement(from: self.windSpeed, in: self, attributeName: "windSpeed")
result.windDirection = try CoreDataUtils.appValueOptional(name: "windDirection", value: self.windDirection, in: self)
if let precipProb = self.precipitationProbability {
result.precipitationProbability = Percent(precipProb.uintValue)
}
result.temp = try CoreDataUtils.measurement(from: self.temp, in: self, attributeName: "temp")
result.apparentTemp = try CoreDataUtils.measurement(from: self.apparentTemp, in: self, attributeName: "apparentTemp")
if let humidity = self.humidity {
result.humidity = Percent(humidity.uintValue)
}
result.visibility = try CoreDataUtils.measurement(from: self.visibility, in: self, attributeName: "visibility")
result.pressure = try CoreDataUtils.measurement(from: self.pressure, in: self, attributeName: "pressure")
result.sunrise = self.sunrise
result.sunset = self.sunset
result.sunState = try CoreDataUtils.appValueOptional(name: "sunState", value: self.sunState, in: self)
result.moonrise = self.moonrise
result.moonset = self.moonset
result.approximateMoonrise = self.approximateMoonrise
result.moonState = try CoreDataUtils.appValueOptional(name: "moonState", value: self.moonState, in: self)
result.moonPhase = try CoreDataUtils.appValueOptional(name: "moonPhase", value: self.moonPhase, in: self)
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: CurrentWeather?) throws {
guard let appModel = appModel else {
return nil
}
self.init(managedObjectContext: context)
self.lastTimeUpdated = appModel.lastTimeUpdated
self.date = appModel.date
self.timeZone = try CoreDataUtils.timeZoneToString(appModel.timeZone, in: self, attributeName: "timeZone")
self.weekDay = appModel.weekDay.rawValue
self.type = appModel.type.rawValue
self.isDay = appModel.isDay
self.minTemp = try CoreDataUtils.measurementToData(appModel.minTemp, in: self, attributeName: "minTemp")
self.maxTemp = try CoreDataUtils.measurementToData(appModel.maxTemp, in: self, attributeName: "maxTemp")
self.windSpeed = try CoreDataUtils.measurementToData(appModel.windSpeed, in: self, attributeName: "windSpeed")
self.windDirection = appModel.windDirection?.rawValue
if let precipProb = appModel.precipitationProbability {
self.precipitationProbability = NSDecimalNumber(value: precipProb)
}
self.temp = try CoreDataUtils.measurementToData(appModel.temp, in: self, attributeName: "temp")
self.apparentTemp = try CoreDataUtils.measurementToData(appModel.apparentTemp, in: self, attributeName: "apparentTemp")
if let humidity = appModel.humidity {
self.humidity = NSDecimalNumber(value: humidity)
}
self.visibility = try CoreDataUtils.measurementToData(appModel.visibility, in: self, attributeName: "visibility")
self.pressure = try CoreDataUtils.measurementToData(appModel.pressure, in: self, attributeName: "pressure")
self.sunrise = appModel.sunrise
self.sunset = appModel.sunset
self.sunState = appModel.sunState?.rawValue
self.moonrise = appModel.moonrise
self.moonset = appModel.moonset
self.approximateMoonrise = appModel.approximateMoonrise
self.moonState = appModel.moonState?.rawValue
self.moonPhase = appModel.moonPhase?.rawValue
}
}
import Foundation
import CoreData
@objc(CoreDailyWeather)
open class CoreDailyWeather: _CoreDailyWeather, CoreDataAppModelConvertable {
typealias AppModel = DailyWeather
func toAppModel() throws -> DailyWeather {
let timeZone = try CoreDataUtils.timeZone(from: self.timeZone, in: self, attributeName: "timeZone")
let weekDay: WeekDay = try CoreDataUtils.appValue(name: "weekDay", value: self.weekDay, in: self)
var result = DailyWeather(lastTimeUpdated: lastTimeUpdated, date: date, timeZone: timeZone, weekDay: weekDay)
result.type = try CoreDataUtils.appValue(name: "type", value: self.type, in: self)
result.minTemp = try CoreDataUtils.measurement(from: self.minTemp, in: self, attributeName: "minTemp")
result.maxTemp = try CoreDataUtils.measurement(from: self.maxTemp, in: self, attributeName: "maxTemp")
result.windSpeed = try CoreDataUtils.measurement(from: self.windSpeed, in: self, attributeName: "windSpeed")
result.windDirection = try CoreDataUtils.appValueOptional(name: "windDirection", value: self.windDirection, in: self)
if let precipProb = self.precipitationProbability {
result.precipitationProbability = Percent(precipProb.uintValue)
}
result.sunrise = self.sunrise
result.sunset = self.sunset
result.sunState = try CoreDataUtils.appValueOptional(name: "sunState", value: self.sunState, in: self)
result.moonrise = self.moonrise
result.moonset = self.moonset
result.moonState = try CoreDataUtils.appValueOptional(name: "moonState", value: self.moonState, in: self)
result.moonPhase = try CoreDataUtils.appValueOptional(name: "moonPhase", value: self.moonPhase, in: self)
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: DailyWeather?) throws {
guard let appModel = appModel else {
return nil
}
self.init(managedObjectContext: context)
self.date = appModel.date
self.lastTimeUpdated = appModel.lastTimeUpdated
self.minTemp = try CoreDataUtils.measurementToData(appModel.minTemp, in: self, attributeName: "minTemp")
self.maxTemp = try CoreDataUtils.measurementToData(appModel.maxTemp, in: self, attributeName: "maxTemp")
self.moonPhase = appModel.moonPhase?.rawValue
self.moonrise = appModel.moonrise
self.moonset = appModel.moonset
self.moonState = appModel.moonState?.rawValue
if let precipProb = appModel.precipitationProbability {
self.precipitationProbability = NSDecimalNumber(value: precipProb)
}
self.sunrise = appModel.sunrise
self.sunset = appModel.sunset
self.sunState = appModel.sunState?.rawValue
self.timeZone = try CoreDataUtils.timeZoneToString(appModel.timeZone, in: self, attributeName: "timeZone")
self.type = appModel.type.rawValue
self.weekDay = appModel.weekDay.rawValue
self.windDirection = appModel.windDirection?.rawValue
self.windSpeed = try CoreDataUtils.measurementToData(appModel.windSpeed, in: self, attributeName: "windSpeed")
}
}
import Foundation
import CoreData
@objc(CoreHealth)
open class CoreHealth: _CoreHealth, CoreDataAppModelConvertable {
func toAppModel() throws -> Health {
let airQuality = try self.airQuality?.toAppModel()
var pollutants = [String: Pollutant]()
try CoreDataUtils.foreach(in: self.pollutants, of: self, attributeName: "pollutants") { (corePollutant: CorePollutant) in
let pollutant = try corePollutant.toAppModel()
pollutants[pollutant.name] = pollutant
}
let result = Health(lastUpdateTime: self.lastUpdateTime, airQuality: airQuality, pollutants: pollutants)
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: Health?) throws {
guard let appModel = appModel else {
return nil
}
self.init(managedObjectContext: context)
self.lastUpdateTime = appModel.lastUpdateTime
self.airQuality = try CoreAirQuality(context: context, appModel: appModel.airQuality)
self.pollutants = NSOrderedSet(array: try appModel.pollutants.compactMap { try CorePollutant(context: context, appModel: $0.value) })
}
typealias AppModel = Health
}
import Foundation
import CoreData
@objc(CoreHourlyWeather)
open class CoreHourlyWeather: _CoreHourlyWeather, CoreDataAppModelConvertable {
func toAppModel() throws -> HourlyWeather {
let timeZone = try CoreDataUtils.timeZone(from: self.timeZone, in: self, attributeName: "timeZone")
let weekDay: WeekDay = try CoreDataUtils.appValue(name: "weekDay", value: self.weekDay, in: self)
var result = HourlyWeather(lastTimeUpdated: self.lastTimeUpdated, date: self.date, timeZone: timeZone, weekDay: weekDay, isDay: self.isDay)
result.type = try CoreDataUtils.appValue(name: "type", value: self.type, in: self)
result.isDay = self.isDay
result.temp = try CoreDataUtils.measurement(from: self.temp, in: self, attributeName: "temp")
result.apparentTemp = try CoreDataUtils.measurement(from: self.apparentTemp, in: self, attributeName: "apparentTemp")
result.windSpeed = try CoreDataUtils.measurement(from: self.windSpeed, in: self, attributeName: "windSpeed")
result.windDirection = try CoreDataUtils.appValueOptional(name: "windDirection", value: self.windDirection, in: self)
if let precipProb = self.precipitationProbability {
result.precipitationProbability = Percent(precipProb.uintValue)
}
if let humidity = self.humidity {
result.humidity = Percent(humidity.uintValue)
}
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: HourlyWeather?) throws {
guard let appModel = appModel else {
return nil
}
self.init(managedObjectContext: context)
self.lastTimeUpdated = appModel.lastTimeUpdated
self.date = appModel.date
self.timeZone = try CoreDataUtils.timeZoneToString(appModel.timeZone, in: self, attributeName: "timeZone")
self.weekDay = appModel.weekDay.rawValue
self.type = appModel.type.rawValue
self.isDay = appModel.isDay
self.temp = try CoreDataUtils.measurementToData(appModel.temp, in: self, attributeName: "temp")
self.apparentTemp = try CoreDataUtils.measurementToData(appModel.apparentTemp, in: self, attributeName: "apparentTemp")
self.windSpeed = try CoreDataUtils.measurementToData(appModel.windSpeed, in: self, attributeName: "windSpeed")
self.windDirection = appModel.windDirection?.rawValue
if let precipProb = appModel.precipitationProbability {
self.precipitationProbability = NSDecimalNumber(value: precipProb)
}
if let humidity = appModel.humidity {
self.humidity = NSDecimalNumber(value: humidity)
}
}
typealias AppModel = HourlyWeather
}
import Foundation
import CoreData
@objc(CoreLocation)
open class CoreLocation: _CoreLocation, CoreDataAppModelConvertable {
typealias AppModel = Location
func toAppModel() throws -> Location {
let timeZone = try CoreDataUtils.timeZone(from: self.timeZone, in: self, attributeName: "timeZone")
var location = Location(deviceLocation: self.deviceLocation, timeZone: timeZone)
location.lastWeatherUpdateDate = self.lastWeatherUpdateDate
if let lat = self.latitude?.doubleValue, let lon = self.longitude?.doubleValue {
location.coordinates = CLLocationCoordinate2D(latitude: lat, longitude: lon)
}
location.imageName = self.imageName
location.countryCode = self.countryCode
location.countryName = self.countryName
location.region = self.region
location.cityName = self.cityName
location.nickname = self.nickname
location.zip = self.zip
location.fipsCode = self.fipsCode
location.today = try (self.today?.toAppModel())
try CoreDataUtils.foreach(in: self.daily, of: self, attributeName: "daily") { (coreDaily: CoreDailyWeather) in
location.daily.append(try coreDaily.toAppModel())
}
try CoreDataUtils.foreach(in: self.hourly, of: self, attributeName: "hourly") { (coreHourly: CoreHourlyWeather) in
location.hourly.append(try coreHourly.toAppModel())
}
location.health = try self.health?.toAppModel()
return location
}
/// 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: Location?) throws {
guard let appModel = appModel else {
return nil
}
self.init(managedObjectContext: context)
self.deviceLocation = appModel.deviceLocation
self.lastWeatherUpdateDate = appModel.lastWeatherUpdateDate
if let coordinates = appModel.coordinates {
self.latitude = NSDecimalNumber(value: coordinates.latitude)
self.longitude = NSDecimalNumber(value: coordinates.longitude)
}
self.imageName = appModel.imageName
self.countryCode = appModel.countryCode
self.countryName = appModel.countryName
self.region = appModel.region
self.cityName = appModel.cityName
self.nickname = appModel.nickname
self.zip = appModel.zip
self.fipsCode = appModel.fipsCode
self.timeZone = try CoreDataUtils.timeZoneToString(appModel.timeZone, in: self, attributeName: "timeZone")
self.today = skipIfError(attribute: "today", action: {try CoreCurrentWeather(context: context, appModel: appModel.today)})
self.daily = NSOrderedSet(array: appModel.daily.compactMap {elem in
skipIfError(attribute: "daily") {
try CoreDailyWeather(context: context, appModel: elem)
}
})
self.hourly = NSOrderedSet(array: appModel.hourly.compactMap {elem in
skipIfError(attribute: "hourly") {
try CoreHourlyWeather(context: context, appModel: elem)
}
})
self.health = skipIfError(attribute: "health", action: { try CoreHealth(context: context, appModel: appModel.health) })
}
private func skipIfError<T>(attribute: String, action: () throws -> T?) -> T? {
do {
let result = try action()
return result
}
catch {
Logger(componentName: "CoreLocation").error("Couldn't parse \(attribute) in CoreLocation due to error: \(error)")
return nil
}
}
}
import Foundation
import CoreData
@objc(CorePollutant)
open class CorePollutant: _CorePollutant, CoreDataAppModelConvertable {
func toAppModel() throws -> Pollutant {
let result = Pollutant(name: self.name, value: self.value)
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: Pollutant?) throws {
guard let appModel = appModel else {
return nil
}
self.init(managedObjectContext: context)
self.name = appModel.name
self.value = appModel.value
}
typealias AppModel = Pollutant
}
// DO NOT EDIT. This file is machine-generated and constantly overwritten.
// Make changes to CoreAirQuality.swift instead.
import Foundation
import CoreData
public enum CoreAirQualityAttributes: String {
case advice = "advice"
case index = "index"
}
public enum CoreAirQualityRelationships: String {
case health = "health"
}
open class _CoreAirQuality: NSManagedObject {
// MARK: - Class methods
open class func entityName () -> String {
return "CoreAirQuality"
}
open class func entity(managedObjectContext: NSManagedObjectContext) -> NSEntityDescription? {
return NSEntityDescription.entity(forEntityName: self.entityName(), in: managedObjectContext)
}
@nonobjc
open class func fetchRequest() -> NSFetchRequest<CoreAirQuality> {
return NSFetchRequest(entityName: self.entityName())
}
// MARK: - Life cycle methods
public override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?) {
super.init(entity: entity, insertInto: context)
}
public convenience init?(managedObjectContext: NSManagedObjectContext) {
guard let entity = _CoreAirQuality.entity(managedObjectContext: managedObjectContext) else { return nil }
self.init(entity: entity, insertInto: managedObjectContext)
}
// MARK: - Properties
@NSManaged open
var advice: String!
@NSManaged open
var index: Double
// MARK: - Relationships
@NSManaged open
var health: CoreHealth?
}
// DO NOT EDIT. This file is machine-generated and constantly overwritten.
// Make changes to CoreAppData.swift instead.
import Foundation
import CoreData
public enum CoreAppDataAttributes: String {
case selectedIndex = "selectedIndex"
}
public enum CoreAppDataRelationships: String {
case locations = "locations"
}
open class _CoreAppData: NSManagedObject {
// MARK: - Class methods
open class func entityName () -> String {
return "CoreAppData"
}
open class func entity(managedObjectContext: NSManagedObjectContext) -> NSEntityDescription? {
return NSEntityDescription.entity(forEntityName: self.entityName(), in: managedObjectContext)
}
@nonobjc
open class func fetchRequest() -> NSFetchRequest<CoreAppData> {
return NSFetchRequest(entityName: self.entityName())
}
// MARK: - Life cycle methods
public override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?) {
super.init(entity: entity, insertInto: context)
}
public convenience init?(managedObjectContext: NSManagedObjectContext) {
guard let entity = _CoreAppData.entity(managedObjectContext: managedObjectContext) else { return nil }
self.init(entity: entity, insertInto: managedObjectContext)
}
// MARK: - Properties
@NSManaged open
var selectedIndex: NSDecimalNumber?
// MARK: - Relationships
@NSManaged open
var locations: NSOrderedSet
open func locationsSet() -> NSMutableOrderedSet {
return self.locations.mutableCopy() as! NSMutableOrderedSet
}
}
extension _CoreAppData {
open func addLocations(_ objects: NSOrderedSet) {
let mutable = self.locations.mutableCopy() as! NSMutableOrderedSet
mutable.union(objects)
self.locations = mutable.copy() as! NSOrderedSet
}
open func removeLocations(_ objects: NSOrderedSet) {
let mutable = self.locations.mutableCopy() as! NSMutableOrderedSet
mutable.minus(objects)
self.locations = mutable.copy() as! NSOrderedSet
}
open func addLocationsObject(_ value: CoreLocation) {
let mutable = self.locations.mutableCopy() as! NSMutableOrderedSet
mutable.add(value)
self.locations = mutable.copy() as! NSOrderedSet
}
open func removeLocationsObject(_ value: CoreLocation) {
let mutable = self.locations.mutableCopy() as! NSMutableOrderedSet
mutable.remove(value)
self.locations = mutable.copy() as! NSOrderedSet
}
}
// DO NOT EDIT. This file is machine-generated and constantly overwritten.
// Make changes to CoreCurrentWeather.swift instead.
import Foundation
import CoreData
public enum CoreCurrentWeatherAttributes: String {
case apparentTemp = "apparentTemp"
case approximateMoonrise = "approximateMoonrise"
case date = "date"
case humidity = "humidity"
case isDay = "isDay"
case lastTimeUpdated = "lastTimeUpdated"
case maxTemp = "maxTemp"
case minTemp = "minTemp"
case moonPhase = "moonPhase"
case moonState = "moonState"
case moonrise = "moonrise"
case moonset = "moonset"
case precipitationProbability = "precipitationProbability"
case pressure = "pressure"
case sunState = "sunState"
case sunrise = "sunrise"
case sunset = "sunset"
case temp = "temp"
case timeZone = "timeZone"
case type = "type"
case visibility = "visibility"
case weekDay = "weekDay"
case windDirection = "windDirection"
case windSpeed = "windSpeed"
}
public enum CoreCurrentWeatherRelationships: String {
case location = "location"
}
open class _CoreCurrentWeather: NSManagedObject {
// MARK: - Class methods
open class func entityName () -> String {
return "CoreCurrentWeather"
}
open class func entity(managedObjectContext: NSManagedObjectContext) -> NSEntityDescription? {
return NSEntityDescription.entity(forEntityName: self.entityName(), in: managedObjectContext)
}
@nonobjc
open class func fetchRequest() -> NSFetchRequest<CoreCurrentWeather> {
return NSFetchRequest(entityName: self.entityName())
}
// MARK: - Life cycle methods
public override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?) {
super.init(entity: entity, insertInto: context)
}
public convenience init?(managedObjectContext: NSManagedObjectContext) {
guard let entity = _CoreCurrentWeather.entity(managedObjectContext: managedObjectContext) else { return nil }
self.init(entity: entity, insertInto: managedObjectContext)
}
// MARK: - Properties
@NSManaged open
var apparentTemp: Data?
@NSManaged open
var approximateMoonrise: Date?
@NSManaged open
var date: Date!
@NSManaged open
var humidity: NSDecimalNumber?
@NSManaged open
var isDay: Bool
@NSManaged open
var lastTimeUpdated: Date!
@NSManaged open
var maxTemp: Data?
@NSManaged open
var minTemp: Data?
@NSManaged open
var moonPhase: String?
@NSManaged open
var moonState: String?
@NSManaged open
var moonrise: Date?
@NSManaged open
var moonset: Date?
@NSManaged open
var precipitationProbability: NSDecimalNumber?
@NSManaged open
var pressure: Data?
@NSManaged open
var sunState: String?
@NSManaged open
var sunrise: Date?
@NSManaged open
var sunset: Date?
@NSManaged open
var temp: Data?
@NSManaged open
var timeZone: String!
@NSManaged open
var type: String!
@NSManaged open
var visibility: Data?
@NSManaged open
var weekDay: String!
@NSManaged open
var windDirection: String?
@NSManaged open
var windSpeed: Data?
// MARK: - Relationships
@NSManaged open
var location: CoreLocation?
}
// DO NOT EDIT. This file is machine-generated and constantly overwritten.
// Make changes to CoreDailyWeather.swift instead.
import Foundation
import CoreData
public enum CoreDailyWeatherAttributes: String {
case date = "date"
case lastTimeUpdated = "lastTimeUpdated"
case maxTemp = "maxTemp"
case minTemp = "minTemp"
case moonPhase = "moonPhase"
case moonState = "moonState"
case moonrise = "moonrise"
case moonset = "moonset"
case precipitationProbability = "precipitationProbability"
case sunState = "sunState"
case sunrise = "sunrise"
case sunset = "sunset"
case timeZone = "timeZone"
case type = "type"
case weekDay = "weekDay"
case windDirection = "windDirection"
case windSpeed = "windSpeed"
}
public enum CoreDailyWeatherRelationships: String {
case location = "location"
}
open class _CoreDailyWeather: NSManagedObject {
// MARK: - Class methods
open class func entityName () -> String {
return "CoreDailyWeather"
}
open class func entity(managedObjectContext: NSManagedObjectContext) -> NSEntityDescription? {
return NSEntityDescription.entity(forEntityName: self.entityName(), in: managedObjectContext)
}
@nonobjc
open class func fetchRequest() -> NSFetchRequest<CoreDailyWeather> {
return NSFetchRequest(entityName: self.entityName())
}
// MARK: - Life cycle methods
public override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?) {
super.init(entity: entity, insertInto: context)
}
public convenience init?(managedObjectContext: NSManagedObjectContext) {
guard let entity = _CoreDailyWeather.entity(managedObjectContext: managedObjectContext) else { return nil }
self.init(entity: entity, insertInto: managedObjectContext)
}
// MARK: - Properties
@NSManaged open
var date: Date!
@NSManaged open
var lastTimeUpdated: Date!
@NSManaged open
var maxTemp: Data?
@NSManaged open
var minTemp: Data?
@NSManaged open
var moonPhase: String?
@NSManaged open
var moonState: String?
@NSManaged open
var moonrise: Date?
@NSManaged open
var moonset: Date?
@NSManaged open
var precipitationProbability: NSDecimalNumber?
@NSManaged open
var sunState: String?
@NSManaged open
var sunrise: Date?
@NSManaged open
var sunset: Date?
@NSManaged open
var timeZone: String!
@NSManaged open
var type: String!
@NSManaged open
var weekDay: String!
@NSManaged open
var windDirection: String?
@NSManaged open
var windSpeed: Data?
// MARK: - Relationships
@NSManaged open
var location: CoreLocation?
}
// DO NOT EDIT. This file is machine-generated and constantly overwritten.
// Make changes to CoreHealth.swift instead.
import Foundation
import CoreData
public enum CoreHealthAttributes: String {
case lastUpdateTime = "lastUpdateTime"
}
public enum CoreHealthRelationships: String {
case airQuality = "airQuality"
case location = "location"
case pollutants = "pollutants"
}
open class _CoreHealth: NSManagedObject {
// MARK: - Class methods
open class func entityName () -> String {
return "CoreHealth"
}
open class func entity(managedObjectContext: NSManagedObjectContext) -> NSEntityDescription? {
return NSEntityDescription.entity(forEntityName: self.entityName(), in: managedObjectContext)
}
@nonobjc
open class func fetchRequest() -> NSFetchRequest<CoreHealth> {
return NSFetchRequest(entityName: self.entityName())
}
// MARK: - Life cycle methods
public override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?) {
super.init(entity: entity, insertInto: context)
}
public convenience init?(managedObjectContext: NSManagedObjectContext) {
guard let entity = _CoreHealth.entity(managedObjectContext: managedObjectContext) else { return nil }
self.init(entity: entity, insertInto: managedObjectContext)
}
// MARK: - Properties
@NSManaged open
var lastUpdateTime: Date!
// MARK: - Relationships
@NSManaged open
var airQuality: CoreAirQuality?
@NSManaged open
var location: CoreLocation?
@NSManaged open
var pollutants: NSOrderedSet
open func pollutantsSet() -> NSMutableOrderedSet {
return self.pollutants.mutableCopy() as! NSMutableOrderedSet
}
}
extension _CoreHealth {
open func addPollutants(_ objects: NSOrderedSet) {
let mutable = self.pollutants.mutableCopy() as! NSMutableOrderedSet
mutable.union(objects)
self.pollutants = mutable.copy() as! NSOrderedSet
}
open func removePollutants(_ objects: NSOrderedSet) {
let mutable = self.pollutants.mutableCopy() as! NSMutableOrderedSet
mutable.minus(objects)
self.pollutants = mutable.copy() as! NSOrderedSet
}
open func addPollutantsObject(_ value: CorePollutant) {
let mutable = self.pollutants.mutableCopy() as! NSMutableOrderedSet
mutable.add(value)
self.pollutants = mutable.copy() as! NSOrderedSet
}
open func removePollutantsObject(_ value: CorePollutant) {
let mutable = self.pollutants.mutableCopy() as! NSMutableOrderedSet
mutable.remove(value)
self.pollutants = mutable.copy() as! NSOrderedSet
}
}
// DO NOT EDIT. This file is machine-generated and constantly overwritten.
// Make changes to CoreHourlyWeather.swift instead.
import Foundation
import CoreData
public enum CoreHourlyWeatherAttributes: String {
case apparentTemp = "apparentTemp"
case date = "date"
case humidity = "humidity"
case isDay = "isDay"
case lastTimeUpdated = "lastTimeUpdated"
case precipitationProbability = "precipitationProbability"
case temp = "temp"
case timeZone = "timeZone"
case type = "type"
case weekDay = "weekDay"
case windDirection = "windDirection"
case windSpeed = "windSpeed"
}
public enum CoreHourlyWeatherRelationships: String {
case location = "location"
}
open class _CoreHourlyWeather: NSManagedObject {
// MARK: - Class methods
open class func entityName () -> String {
return "CoreHourlyWeather"
}
open class func entity(managedObjectContext: NSManagedObjectContext) -> NSEntityDescription? {
return NSEntityDescription.entity(forEntityName: self.entityName(), in: managedObjectContext)
}
@nonobjc
open class func fetchRequest() -> NSFetchRequest<CoreHourlyWeather> {
return NSFetchRequest(entityName: self.entityName())
}
// MARK: - Life cycle methods
public override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?) {
super.init(entity: entity, insertInto: context)
}
public convenience init?(managedObjectContext: NSManagedObjectContext) {
guard let entity = _CoreHourlyWeather.entity(managedObjectContext: managedObjectContext) else { return nil }
self.init(entity: entity, insertInto: managedObjectContext)
}
// MARK: - Properties
@NSManaged open
var apparentTemp: Data?
@NSManaged open
var date: Date!
@NSManaged open
var humidity: NSDecimalNumber?
@NSManaged open
var isDay: Bool
@NSManaged open
var lastTimeUpdated: Date!
@NSManaged open
var precipitationProbability: NSDecimalNumber?
@NSManaged open
var temp: Data?
@NSManaged open
var timeZone: String!
@NSManaged open
var type: String!
@NSManaged open
var weekDay: String!
@NSManaged open
var windDirection: String?
@NSManaged open
var windSpeed: Data?
// MARK: - Relationships
@NSManaged open
var location: CoreLocation?
}
// DO NOT EDIT. This file is machine-generated and constantly overwritten.
// Make changes to CoreLocation.swift instead.
import Foundation
import CoreData
public enum CoreLocationAttributes: String {
case cityName = "cityName"
case countryCode = "countryCode"
case countryName = "countryName"
case deviceLocation = "deviceLocation"
case fipsCode = "fipsCode"
case imageName = "imageName"
case lastWeatherUpdateDate = "lastWeatherUpdateDate"
case latitude = "latitude"
case longitude = "longitude"
case nickname = "nickname"
case region = "region"
case timeZone = "timeZone"
case zip = "zip"
}
public enum CoreLocationRelationships: String {
case appData = "appData"
case daily = "daily"
case health = "health"
case hourly = "hourly"
case today = "today"
}
open class _CoreLocation: NSManagedObject {
// MARK: - Class methods
open class func entityName () -> String {
return "CoreLocation"
}
open class func entity(managedObjectContext: NSManagedObjectContext) -> NSEntityDescription? {
return NSEntityDescription.entity(forEntityName: self.entityName(), in: managedObjectContext)
}
@nonobjc
open class func fetchRequest() -> NSFetchRequest<CoreLocation> {
return NSFetchRequest(entityName: self.entityName())
}
// MARK: - Life cycle methods
public override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?) {
super.init(entity: entity, insertInto: context)
}
public convenience init?(managedObjectContext: NSManagedObjectContext) {
guard let entity = _CoreLocation.entity(managedObjectContext: managedObjectContext) else { return nil }
self.init(entity: entity, insertInto: managedObjectContext)
}
// MARK: - Properties
@NSManaged open
var cityName: String?
@NSManaged open
var countryCode: String?
@NSManaged open
var countryName: String?
@NSManaged open
var deviceLocation: Bool
@NSManaged open
var fipsCode: String?
@NSManaged open
var imageName: String?
@NSManaged open
var lastWeatherUpdateDate: Date?
@NSManaged open
var latitude: NSDecimalNumber?
@NSManaged open
var longitude: NSDecimalNumber?
@NSManaged open
var nickname: String?
@NSManaged open
var region: String?
@NSManaged open
var timeZone: String!
@NSManaged open
var zip: String?
// MARK: - Relationships
@NSManaged open
var appData: CoreAppData
@NSManaged open
var daily: NSOrderedSet
open func dailySet() -> NSMutableOrderedSet {
return self.daily.mutableCopy() as! NSMutableOrderedSet
}
@NSManaged open
var health: CoreHealth?
@NSManaged open
var hourly: NSOrderedSet
open func hourlySet() -> NSMutableOrderedSet {
return self.hourly.mutableCopy() as! NSMutableOrderedSet
}
@NSManaged open
var today: CoreCurrentWeather?
}
extension _CoreLocation {
open func addDaily(_ objects: NSOrderedSet) {
let mutable = self.daily.mutableCopy() as! NSMutableOrderedSet
mutable.union(objects)
self.daily = mutable.copy() as! NSOrderedSet
}
open func removeDaily(_ objects: NSOrderedSet) {
let mutable = self.daily.mutableCopy() as! NSMutableOrderedSet
mutable.minus(objects)
self.daily = mutable.copy() as! NSOrderedSet
}
open func addDailyObject(_ value: CoreDailyWeather) {
let mutable = self.daily.mutableCopy() as! NSMutableOrderedSet
mutable.add(value)
self.daily = mutable.copy() as! NSOrderedSet
}
open func removeDailyObject(_ value: CoreDailyWeather) {
let mutable = self.daily.mutableCopy() as! NSMutableOrderedSet
mutable.remove(value)
self.daily = mutable.copy() as! NSOrderedSet
}
}
extension _CoreLocation {
open func addHourly(_ objects: NSOrderedSet) {
let mutable = self.hourly.mutableCopy() as! NSMutableOrderedSet
mutable.union(objects)
self.hourly = mutable.copy() as! NSOrderedSet
}
open func removeHourly(_ objects: NSOrderedSet) {
let mutable = self.hourly.mutableCopy() as! NSMutableOrderedSet
mutable.minus(objects)
self.hourly = mutable.copy() as! NSOrderedSet
}
open func addHourlyObject(_ value: CoreHourlyWeather) {
let mutable = self.hourly.mutableCopy() as! NSMutableOrderedSet
mutable.add(value)
self.hourly = mutable.copy() as! NSOrderedSet
}
open func removeHourlyObject(_ value: CoreHourlyWeather) {
let mutable = self.hourly.mutableCopy() as! NSMutableOrderedSet
mutable.remove(value)
self.hourly = mutable.copy() as! NSOrderedSet
}
}
// DO NOT EDIT. This file is machine-generated and constantly overwritten.
// Make changes to CorePollutant.swift instead.
import Foundation
import CoreData
public enum CorePollutantAttributes: String {
case name = "name"
case value = "value"
}
public enum CorePollutantRelationships: String {
case health = "health"
}
open class _CorePollutant: NSManagedObject {
// MARK: - Class methods
open class func entityName () -> String {
return "CorePollutant"
}
open class func entity(managedObjectContext: NSManagedObjectContext) -> NSEntityDescription? {
return NSEntityDescription.entity(forEntityName: self.entityName(), in: managedObjectContext)
}
@nonobjc
open class func fetchRequest() -> NSFetchRequest<CorePollutant> {
return NSFetchRequest(entityName: self.entityName())
}
// MARK: - Life cycle methods
public override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?) {
super.init(entity: entity, insertInto: context)
}
public convenience init?(managedObjectContext: NSManagedObjectContext) {
guard let entity = _CorePollutant.entity(managedObjectContext: managedObjectContext) else { return nil }
self.init(entity: entity, insertInto: managedObjectContext)
}
// MARK: - Properties
@NSManaged open
var name: String!
@NSManaged open
var value: Double
// MARK: - Relationships
@NSManaged open
var health: CoreHealth?
}
#!/bin/sh
# This project uses the mogenerator tool to generate model objects for CoreData: https://rentzsch.github.io/mogenerator/
# Run this script after changing the CoreData model (1WModel.xcdatamodeld).
# This script should be run from the same directory where the model is located.
MODEL_NAME="1WModel.xcdatamodeld"
if ! [ -e "$MODEL_NAME" ]
then
echo "Please, call this script from the same directory where the model file is located."
exit 1
fi
mogenerator --model "./$MODEL_NAME" \
--swift \
--machine-dir ./Objects/Machine \
--human-dir ./Objects/Human
//
// Storage.swift
// 1Weather
//
// Created by Demid Merzlyakov on 02.04.2021.
//
import Foundation
public typealias StorageCompletion = ([Location]?, Int?, Error?) -> ()
public protocol Storage {
func save(locations: [Location], selectedIndex: Int?)
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