From 3d9ebab8c24308bf9d0cbde9088ebcc531b075e8 Mon Sep 17 00:00:00 2001 From: mamadnazar Date: Sun, 2 Mar 2025 17:38:28 +0500 Subject: [PATCH] ios: core data thread safety crashes fix --- iphone/Maps/Maps.xcodeproj/project.pbxproj | 49 +--- .../SingleEntityCoreDataController.swift | 36 +-- .../Tourism/Data/Db/CoreDataManager.swift | 54 ++++ .../CurrencyRates.xcdatamodel/contents | 9 - .../PersonalData.xcdatamodel/contents | 12 - .../Place.xcdatamodel/contents | 39 --- .../TourismDB.xcdatamodel/contents | 54 ++++ .../CurrencyPersistenceController.swift | 2 +- .../HashesPersistenceController.swift | 67 +++-- .../PersonalDataPersistenceController.swift | 2 +- .../PlacesPersistenceController.swift | 214 ++++++++------- .../ReviewsPersistenceController.swift | 247 ++++++++++-------- 12 files changed, 435 insertions(+), 350 deletions(-) create mode 100644 iphone/Maps/Tourism/Data/Db/CoreDataManager.swift delete mode 100644 iphone/Maps/Tourism/Data/Db/DataModels/Currency.xcdatamodeld/CurrencyRates.xcdatamodel/contents delete mode 100644 iphone/Maps/Tourism/Data/Db/DataModels/PersonalData.xcdatamodeld/PersonalData.xcdatamodel/contents delete mode 100644 iphone/Maps/Tourism/Data/Db/DataModels/Place.xcdatamodeld/Place.xcdatamodel/contents create mode 100644 iphone/Maps/Tourism/Data/Db/DataModels/TourismDB.xcdatamodeld/TourismDB.xcdatamodel/contents diff --git a/iphone/Maps/Maps.xcodeproj/project.pbxproj b/iphone/Maps/Maps.xcodeproj/project.pbxproj index 15d97c129e..94b7662547 100644 --- a/iphone/Maps/Maps.xcodeproj/project.pbxproj +++ b/iphone/Maps/Maps.xcodeproj/project.pbxproj @@ -301,7 +301,6 @@ 527D5E7F2C60E69C00736A85 /* Layouting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 527D5E7E2C60E69C00736A85 /* Layouting.swift */; }; 527D5E822C60EFEE00736A85 /* UIViewExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 527D5E812C60EFEE00736A85 /* UIViewExtensions.swift */; }; 528D72A12C5BBBF700D53210 /* Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 528D72A02C5BBBF700D53210 /* Colors.xcassets */; }; - 529A5F162C8595BB004FE4A1 /* PersonalData.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 529A5F112C859535004FE4A1 /* PersonalData.xcdatamodeld */; }; 529A5F192C85BFF0004FE4A1 /* ToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 529A5F182C85BFF0004FE4A1 /* ToastView.swift */; }; 529A5F1E2C86DDE5004FE4A1 /* PlaceDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 529A5F1D2C86DDE5004FE4A1 /* PlaceDTO.swift */; }; 529A5F202C86DE14004FE4A1 /* CoordinatesDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 529A5F1F2C86DE14004FE4A1 /* CoordinatesDTO.swift */; }; @@ -369,7 +368,6 @@ 52ECA81A2C8A25D800F213B3 /* PlaceTopBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52ECA8192C8A25D800F213B3 /* PlaceTopBar.swift */; }; 52ED919D2C71F639000EE25B /* SimpleResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52ED919C2C71F639000EE25B /* SimpleResponse.swift */; }; 52ED919F2C71F718000EE25B /* SignUpRequestDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52ED919E2C71F718000EE25B /* SignUpRequestDTO.swift */; }; - 52ED91A32C7200C4000EE25B /* Currency.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 52ED91A12C7200C4000EE25B /* Currency.xcdatamodeld */; }; 52ED91A52C72C50F000EE25B /* CurrencyRepositoryImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52ED91A42C72C50F000EE25B /* CurrencyRepositoryImpl.swift */; }; 52ED91A72C72C58A000EE25B /* CurrencyPersistenceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52ED91A62C72C58A000EE25B /* CurrencyPersistenceController.swift */; }; 52ED91A92C73020A000EE25B /* CurrencyService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52ED91A82C73020A000EE25B /* CurrencyService.swift */; }; @@ -608,13 +606,14 @@ CED0E0372C902532008C61CA /* ThemeDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = CED0E0362C902532008C61CA /* ThemeDTO.swift */; }; CED0E0392C904868008C61CA /* NavigationUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = CED0E0382C904868008C61CA /* NavigationUtils.swift */; }; CED0E03B2C904A06008C61CA /* FavoritesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CED0E03A2C904A06008C61CA /* FavoritesViewModel.swift */; }; - CED0E03E2C905140008C61CA /* Place.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = CED0E03C2C905140008C61CA /* Place.xcdatamodeld */; }; CED0E0422C9077D3008C61CA /* HashesPersistenceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CED0E0412C9077D3008C61CA /* HashesPersistenceController.swift */; }; CED0E0452C918ED4008C61CA /* Hash.swift in Sources */ = {isa = PBXBuildFile; fileRef = CED0E0442C918ED4008C61CA /* Hash.swift */; }; CED0E0472C919F44008C61CA /* PlacesPersistenceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CED0E0462C919F44008C61CA /* PlacesPersistenceController.swift */; }; CED0E04A2C91A2A9008C61CA /* DBUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = CED0E0492C91A2A9008C61CA /* DBUtils.swift */; }; CED0E04C2C91A6A3008C61CA /* CoordinatesEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = CED0E04B2C91A6A3008C61CA /* CoordinatesEntity.swift */; }; CED0E04E2C91A702008C61CA /* UserEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = CED0E04D2C91A702008C61CA /* UserEntity.swift */; }; + E11202462D744BFA001B3B24 /* TourismDB.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = E11202442D744BFA001B3B24 /* TourismDB.xcdatamodeld */; }; + E112024A2D7454F3001B3B24 /* CoreDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11202492D7454EF001B3B24 /* CoreDataManager.swift */; }; ED0B1C312BC2951F00FB8EDD /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = ED0B1C302BC2951F00FB8EDD /* PrivacyInfo.xcprivacy */; }; ED1080A72B791CFE0023F27E /* SocialMediaCollectionViewHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED1080A62B791CFE0023F27E /* SocialMediaCollectionViewHeader.swift */; }; ED1263AB2B6F99F900AD99F3 /* UIView+AddSeparator.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED1263AA2B6F99F900AD99F3 /* UIView+AddSeparator.swift */; }; @@ -1370,7 +1369,6 @@ 527D5E7E2C60E69C00736A85 /* Layouting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Layouting.swift; sourceTree = ""; }; 527D5E812C60EFEE00736A85 /* UIViewExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIViewExtensions.swift; sourceTree = ""; }; 528D72A02C5BBBF700D53210 /* Colors.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Colors.xcassets; sourceTree = ""; }; - 529A5F122C859535004FE4A1 /* PersonalData.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = PersonalData.xcdatamodel; sourceTree = ""; }; 529A5F182C85BFF0004FE4A1 /* ToastView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ToastView.swift; sourceTree = ""; }; 529A5F1D2C86DDE5004FE4A1 /* PlaceDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaceDTO.swift; sourceTree = ""; }; 529A5F1F2C86DE14004FE4A1 /* CoordinatesDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoordinatesDTO.swift; sourceTree = ""; }; @@ -1438,7 +1436,6 @@ 52ECA8192C8A25D800F213B3 /* PlaceTopBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaceTopBar.swift; sourceTree = ""; }; 52ED919C2C71F639000EE25B /* SimpleResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleResponse.swift; sourceTree = ""; }; 52ED919E2C71F718000EE25B /* SignUpRequestDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpRequestDTO.swift; sourceTree = ""; }; - 52ED91A22C7200C4000EE25B /* CurrencyRates.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = CurrencyRates.xcdatamodel; sourceTree = ""; }; 52ED91A42C72C50F000EE25B /* CurrencyRepositoryImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrencyRepositoryImpl.swift; sourceTree = ""; }; 52ED91A62C72C58A000EE25B /* CurrencyPersistenceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrencyPersistenceController.swift; sourceTree = ""; }; 52ED91A82C73020A000EE25B /* CurrencyService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrencyService.swift; sourceTree = ""; }; @@ -1661,13 +1658,14 @@ CED0E0362C902532008C61CA /* ThemeDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeDTO.swift; sourceTree = ""; }; CED0E0382C904868008C61CA /* NavigationUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationUtils.swift; sourceTree = ""; }; CED0E03A2C904A06008C61CA /* FavoritesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoritesViewModel.swift; sourceTree = ""; }; - CED0E03D2C905140008C61CA /* Place.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Place.xcdatamodel; sourceTree = ""; }; CED0E0412C9077D3008C61CA /* HashesPersistenceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashesPersistenceController.swift; sourceTree = ""; }; CED0E0442C918ED4008C61CA /* Hash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Hash.swift; sourceTree = ""; }; CED0E0462C919F44008C61CA /* PlacesPersistenceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlacesPersistenceController.swift; sourceTree = ""; }; CED0E0492C91A2A9008C61CA /* DBUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBUtils.swift; sourceTree = ""; }; CED0E04B2C91A6A3008C61CA /* CoordinatesEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoordinatesEntity.swift; sourceTree = ""; }; CED0E04D2C91A702008C61CA /* UserEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserEntity.swift; sourceTree = ""; }; + E11202452D744BFA001B3B24 /* TourismDB.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = TourismDB.xcdatamodel; sourceTree = ""; }; + E11202492D7454EF001B3B24 /* CoreDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataManager.swift; sourceTree = ""; }; ED097E762BB80C320006ED01 /* OMapsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = OMapsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; ED0B1C302BC2951F00FB8EDD /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; ED1080A62B791CFE0023F27E /* SocialMediaCollectionViewHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocialMediaCollectionViewHeader.swift; sourceTree = ""; }; @@ -3120,6 +3118,7 @@ 5260D3C92C64F58A00C673B4 /* Db */ = { isa = PBXGroup; children = ( + E11202492D7454EF001B3B24 /* CoreDataManager.swift */, CED0E0492C91A2A9008C61CA /* DBUtils.swift */, CED0E0402C9077B7008C61CA /* PersistenceControllers */, 3D2D79B82C7C4FC30062BC3D /* ControllerTemplates */, @@ -3502,11 +3501,9 @@ 52ED91A02C72007C000EE25B /* DataModels */ = { isa = PBXGroup; children = ( - 529A5F112C859535004FE4A1 /* PersonalData.xcdatamodeld */, - 52ED91A12C7200C4000EE25B /* Currency.xcdatamodeld */, - CED0E03C2C905140008C61CA /* Place.xcdatamodeld */, CED0E04B2C91A6A3008C61CA /* CoordinatesEntity.swift */, CED0E04D2C91A702008C61CA /* UserEntity.swift */, + E11202442D744BFA001B3B24 /* TourismDB.xcdatamodeld */, ); path = DataModels; sourceTree = ""; @@ -5120,6 +5117,7 @@ 47E8163323B17734008FD836 /* MWMStorage+UI.m in Sources */, 3D2D79BA2C7C508E0062BC3D /* SingleEntityCoreDataController.swift in Sources */, 993DF11123F6BDB100AC231A /* UILabelRenderer.swift in Sources */, + E112024A2D7454F3001B3B24 /* CoreDataManager.swift in Sources */, 52A48AE12C882FEE0081E522 /* SearchViewModel.swift in Sources */, 34AB66471FC5AA330078E451 /* RouteManagerTableView.swift in Sources */, 52D588BA2C5CE2E800AB96B3 /* Font.swift in Sources */, @@ -5162,7 +5160,6 @@ F6E2FEE51E097BA00083EBEC /* MWMSearchNoResults.m in Sources */, 3DBD7BE42425015C00ED9FE8 /* ParntersStyleSheet.swift in Sources */, F6E2FF631E097BA00083EBEC /* MWMTTSLanguageViewController.mm in Sources */, - 52ED91A32C7200C4000EE25B /* Currency.xcdatamodeld in Sources */, 52522F462C6DFE060015709C /* AppTopBar.swift in Sources */, 52E2D3A42C59F9CE00A8843A /* WelcomeViewController.swift in Sources */, 4715273524907F8200E91BBA /* BookmarkColorViewController.swift in Sources */, @@ -5171,7 +5168,6 @@ 34AB667D1FC5AA330078E451 /* MWMRoutePreview.mm in Sources */, 529A5F3F2C86E09B004FE4A1 /* HashDTO.swift in Sources */, 993DF11B23F6BDB100AC231A /* UIViewRenderer.swift in Sources */, - 529A5F162C8595BB004FE4A1 /* PersonalData.xcdatamodeld in Sources */, 99C964302428C27A00E41723 /* PlacePageHeaderView.swift in Sources */, 9989273A2449E60200260CE2 /* BottomMenuViewController.swift in Sources */, 47E3C72D2111E6A2008B3B27 /* FadeTransitioning.swift in Sources */, @@ -5262,6 +5258,7 @@ 34C9BD031C6DB693000DC38D /* MWMTableViewController.m in Sources */, 52E95F0D2C6C797B00A3FE2E /* ProfileViewController.swift in Sources */, CE2D27F82CA2C49F00094565 /* BackButtonWithText.m in Sources */, + E11202462D744BFA001B3B24 /* TourismDB.xcdatamodeld in Sources */, F6E2FD8C1E097BA00083EBEC /* MWMNoMapsView.m in Sources */, 529A5F312C86DF61004FE4A1 /* Review Models.swift in Sources */, 34D3B0361E389D05004100F9 /* MWMEditorSelectTableViewCell.m in Sources */, @@ -5359,7 +5356,6 @@ 993DF10223F6BDB100AC231A /* Colors.swift in Sources */, 34AB66201FC5AA330078E451 /* RouteStartButton.swift in Sources */, AC79C8922A65AB9500594C24 /* UIColor+hexString.swift in Sources */, - CED0E03E2C905140008C61CA /* Place.xcdatamodeld in Sources */, 99DEF9D723E420F6006BFD21 /* ElevationProfileDescriptionCell.swift in Sources */, CED0E04A2C91A2A9008C61CA /* DBUtils.swift in Sources */, 993DF11623F6BDB100AC231A /* UIWindowRenderer.swift in Sources */, @@ -6037,34 +6033,13 @@ /* End XCSwiftPackageProductDependency section */ /* Begin XCVersionGroup section */ - 529A5F112C859535004FE4A1 /* PersonalData.xcdatamodeld */ = { + E11202442D744BFA001B3B24 /* TourismDB.xcdatamodeld */ = { isa = XCVersionGroup; children = ( - 529A5F122C859535004FE4A1 /* PersonalData.xcdatamodel */, + E11202452D744BFA001B3B24 /* TourismDB.xcdatamodel */, ); - currentVersion = 529A5F122C859535004FE4A1 /* PersonalData.xcdatamodel */; - path = PersonalData.xcdatamodeld; - sourceTree = ""; - versionGroupType = wrapper.xcdatamodel; - }; - 52ED91A12C7200C4000EE25B /* Currency.xcdatamodeld */ = { - isa = XCVersionGroup; - children = ( - 52ED91A22C7200C4000EE25B /* CurrencyRates.xcdatamodel */, - ); - currentVersion = 52ED91A22C7200C4000EE25B /* CurrencyRates.xcdatamodel */; - path = Currency.xcdatamodeld; - sourceTree = ""; - versionGroupType = wrapper.xcdatamodel; - }; - CED0E03C2C905140008C61CA /* Place.xcdatamodeld */ = { - isa = XCVersionGroup; - children = ( - CED0E03D2C905140008C61CA /* Place.xcdatamodel */, - ); - currentVersion = CED0E03D2C905140008C61CA /* Place.xcdatamodel */; - name = Place.xcdatamodeld; - path = /Users/user/Projects/Tourism/iphone/Maps/Tourism/Data/Db/DataModels/Place.xcdatamodeld; + currentVersion = E11202452D744BFA001B3B24 /* TourismDB.xcdatamodel */; + path = TourismDB.xcdatamodeld; sourceTree = ""; versionGroupType = wrapper.xcdatamodel; }; diff --git a/iphone/Maps/Tourism/Data/Db/ControllerTemplates/SingleEntityCoreDataController.swift b/iphone/Maps/Tourism/Data/Db/ControllerTemplates/SingleEntityCoreDataController.swift index 76a1bed399..06e2c9488b 100644 --- a/iphone/Maps/Tourism/Data/Db/ControllerTemplates/SingleEntityCoreDataController.swift +++ b/iphone/Maps/Tourism/Data/Db/ControllerTemplates/SingleEntityCoreDataController.swift @@ -2,30 +2,34 @@ import CoreData import Combine class SingleEntityCoreDataController: NSObject, NSFetchedResultsControllerDelegate { - private let container: NSPersistentContainer + private var fetchedResultsController: NSFetchedResultsController? var entitySubject = PassthroughSubject() - init(modelName: String) { - container = NSPersistentContainer(name: modelName) - super.init() - container.loadPersistentStores { (description, error) in - if let error = error { - fatalError("Failed to load Core Data stack: \(error)") - } - } - } + private let viewContext = CoreDataManager.shared.viewContext + private let backgroundContext = CoreDataManager.shared.backgroundContext - var context: NSManagedObjectContext { - return container.viewContext - } +// init(modelName: String) { +// container = NSPersistentContainer(name: modelName) +// super.init() +// container.loadPersistentStores { (description, error) in +// if let error = error { +// fatalError("Failed to load Core Data stack: \(error)") +// } +// } +// container.viewContext.automaticallyMergesChangesFromParent = true +// } + +// var context: NSManagedObjectContext { +// return container.viewContext +// } func observeEntity(fetchRequest: NSFetchRequest, sortDescriptor: NSSortDescriptor) { fetchRequest.sortDescriptors = [sortDescriptor] fetchedResultsController = NSFetchedResultsController( fetchRequest: fetchRequest, - managedObjectContext: context, + managedObjectContext: viewContext, sectionNameKeyPath: nil, cacheName: nil ) @@ -47,9 +51,9 @@ class SingleEntityCoreDataController: NSObject, NSFetch func updateEntity(updateBlock: @escaping (Entity) -> Void, fetchRequest: NSFetchRequest) -> AnyPublisher { Future { promise in do { - let entityToUpdate = try self.context.fetch(fetchRequest).first ?? Entity(context: self.context) + let entityToUpdate = try self.viewContext.fetch(fetchRequest).first ?? Entity(context: self.viewContext) updateBlock(entityToUpdate) - try self.context.save() + try self.viewContext.save() promise(.success(())) } catch { diff --git a/iphone/Maps/Tourism/Data/Db/CoreDataManager.swift b/iphone/Maps/Tourism/Data/Db/CoreDataManager.swift new file mode 100644 index 0000000000..354d5397a3 --- /dev/null +++ b/iphone/Maps/Tourism/Data/Db/CoreDataManager.swift @@ -0,0 +1,54 @@ +import CoreData + +final class CoreDataManager: NSObject { + + static let shared = CoreDataManager() + + + let persistentContainer: NSPersistentContainer + + var viewContext: NSManagedObjectContext { + return persistentContainer.viewContext + } + + var backgroundContext: NSManagedObjectContext { + return persistentContainer.newBackgroundContext() + } + + + private override init() { + persistentContainer = NSPersistentContainer(name: "TourismDB") + persistentContainer.loadPersistentStores { (storeDescription, error) in + if let error = error as NSError? { + fatalError("Unresolved error \(error), \(error.userInfo)") + } + } + + persistentContainer.viewContext.automaticallyMergesChangesFromParent = true + + } + +// lazy var persistentContainer: NSPersistentContainer = { +// let container = NSPersistentContainer(name: "TourismDB") +// container.loadPersistentStores { (storeDescription, error) in +// if let error = error as NSError? { +// fatalError("Unresolved error \(error), \(error.userInfo)") +// } +// } +// return container +// }() + + + + func saveContext() { + let context = viewContext + if context.hasChanges { + do { + try context.save() + } catch { + let nserror = error as NSError + fatalError("Unresolved error \(nserror), \(nserror.userInfo)") + } + } + } +} diff --git a/iphone/Maps/Tourism/Data/Db/DataModels/Currency.xcdatamodeld/CurrencyRates.xcdatamodel/contents b/iphone/Maps/Tourism/Data/Db/DataModels/Currency.xcdatamodeld/CurrencyRates.xcdatamodel/contents deleted file mode 100644 index 1fb14e28a3..0000000000 --- a/iphone/Maps/Tourism/Data/Db/DataModels/Currency.xcdatamodeld/CurrencyRates.xcdatamodel/contents +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/iphone/Maps/Tourism/Data/Db/DataModels/PersonalData.xcdatamodeld/PersonalData.xcdatamodel/contents b/iphone/Maps/Tourism/Data/Db/DataModels/PersonalData.xcdatamodeld/PersonalData.xcdatamodel/contents deleted file mode 100644 index 9fa30123e4..0000000000 --- a/iphone/Maps/Tourism/Data/Db/DataModels/PersonalData.xcdatamodeld/PersonalData.xcdatamodel/contents +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/iphone/Maps/Tourism/Data/Db/DataModels/Place.xcdatamodeld/Place.xcdatamodel/contents b/iphone/Maps/Tourism/Data/Db/DataModels/Place.xcdatamodeld/Place.xcdatamodel/contents deleted file mode 100644 index c34f0911f5..0000000000 --- a/iphone/Maps/Tourism/Data/Db/DataModels/Place.xcdatamodeld/Place.xcdatamodel/contents +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/iphone/Maps/Tourism/Data/Db/DataModels/TourismDB.xcdatamodeld/TourismDB.xcdatamodel/contents b/iphone/Maps/Tourism/Data/Db/DataModels/TourismDB.xcdatamodeld/TourismDB.xcdatamodel/contents new file mode 100644 index 0000000000..6ef343cfaf --- /dev/null +++ b/iphone/Maps/Tourism/Data/Db/DataModels/TourismDB.xcdatamodeld/TourismDB.xcdatamodel/contents @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/iphone/Maps/Tourism/Data/Db/PersistenceControllers/CurrencyPersistenceController.swift b/iphone/Maps/Tourism/Data/Db/PersistenceControllers/CurrencyPersistenceController.swift index 3d2f32ee60..257c7e66ee 100644 --- a/iphone/Maps/Tourism/Data/Db/PersistenceControllers/CurrencyPersistenceController.swift +++ b/iphone/Maps/Tourism/Data/Db/PersistenceControllers/CurrencyPersistenceController.swift @@ -6,7 +6,7 @@ class CurrencyPersistenceController { private let coreDataController: SingleEntityCoreDataController private init() { - coreDataController = SingleEntityCoreDataController(modelName: "Currency") + coreDataController = SingleEntityCoreDataController() } var currencyRatesSubject: PassthroughSubject { diff --git a/iphone/Maps/Tourism/Data/Db/PersistenceControllers/HashesPersistenceController.swift b/iphone/Maps/Tourism/Data/Db/PersistenceControllers/HashesPersistenceController.swift index 58a7490ecc..fe574ae2c7 100644 --- a/iphone/Maps/Tourism/Data/Db/PersistenceControllers/HashesPersistenceController.swift +++ b/iphone/Maps/Tourism/Data/Db/PersistenceControllers/HashesPersistenceController.swift @@ -4,44 +4,35 @@ import CoreData class HashesPersistenceController { static let shared = HashesPersistenceController() - let container: NSPersistentContainer - - init(inMemory: Bool = false) { - container = NSPersistentContainer(name: "Place") - if inMemory { - container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null") - } - container.loadPersistentStores { (storeDescription, error) in - if let error = error as NSError? { - fatalError("Unresolved error \(error), \(error.userInfo)") - } - } - } + private let viewContext = CoreDataManager.shared.viewContext // MARK: - CRUD Operations func insertHashes(hashes: [Hash]) { - let context = container.viewContext - do { - for hash in hashes { - let newHash = HashEntity(context: context) - newHash.categoryId = hash.categoryId - newHash.value = hash.value + let backgroundContext = CoreDataManager.shared.backgroundContext + + backgroundContext.perform { + do { + for hash in hashes { + let newHash = HashEntity(context: backgroundContext) + newHash.categoryId = hash.categoryId + newHash.value = hash.value + } + try backgroundContext.save() + } catch { + print("Failed to save context: \(error)") } - try context.save() - } catch { - print("Failed to save context: \(error)") } + } func getHash(categoryId: Int64) -> Hash? { - let context = container.viewContext let fetchRequest: NSFetchRequest = HashEntity.fetchRequest() fetchRequest.predicate = NSPredicate(format: "categoryId == %lld", categoryId) fetchRequest.fetchLimit = 1 do { - let result = try context.fetch(fetchRequest).first + let result = try viewContext.fetch(fetchRequest).first if let result = result { return Hash(categoryId: result.categoryId, value: result.value!) } else { @@ -54,11 +45,10 @@ class HashesPersistenceController { } func getHashes() -> [Hash] { - let context = container.viewContext let fetchRequest: NSFetchRequest = HashEntity.fetchRequest() do { - let result = try context.fetch(fetchRequest) + let result = try viewContext.fetch(fetchRequest) let hashes = result.map { hashEntity in Hash(categoryId: hashEntity.categoryId, value: hashEntity.value!) } @@ -70,18 +60,23 @@ class HashesPersistenceController { } func deleteHash(hash: Hash) { - let context = container.viewContext - let fetchRequest: NSFetchRequest = HashEntity.fetchRequest() - fetchRequest.predicate = NSPredicate(format: "categoryId == %lld", hash.categoryId) - do { - if let hash = try context.fetch(fetchRequest).first { - context.delete(hash) - try context.save() + let backgroundContext = CoreDataManager.shared.backgroundContext + + backgroundContext.perform { + let fetchRequest: NSFetchRequest = HashEntity.fetchRequest() + fetchRequest.predicate = NSPredicate(format: "categoryId == %lld", hash.categoryId) + + do { + if let hash = try backgroundContext.fetch(fetchRequest).first { + backgroundContext.delete(hash) + try backgroundContext.save() + } + } catch { + print(error) + print("Failed to delete review: \(error)") } - } catch { - print(error) - print("Failed to delete review: \(error)") } } + } diff --git a/iphone/Maps/Tourism/Data/Db/PersistenceControllers/PersonalDataPersistenceController.swift b/iphone/Maps/Tourism/Data/Db/PersistenceControllers/PersonalDataPersistenceController.swift index ed265413a2..d238923729 100644 --- a/iphone/Maps/Tourism/Data/Db/PersistenceControllers/PersonalDataPersistenceController.swift +++ b/iphone/Maps/Tourism/Data/Db/PersistenceControllers/PersonalDataPersistenceController.swift @@ -6,7 +6,7 @@ class PersonalDataPersistenceController { private let coreDataController: SingleEntityCoreDataController private init() { - coreDataController = SingleEntityCoreDataController(modelName: "PersonalData") + coreDataController = SingleEntityCoreDataController() } var personalDataSubject: PassthroughSubject { diff --git a/iphone/Maps/Tourism/Data/Db/PersistenceControllers/PlacesPersistenceController.swift b/iphone/Maps/Tourism/Data/Db/PersistenceControllers/PlacesPersistenceController.swift index a7a066812c..92465060e8 100644 --- a/iphone/Maps/Tourism/Data/Db/PersistenceControllers/PlacesPersistenceController.swift +++ b/iphone/Maps/Tourism/Data/Db/PersistenceControllers/PlacesPersistenceController.swift @@ -4,7 +4,7 @@ import Combine class PlacesPersistenceController: NSObject, NSFetchedResultsControllerDelegate { static let shared = PlacesPersistenceController() - let container: NSPersistentContainer + private let viewContext = CoreDataManager.shared.viewContext private var searchFetchedResultsController: NSFetchedResultsController? private var placesByCatFetchedResultsController: NSFetchedResultsController? @@ -21,33 +21,38 @@ class PlacesPersistenceController: NSObject, NSFetchedResultsControllerDelegate let favoritePlacesSubject = PassthroughSubject<[PlaceShort], ResourceError>() - init(inMemory: Bool = false) { - container = NSPersistentContainer(name: "Place") - if inMemory { - container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null") - } - super.init() - container.loadPersistentStores { (storeDescription, error) in - if let error = error as NSError? { - fatalError("Unresolved error \(error), \(error.userInfo)") - } - } - } +// init(inMemory: Bool = false) { +// container = NSPersistentContainer(name: "Place") +// if inMemory { +// container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null") +// } +// super.init() +// container.loadPersistentStores { (storeDescription, error) in +// if let error = error as NSError? { +// fatalError("Unresolved error \(error), \(error.userInfo)") +// } +// } +// container.viewContext.automaticallyMergesChangesFromParent = true +// } // MARK: Places func insertPlaces(_ places: [PlaceFull], categoryId: Int64) { - let context = container.viewContext - do { - for place in places { - let newPlace = PlaceEntity(context: context) - newPlace.id = place.id - updatePlace(newPlace, with: place, categoryId: categoryId) + let backgroundContext = CoreDataManager.shared.backgroundContext + + backgroundContext.perform { [weak self] in + do { + for place in places { + let newPlace = PlaceEntity(context: backgroundContext) + newPlace.id = place.id + self?.updatePlace(newPlace, with: place, categoryId: categoryId) + } + try backgroundContext.save() + } catch { + print("Failed to save context: \(error)") } - try context.save() - } catch { - print("Failed to save context: \(error)") } + } private func updatePlace(_ placeEntity: PlaceEntity, with place: PlaceFull, categoryId: Int64) { @@ -63,47 +68,57 @@ class PlacesPersistenceController: NSObject, NSFetchedResultsControllerDelegate } func deleteAllPlaces() { - let context = container.viewContext - let fetchRequest: NSFetchRequest = PlaceEntity.fetchRequest() - let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest) - // Configure the request to return the IDs of the deleted objects - deleteRequest.resultType = .resultTypeObjectIDs + let backgroundContext = CoreDataManager.shared.backgroundContext - do { - let result = try context.execute(deleteRequest) as? NSBatchDeleteResult - let changes: [AnyHashable: Any] = [ - NSDeletedObjectsKey: result?.result as? [NSManagedObjectID] ?? [] - ] + backgroundContext.perform { + let fetchRequest: NSFetchRequest = PlaceEntity.fetchRequest() + let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest) - NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes, into: [context]) - try context.save() - } catch { - print("Failed to delete all places: \(error)") + // Configure the request to return the IDs of the deleted objects + deleteRequest.resultType = .resultTypeObjectIDs + + do { + let result = try backgroundContext.execute(deleteRequest) as? NSBatchDeleteResult + let changes: [AnyHashable: Any] = [ + NSDeletedObjectsKey: result?.result as? [NSManagedObjectID] ?? [] + ] + + NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes, into: [backgroundContext]) + try backgroundContext.save() + } catch { + print("Failed to delete all places: \(error)") + } } + } func deleteAllPlacesByCategory(categoryId: Int64) { - let context = container.viewContext - let fetchRequest: NSFetchRequest = PlaceEntity.fetchRequest() - fetchRequest.predicate = NSPredicate(format: "categoryId == %d", categoryId) - let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest) + let backgroundContext = CoreDataManager.shared.backgroundContext - // Configure the request to return the IDs of the deleted objects - deleteRequest.resultType = .resultTypeObjectIDs - - do { - let result = try context.execute(deleteRequest) as? NSBatchDeleteResult - let changes: [AnyHashable: Any] = [ - NSDeletedObjectsKey: result?.result as? [NSManagedObjectID] ?? [] - ] + backgroundContext.perform { + let fetchRequest: NSFetchRequest = PlaceEntity.fetchRequest() + fetchRequest.predicate = NSPredicate(format: "categoryId == %d", categoryId) - NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes, into: [context]) - try context.save() - } catch { - print("Failed to delete places by category \(categoryId): \(error)") + let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest) + + // Configure the request to return the IDs of the deleted objects + deleteRequest.resultType = .resultTypeObjectIDs + + do { + let result = try backgroundContext.execute(deleteRequest) as? NSBatchDeleteResult + let changes: [AnyHashable: Any] = [ + NSDeletedObjectsKey: result?.result as? [NSManagedObjectID] ?? [] + ] + + NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes, into: [backgroundContext]) + try backgroundContext.save() + } catch { + print("Failed to delete places by category \(categoryId): \(error)") + } } + } // MARK: Observe places @@ -114,7 +129,7 @@ class PlacesPersistenceController: NSObject, NSFetchedResultsControllerDelegate fetchRequest.predicate = NSPredicate(format: "name CONTAINS[cd] %@", query) } - searchFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: container.viewContext, sectionNameKeyPath: nil, cacheName: nil) + searchFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: viewContext, sectionNameKeyPath: nil, cacheName: nil) searchFetchedResultsController?.delegate = self @@ -134,7 +149,7 @@ class PlacesPersistenceController: NSObject, NSFetchedResultsControllerDelegate fetchRequest.predicate = NSPredicate(format: "categoryId == %d", categoryId) placesByCatFetchedResultsController = NSFetchedResultsController( - fetchRequest: fetchRequest, managedObjectContext: container.viewContext, sectionNameKeyPath: nil, cacheName: nil + fetchRequest: fetchRequest, managedObjectContext: viewContext, sectionNameKeyPath: nil, cacheName: nil ) placesByCatFetchedResultsController?.delegate = self @@ -156,7 +171,7 @@ class PlacesPersistenceController: NSObject, NSFetchedResultsControllerDelegate fetchRequest.fetchLimit = 15 topSightsFetchedResultsController = NSFetchedResultsController( - fetchRequest: fetchRequest, managedObjectContext: container.viewContext, sectionNameKeyPath: nil, cacheName: nil + fetchRequest: fetchRequest, managedObjectContext: viewContext, sectionNameKeyPath: nil, cacheName: nil ) topSightsFetchedResultsController?.delegate = self @@ -178,7 +193,7 @@ class PlacesPersistenceController: NSObject, NSFetchedResultsControllerDelegate fetchRequest.fetchLimit = 15 topRestaurantsFetchedResultsController = NSFetchedResultsController( - fetchRequest: fetchRequest, managedObjectContext: container.viewContext, sectionNameKeyPath: nil, cacheName: nil + fetchRequest: fetchRequest, managedObjectContext: viewContext, sectionNameKeyPath: nil, cacheName: nil ) topRestaurantsFetchedResultsController?.delegate = self @@ -200,7 +215,7 @@ class PlacesPersistenceController: NSObject, NSFetchedResultsControllerDelegate fetchRequest.fetchLimit = 1 singlePlaceFetchedResultsController = NSFetchedResultsController( - fetchRequest: fetchRequest, managedObjectContext: container.viewContext, sectionNameKeyPath: nil, cacheName: nil + fetchRequest: fetchRequest, managedObjectContext: viewContext, sectionNameKeyPath: nil, cacheName: nil ) singlePlaceFetchedResultsController?.delegate = self @@ -230,7 +245,7 @@ class PlacesPersistenceController: NSObject, NSFetchedResultsControllerDelegate fetchRequest.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: predicates) favoritePlacesFetchedResultsController = NSFetchedResultsController( - fetchRequest: fetchRequest, managedObjectContext: container.viewContext, sectionNameKeyPath: nil, cacheName: nil + fetchRequest: fetchRequest, managedObjectContext: viewContext, sectionNameKeyPath: nil, cacheName: nil ) favoritePlacesFetchedResultsController?.delegate = self @@ -246,7 +261,6 @@ class PlacesPersistenceController: NSObject, NSFetchedResultsControllerDelegate } func getFavoritePlaces(query: String = "") -> [PlaceEntity] { - let context = container.viewContext let fetchRequest: NSFetchRequest = PlaceEntity.fetchRequest() fetchRequest.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)] var predicates = [ @@ -258,7 +272,7 @@ class PlacesPersistenceController: NSObject, NSFetchedResultsControllerDelegate fetchRequest.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: predicates) do { - return try context.fetch(fetchRequest) + return try viewContext.fetch(fetchRequest) } catch { print("Failed to fetch favorite places: \(error)") return [] @@ -266,60 +280,74 @@ class PlacesPersistenceController: NSObject, NSFetchedResultsControllerDelegate } func setFavorite(placeId: Int64, isFavorite: Bool) { - let context = container.viewContext - let fetchRequest: NSFetchRequest = PlaceEntity.fetchRequest() - fetchRequest.predicate = NSPredicate(format: "id == %lld", placeId) - do { - if let place = try context.fetch(fetchRequest).first { - place.isFavorite = isFavorite - try context.save() + let backgroundContext = CoreDataManager.shared.backgroundContext + + backgroundContext.perform { + let fetchRequest: NSFetchRequest = PlaceEntity.fetchRequest() + fetchRequest.predicate = NSPredicate(format: "id == %lld", placeId) + + do { + if let place = try backgroundContext.fetch(fetchRequest).first { + place.isFavorite = isFavorite + try backgroundContext.save() + } + } catch { + print("Failed to set favorite status: \(error)") } - } catch { - print("Failed to set favorite status: \(error)") } + } func addFavoritingRecordForSync(placeId: Int64, isFavorite: Bool) { - let context = container.viewContext - let favoriteSyncEntity = FavoriteSyncEntity(context: context) - favoriteSyncEntity.placeId = placeId - favoriteSyncEntity.isFavorite = isFavorite - do { - try context.save() - } catch { - print("Failed to add favorite sync: \(error)") + let backgroundContext = CoreDataManager.shared.backgroundContext + + backgroundContext.perform { + let favoriteSyncEntity = FavoriteSyncEntity(context: backgroundContext) + favoriteSyncEntity.placeId = placeId + favoriteSyncEntity.isFavorite = isFavorite + + do { + try backgroundContext.save() + } catch { + print("Failed to add favorite sync: \(error)") + } } + } func removeFavoritingRecordsForSync(placeIds: [Int64]) { - let context = container.viewContext - let fetchRequest: NSFetchRequest = FavoriteSyncEntity.fetchRequest() - fetchRequest.predicate = NSPredicate(format: "placeId IN %@", placeIds) - let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest) - deleteRequest.resultType = .resultTypeObjectIDs + let backgroundContext = CoreDataManager.shared.backgroundContext - do { - let result = try context.execute(deleteRequest) as? NSBatchDeleteResult - let changes: [AnyHashable: Any] = [ - NSDeletedObjectsKey: result?.result as? [NSManagedObjectID] ?? [] - ] + backgroundContext.perform { + let fetchRequest: NSFetchRequest = FavoriteSyncEntity.fetchRequest() + fetchRequest.predicate = NSPredicate(format: "placeId IN %@", placeIds) - NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes, into: [context]) - try context.save() - } catch { - print("Failed to remove favoriting records for sync: \(error)") + let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest) + deleteRequest.resultType = .resultTypeObjectIDs + + do { + let result = try backgroundContext.execute(deleteRequest) as? NSBatchDeleteResult + let changes: [AnyHashable: Any] = [ + NSDeletedObjectsKey: result?.result as? [NSManagedObjectID] ?? [] + ] + + NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes, into: [backgroundContext]) + try backgroundContext.save() + } catch { + print("Failed to remove favoriting records for sync: \(error)") + } } + } func getFavoritingRecordsForSync() -> [FavoriteSyncEntity] { - let context = container.viewContext let fetchRequest: NSFetchRequest = FavoriteSyncEntity.fetchRequest() do { - return try context.fetch(fetchRequest) + return try viewContext.fetch(fetchRequest) } catch { print("Failed to fetch favorite sync data: \(error)") return [] diff --git a/iphone/Maps/Tourism/Data/Db/PersistenceControllers/ReviewsPersistenceController.swift b/iphone/Maps/Tourism/Data/Db/PersistenceControllers/ReviewsPersistenceController.swift index 01abc28f65..b052285529 100644 --- a/iphone/Maps/Tourism/Data/Db/PersistenceControllers/ReviewsPersistenceController.swift +++ b/iphone/Maps/Tourism/Data/Db/PersistenceControllers/ReviewsPersistenceController.swift @@ -5,7 +5,7 @@ import Combine class ReviewsPersistenceController: NSObject, NSFetchedResultsControllerDelegate { static let shared = ReviewsPersistenceController() - let container: NSPersistentContainer + private let viewContext = CoreDataManager.shared.viewContext private var reviewsForPlaceFetchedResultsController: NSFetchedResultsController? private var reviewsPlannedToPostFetchedResultsController: NSFetchedResultsController? @@ -13,31 +13,36 @@ class ReviewsPersistenceController: NSObject, NSFetchedResultsControllerDelegate let reviewsForPlaceSubject = PassthroughSubject<[Review], ResourceError>() let reviewsPlannedToPostSubject = PassthroughSubject<[ReviewPlannedToPostEntity], ResourceError>() - override init() { - container = NSPersistentContainer(name: "Place") - super.init() - container.loadPersistentStores { (storeDescription, error) in - if let error = error as NSError? { - fatalError("Unresolved error \(error), \(error.userInfo)") - } - } - } +// override init() { +// container = NSPersistentContainer(name: "Place") +// super.init() +// container.loadPersistentStores { (storeDescription, error) in +// if let error = error as NSError? { +// fatalError("Unresolved error \(error), \(error.userInfo)") +// } +// } +// container.viewContext.automaticallyMergesChangesFromParent = true +// } // MARK: - Review Operations func insertReviews(_ reviews: [Review]) { - let context = container.viewContext + let backgroundContext = CoreDataManager.shared.backgroundContext - do { - for review in reviews { - let newReview = ReviewEntity(context: context) - newReview.id = review.id - updateReviewEntity(newReview, with: review) + backgroundContext.perform { + do { + for review in reviews { + let newReview = ReviewEntity(context: backgroundContext) + newReview.id = review.id + self.updateReviewEntity(newReview, with: review) + } + try backgroundContext.save() + } catch { + print(error) + print("Failed to insert/update reviews: \(error)") } - try context.save() - } catch { - print(error) - print("Failed to insert/update reviews: \(error)") } + + } private func updateReviewEntity(_ entity: ReviewEntity, with review: Review) { @@ -51,88 +56,110 @@ class ReviewsPersistenceController: NSObject, NSFetchedResultsControllerDelegate } func deleteReview(id: Int64) { - let context = container.viewContext - let fetchRequest: NSFetchRequest = ReviewEntity.fetchRequest() - fetchRequest.predicate = NSPredicate(format: "id == %lld", id) - do { - let reviews = try context.fetch(fetchRequest) - for review in reviews { - context.delete(review) + let backgroundContext = CoreDataManager.shared.backgroundContext + + backgroundContext.perform { + let fetchRequest: NSFetchRequest = ReviewEntity.fetchRequest() + fetchRequest.predicate = NSPredicate(format: "id == %lld", id) + + do { + let reviews = try backgroundContext.fetch(fetchRequest) + for review in reviews { + backgroundContext.delete(review) + } + try backgroundContext.save() + } catch { + print(error) + print("Failed to delete review: \(error)") } - try context.save() - } catch { - print(error) - print("Failed to delete review: \(error)") } + } func deleteReviews(ids: [Int64]) { - let context = container.viewContext - let fetchRequest: NSFetchRequest = ReviewEntity.fetchRequest() - fetchRequest.predicate = NSPredicate(format: "id IN %@", ids) + + let backgroundContext = CoreDataManager.shared.backgroundContext - do { - let reviews = try context.fetch(fetchRequest) - for review in reviews { - context.delete(review) + backgroundContext.perform { + let fetchRequest: NSFetchRequest = ReviewEntity.fetchRequest() + fetchRequest.predicate = NSPredicate(format: "id IN %@", ids) + + do { + let reviews = try backgroundContext.fetch(fetchRequest) + for review in reviews { + backgroundContext.delete(review) + } + try backgroundContext.save() + } catch { + print(error) + print("Failed to delete reviews: \(error)") } - try context.save() - } catch { - print(error) - print("Failed to delete reviews: \(error)") } + } func deleteAllReviews() { - let context = container.viewContext - let fetchRequest: NSFetchRequest = ReviewEntity.fetchRequest() - let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest) - deleteRequest.resultType = .resultTypeObjectIDs - do { - let result = try context.execute(deleteRequest) as? NSBatchDeleteResult - let changes: [AnyHashable: Any] = [ - NSDeletedObjectsKey: result?.result as? [NSManagedObjectID] ?? [] - ] + let backgroundContext = CoreDataManager.shared.backgroundContext + + backgroundContext.perform { + let fetchRequest: NSFetchRequest = ReviewEntity.fetchRequest() + let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest) + deleteRequest.resultType = .resultTypeObjectIDs - NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes, into: [context]) - try context.save() - } catch { - print(error) - print("Failed to delete all places: \(error)") + do { + let result = try backgroundContext.execute(deleteRequest) as? NSBatchDeleteResult + let changes: [AnyHashable: Any] = [ + NSDeletedObjectsKey: result?.result as? [NSManagedObjectID] ?? [] + ] + + NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes, into: [backgroundContext]) + try backgroundContext.save() + } catch { + print(error) + print("Failed to delete all places: \(error)") + } } + } func deleteAllPlaceReviews(placeId: Int64) { - let context = container.viewContext - let fetchRequest: NSFetchRequest = ReviewEntity.fetchRequest() - fetchRequest.predicate = NSPredicate(format: "placeId == %lld", placeId) - let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest) - deleteRequest.resultType = .resultTypeObjectIDs - do { - let result = try context.execute(deleteRequest) as? NSBatchDeleteResult - let changes: [AnyHashable: Any] = [ - NSDeletedObjectsKey: result?.result as? [NSManagedObjectID] ?? [] - ] + let backgroundContext = CoreDataManager.shared.backgroundContext + + backgroundContext.perform { + let fetchRequest: NSFetchRequest = ReviewEntity.fetchRequest() + fetchRequest.predicate = NSPredicate(format: "placeId == %lld", placeId) + let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest) + deleteRequest.resultType = .resultTypeObjectIDs - NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes, into: [context]) - try context.save() - } catch { - print(error) - print("Failed to delete place reviews: \(error)") + do { + let result = try backgroundContext.execute(deleteRequest) as? NSBatchDeleteResult + let changes: [AnyHashable: Any] = [ + NSDeletedObjectsKey: result?.result as? [NSManagedObjectID] ?? [] + ] + + NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes, into: [backgroundContext]) + try backgroundContext.save() + } catch { + print(error) + print("Failed to delete place reviews: \(error)") + } } + + } func observeReviewsForPlace(placeId: Int64) { + let fetchRequest: NSFetchRequest = ReviewEntity.fetchRequest() fetchRequest.predicate = NSPredicate(format: "placeId == %lld", placeId) fetchRequest.sortDescriptors = [NSSortDescriptor(key: "date", ascending: false)] reviewsForPlaceFetchedResultsController = NSFetchedResultsController( fetchRequest: fetchRequest, - managedObjectContext: container.viewContext, + managedObjectContext: viewContext, sectionNameKeyPath: nil, cacheName: nil ) @@ -153,15 +180,14 @@ class ReviewsPersistenceController: NSObject, NSFetchedResultsControllerDelegate } func markReviewForDeletion(id: Int64, deletionPlanned: Bool = true) { - let context = container.viewContext let fetchRequest: NSFetchRequest = ReviewEntity.fetchRequest() fetchRequest.predicate = NSPredicate(format: "id == %lld", id) do { - let reviews = try context.fetch(fetchRequest) + let reviews = try viewContext.fetch(fetchRequest) if let review = reviews.first { review.deletionPlanned = deletionPlanned - try context.save() + try viewContext.save() } } catch { print(error) @@ -170,12 +196,11 @@ class ReviewsPersistenceController: NSObject, NSFetchedResultsControllerDelegate } func getReviewsPlannedForDeletion() -> [ReviewEntity] { - let context = container.viewContext let fetchRequest: NSFetchRequest = ReviewEntity.fetchRequest() fetchRequest.predicate = NSPredicate(format: "deletionPlanned == YES") do { - return try context.fetch(fetchRequest) + return try viewContext.fetch(fetchRequest) } catch { print(error) print("Failed to fetch reviews planned for deletion: \(error)") @@ -186,50 +211,60 @@ class ReviewsPersistenceController: NSObject, NSFetchedResultsControllerDelegate // // MARK: - Planned Review Operations func insertReviewPlannedToPost(_ review: ReviewToPost) { - let context = container.viewContext - let newReview = ReviewPlannedToPostEntity(context: context) - newReview.placeId = review.placeId - newReview.comment = review.comment - newReview.rating = Int32(review.rating) - let imagesJson = DBUtils.encodeToJsonString(review.images) - newReview.imagesJson = imagesJson - do { - try context.save() - } catch { - print(error) - print("Failed to insert planned review: \(error)") + let backgroundContext = CoreDataManager.shared.backgroundContext + + backgroundContext.perform { + let newReview = ReviewPlannedToPostEntity(context: backgroundContext) + newReview.placeId = review.placeId + newReview.comment = review.comment + newReview.rating = Int32(review.rating) + let imagesJson = DBUtils.encodeToJsonString(review.images) + newReview.imagesJson = imagesJson + + do { + try backgroundContext.save() + } catch { + print(error) + print("Failed to insert planned review: \(error)") + } } + + } func deleteReviewPlannedToPost(placeId: Int64) { - let context = container.viewContext - let fetchRequest: NSFetchRequest = ReviewPlannedToPostEntity.fetchRequest() - fetchRequest.predicate = NSPredicate(format: "placeId == %lld", placeId) - let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest) - deleteRequest.resultType = .resultTypeObjectIDs + let backgroundContext = CoreDataManager.shared.backgroundContext - do { - let result = try context.execute(deleteRequest) as? NSBatchDeleteResult - let changes: [AnyHashable: Any] = [ - NSDeletedObjectsKey: result?.result as? [NSManagedObjectID] ?? [] - ] + backgroundContext.perform { + let fetchRequest: NSFetchRequest = ReviewPlannedToPostEntity.fetchRequest() + fetchRequest.predicate = NSPredicate(format: "placeId == %lld", placeId) - NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes, into: [context]) - try context.save() - } catch { - print(error) - print("Failed to delete planned review: \(error)") + let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest) + deleteRequest.resultType = .resultTypeObjectIDs + + do { + let result = try backgroundContext.execute(deleteRequest) as? NSBatchDeleteResult + let changes: [AnyHashable: Any] = [ + NSDeletedObjectsKey: result?.result as? [NSManagedObjectID] ?? [] + ] + + NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes, into: [backgroundContext]) + try backgroundContext.save() + } catch { + print(error) + print("Failed to delete planned review: \(error)") + } } + } func getReviewsPlannedToPost() -> [ReviewPlannedToPostEntity] { - let context = container.viewContext let fetchRequest: NSFetchRequest = ReviewPlannedToPostEntity.fetchRequest() do { - return try context.fetch(fetchRequest) + return try viewContext.fetch(fetchRequest) } catch { print(error) print("Failed to fetch planned reviews: \(error)")