From d02b1a968faa7b2406c716e996b7c6af25c1a976 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Wed, 12 Mar 2025 15:04:04 +1100 Subject: [PATCH 1/2] Remove duplicated `CoreDataStackMock` class --- .../WordPressTest/DataMigratorTests.swift | 21 ------------------- .../SharedDataIssueSolverTests.swift | 4 ++-- 2 files changed, 2 insertions(+), 23 deletions(-) diff --git a/WordPress/WordPressTest/DataMigratorTests.swift b/WordPress/WordPressTest/DataMigratorTests.swift index c834c515b674..916119e2f36b 100644 --- a/WordPress/WordPressTest/DataMigratorTests.swift +++ b/WordPress/WordPressTest/DataMigratorTests.swift @@ -209,27 +209,6 @@ class DataMigratorTests: XCTestCase { } } -// MARK: - CoreDataStackMock - -private final class CoreDataStackMock: CoreDataStack { - var mainContext: NSManagedObjectContext - - init(mainContext: NSManagedObjectContext) { - self.mainContext = mainContext - } - - func newDerivedContext() -> NSManagedObjectContext { - return mainContext - } - - func saveContextAndWait(_ context: NSManagedObjectContext) {} - func save(_ context: NSManagedObjectContext) {} - func save(_ context: NSManagedObjectContext, completion completionBlock: (() -> Void)?, on queue: DispatchQueue) {} - - func performAndSave(_ aBlock: @escaping (NSManagedObjectContext) -> Void) {} - func performAndSave(_ aBlock: @escaping (NSManagedObjectContext) -> Void, completion: (() -> Void)?, on queue: DispatchQueue) {} -} - // MARK: - KeychainUtilsMock private final class KeychainUtilsMock: KeychainUtils { diff --git a/WordPress/WordPressTest/SharedDataIssueSolverTests.swift b/WordPress/WordPressTest/SharedDataIssueSolverTests.swift index c857ac660c9d..d17f61ea8ddc 100644 --- a/WordPress/WordPressTest/SharedDataIssueSolverTests.swift +++ b/WordPress/WordPressTest/SharedDataIssueSolverTests.swift @@ -126,8 +126,8 @@ private extension SharedDataIssueSolverTests { // MARK: - CoreDataStackMock -private final class CoreDataStackMock: CoreDataStack { - var mainContext: NSManagedObjectContext +final class CoreDataStackMock: CoreDataStack { + private(set) var mainContext: NSManagedObjectContext init(mainContext: NSManagedObjectContext) { self.mainContext = mainContext From cb9162dc14cc2da950d3eedfc845368cb88a6a3c Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Wed, 12 Mar 2025 08:45:24 +1100 Subject: [PATCH 2/2] Declare a `sharedInstance` in `CoreDataStack` This is to decouple the Objective-C implementation from knowing about components (i.e. `ContextManager`) in the Swift layer. --- .../Sources/WordPressDataObjC/include/CoreDataStack.h | 2 ++ WordPress/Classes/Models/Blog/Blog+SelfHosted.swift | 3 ++- WordPress/Classes/Services/JetpackSocialService.swift | 7 +++++-- WordPress/Classes/Services/SharingService.swift | 9 +++++---- .../Root View/SplitViewRootPresenter+Welcome.swift | 3 ++- .../Classes/System/UITesting/UITestConfigurator.swift | 4 +++- .../BackgroundTasks/WeeklyRoundupBackgroundTask.swift | 4 ++-- WordPress/Classes/Utility/CoreData/ContextManager.swift | 6 +++--- .../Classes/Utility/Logging/WPCrashLoggingProvider.swift | 5 +++-- .../ViewRelated/Blog/Site Picker/AddSiteController.swift | 3 ++- .../Blog/Site Picker/BlogList/BlogListViewModel.swift | 5 +++-- .../EEUUSCompliance/CompliancePopoverViewModel.swift | 5 +++-- WordPress/WordPressTest/SharedDataIssueSolverTests.swift | 5 +++++ 13 files changed, 40 insertions(+), 21 deletions(-) diff --git a/Modules/Sources/WordPressDataObjC/include/CoreDataStack.h b/Modules/Sources/WordPressDataObjC/include/CoreDataStack.h index 69c79f3a95ab..27cc2076e862 100644 --- a/Modules/Sources/WordPressDataObjC/include/CoreDataStack.h +++ b/Modules/Sources/WordPressDataObjC/include/CoreDataStack.h @@ -7,6 +7,8 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, readonly, strong) NSManagedObjectContext *mainContext; ++ (id)sharedInstance; + - (NSManagedObjectContext *const)newDerivedContext DEPRECATED_MSG_ATTRIBUTE("Use `performAndSave` instead"); - (void)saveContextAndWait:(NSManagedObjectContext *)context; diff --git a/WordPress/Classes/Models/Blog/Blog+SelfHosted.swift b/WordPress/Classes/Models/Blog/Blog+SelfHosted.swift index 6855b738eb8c..64b422f916a4 100644 --- a/WordPress/Classes/Models/Blog/Blog+SelfHosted.swift +++ b/WordPress/Classes/Models/Blog/Blog+SelfHosted.swift @@ -1,6 +1,7 @@ import Foundation import CryptoKit import WordPressAPI +import WordPressData extension Blog { @@ -17,7 +18,7 @@ extension Blog { static func createRestApiBlog( with details: WpApiApplicationPasswordDetails, - in contextManager: ContextManager, + in contextManager: CoreDataStackSwift, using keychainImplementation: KeychainAccessible = KeychainUtils() ) async throws -> String { try await contextManager.performAndSave { context in diff --git a/WordPress/Classes/Services/JetpackSocialService.swift b/WordPress/Classes/Services/JetpackSocialService.swift index 579c3473683b..bcf8c59cbcc8 100644 --- a/WordPress/Classes/Services/JetpackSocialService.swift +++ b/WordPress/Classes/Services/JetpackSocialService.swift @@ -19,8 +19,11 @@ import WordPressKit /// Init method for Objective-C. /// - @objc init(contextManager: ContextManager) { - self.coreDataStack = contextManager + @objc init(contextManager: CoreDataStack) { + guard let typeCastStack = contextManager as? CoreDataStackSwift else { + fatalError("Expected a CoreDataStackSwift-conforming type even though this initializer is marked @objc.") + } + self.coreDataStack = typeCastStack } init(coreDataStack: CoreDataStackSwift = ContextManager.shared) { diff --git a/WordPress/Classes/Services/SharingService.swift b/WordPress/Classes/Services/SharingService.swift index 35be9f1ce855..2cd397f2636d 100644 --- a/WordPress/Classes/Services/SharingService.swift +++ b/WordPress/Classes/Services/SharingService.swift @@ -12,11 +12,12 @@ import WordPressKit private let coreDataStack: CoreDataStackSwift /// The initialiser for Objective-C code. - /// - /// Using `ContextManager` as the argument becuase `CoreDataStackSwift` is not accessible from Objective-C code. @objc - init(contextManager: ContextManager) { - self.coreDataStack = contextManager + init(contextManager: CoreDataStack) { + guard let typeCastStack = contextManager as? CoreDataStackSwift else { + fatalError("Expected a CoreDataStackSwift-conforming type even though this initializer is marked @objc.") + } + self.coreDataStack = typeCastStack } init(coreDataStack: CoreDataStackSwift) { diff --git a/WordPress/Classes/System/Root View/SplitViewRootPresenter+Welcome.swift b/WordPress/Classes/System/Root View/SplitViewRootPresenter+Welcome.swift index 9589f6e4726e..f34b3cdcdf5c 100644 --- a/WordPress/Classes/System/Root View/SplitViewRootPresenter+Welcome.swift +++ b/WordPress/Classes/System/Root View/SplitViewRootPresenter+Welcome.swift @@ -1,6 +1,7 @@ import Foundation import UIKit import SwiftUI +import WordPressData class WelcomeSplitViewContent: SplitViewDisplayable { let supplementary: UINavigationController @@ -9,7 +10,7 @@ class WelcomeSplitViewContent: SplitViewDisplayable { init(addSite: @escaping (AddSiteMenuViewModel.Selection) -> Void) { supplementary = UINavigationController(rootViewController: UnifiedPrologueViewController()) - let addSiteViewModel = AddSiteMenuViewModel(context: .shared, onSelection: addSite) + let addSiteViewModel = AddSiteMenuViewModel(context: ContextManager.shared, onSelection: addSite) let noSitesViewModel = NoSitesViewModel(appUIType: JetpackFeaturesRemovalCoordinator.currentAppUIType, account: nil) let noSiteView = NoSitesView(addSiteViewModel: addSiteViewModel, viewModel: noSitesViewModel) let noSitesVC = UIHostingController(rootView: noSiteView) diff --git a/WordPress/Classes/System/UITesting/UITestConfigurator.swift b/WordPress/Classes/System/UITesting/UITestConfigurator.swift index 5651cf6a8188..cc2d22ec1d12 100644 --- a/WordPress/Classes/System/UITesting/UITestConfigurator.swift +++ b/WordPress/Classes/System/UITesting/UITestConfigurator.swift @@ -37,7 +37,9 @@ struct UITestConfigurator { private static func resetEverything() { // Remove CoreData DB - ContextManager.shared.resetEverything() + // + // We can afford to force cast here because we are in a test-specific code + (ContextManager.shared as! ContextManager).resetEverything() // Clear user defaults. for key in UserDefaults.standard.dictionaryRepresentation().keys { diff --git a/WordPress/Classes/Utility/BackgroundTasks/WeeklyRoundupBackgroundTask.swift b/WordPress/Classes/Utility/BackgroundTasks/WeeklyRoundupBackgroundTask.swift index bd61d575de06..bd04075eda9d 100644 --- a/WordPress/Classes/Utility/BackgroundTasks/WeeklyRoundupBackgroundTask.swift +++ b/WordPress/Classes/Utility/BackgroundTasks/WeeklyRoundupBackgroundTask.swift @@ -330,14 +330,14 @@ class WeeklyRoundupBackgroundTask: BackgroundTask { private let eventTracker: NotificationEventTracker let runDateComponents: DateComponents let notificationScheduler: WeeklyRoundupNotificationScheduler - let coreDataStack: ContextManager + let coreDataStack: CoreDataStackSwift init( eventTracker: NotificationEventTracker = NotificationEventTracker(), runDateComponents: DateComponents? = nil, staticNotificationDateComponents: DateComponents? = nil, store: Store = Store(), - coreDataStack: ContextManager = .shared + coreDataStack: CoreDataStackSwift = ContextManager.shared ) { self.coreDataStack = coreDataStack self.eventTracker = eventTracker diff --git a/WordPress/Classes/Utility/CoreData/ContextManager.swift b/WordPress/Classes/Utility/CoreData/ContextManager.swift index 0f19780d53f9..6d846899b441 100644 --- a/WordPress/Classes/Utility/CoreData/ContextManager.swift +++ b/WordPress/Classes/Utility/CoreData/ContextManager.swift @@ -282,7 +282,7 @@ extension ContextManager { /// Tests purpose only static var overrideInstance: ContextManager? - @objc class func sharedInstance() -> ContextManager { + @objc public class func sharedInstance() -> CoreDataStack { if let overrideInstance { return overrideInstance } @@ -290,8 +290,8 @@ extension ContextManager { return ContextManager.internalSharedInstance } - static var shared: ContextManager { - return sharedInstance() + static var shared: CoreDataStackSwift { + return sharedInstance() as! CoreDataStackSwift } } diff --git a/WordPress/Classes/Utility/Logging/WPCrashLoggingProvider.swift b/WordPress/Classes/Utility/Logging/WPCrashLoggingProvider.swift index ffb960159f95..3142a83cc955 100644 --- a/WordPress/Classes/Utility/Logging/WPCrashLoggingProvider.swift +++ b/WordPress/Classes/Utility/Logging/WPCrashLoggingProvider.swift @@ -2,6 +2,7 @@ import UIKit import Combine import AutomatticTracks import AutomatticEncryptedLogs +import WordPressData /// A wrapper around the logging stack – provides shared initialization and configuration for Tracks Crash and Event Logging struct WPLoggingStack { @@ -38,9 +39,9 @@ struct WPLoggingStack { } struct WPCrashLoggingDataProvider: CrashLoggingDataProvider { - private let contextManager: ContextManager + private let contextManager: CoreDataStackSwift - init(contextManager: ContextManager = .shared) { + init(contextManager: CoreDataStackSwift = ContextManager.shared) { self.contextManager = contextManager } diff --git a/WordPress/Classes/ViewRelated/Blog/Site Picker/AddSiteController.swift b/WordPress/Classes/ViewRelated/Blog/Site Picker/AddSiteController.swift index 0772d442d9fd..e7d2f041f6a4 100644 --- a/WordPress/Classes/ViewRelated/Blog/Site Picker/AddSiteController.swift +++ b/WordPress/Classes/ViewRelated/Blog/Site Picker/AddSiteController.swift @@ -1,6 +1,7 @@ import UIKit import SwiftUI import WordPressAuthenticator +import WordPressData /// Manages the site creation flows. struct AddSiteController { @@ -80,7 +81,7 @@ struct AddSiteMenuViewModel { } } - init(context: ContextManager = .shared, onSelection: @escaping (Selection) -> Void) { + init(context: CoreDataStackSwift = ContextManager.shared, onSelection: @escaping (Selection) -> Void) { let defaultAccount = try? WPAccount.lookupDefaultWordPressComAccount(in: context.mainContext) let canAddSelfHostedSite = AppConfiguration.showAddSelfHostedSiteButton diff --git a/WordPress/Classes/ViewRelated/Blog/Site Picker/BlogList/BlogListViewModel.swift b/WordPress/Classes/ViewRelated/Blog/Site Picker/BlogList/BlogListViewModel.swift index 8ed9d257aa22..540d6d30aa19 100644 --- a/WordPress/Classes/ViewRelated/Blog/Site Picker/BlogList/BlogListViewModel.swift +++ b/WordPress/Classes/ViewRelated/Blog/Site Picker/BlogList/BlogListViewModel.swift @@ -1,5 +1,6 @@ import CoreData import SwiftUI +import WordPressData import WordPressShared final class BlogListViewModel: NSObject, ObservableObject { @@ -15,7 +16,7 @@ final class BlogListViewModel: NSObject, ObservableObject { private let configuration: BlogListConfiguration private var rawSites: [Blog] = [] private let fetchedResultsController: NSFetchedResultsController - private let contextManager: ContextManager + private let contextManager: CoreDataStackSwift private let blogService: BlogService private let eventTracker: EventTracker private let recentSitesService: RecentSitesService @@ -24,7 +25,7 @@ final class BlogListViewModel: NSObject, ObservableObject { var onAddSiteTapped: (AddSiteMenuViewModel.Selection) -> Void = { _ in } init(configuration: BlogListConfiguration = .defaultConfig, - contextManager: ContextManager = ContextManager.shared, + contextManager: CoreDataStackSwift = ContextManager.shared, recentSitesService: RecentSitesService = RecentSitesService(), eventTracker: EventTracker = DefaultEventTracker()) { self.configuration = configuration diff --git a/WordPress/Classes/ViewRelated/EEUUSCompliance/CompliancePopoverViewModel.swift b/WordPress/Classes/ViewRelated/EEUUSCompliance/CompliancePopoverViewModel.swift index c7b15044b923..e7b45f98db08 100644 --- a/WordPress/Classes/ViewRelated/EEUUSCompliance/CompliancePopoverViewModel.swift +++ b/WordPress/Classes/ViewRelated/EEUUSCompliance/CompliancePopoverViewModel.swift @@ -1,5 +1,6 @@ import Foundation import UIKit +import WordPressData import WordPressUI class CompliancePopoverViewModel: ObservableObject { @@ -13,12 +14,12 @@ class CompliancePopoverViewModel: ObservableObject { private let analyticsTracker: PrivacySettingsAnalyticsTracking private let defaults: UserDefaults - private let contextManager: ContextManager + private let contextManager: CoreDataStackSwift // MARK: - Init init(defaults: UserDefaults, - contextManager: ContextManager, + contextManager: CoreDataStackSwift, analyticsTracker: PrivacySettingsAnalyticsTracking = PrivacySettingsAnalyticsTracker()) { self.defaults = defaults self.analyticsTracker = analyticsTracker diff --git a/WordPress/WordPressTest/SharedDataIssueSolverTests.swift b/WordPress/WordPressTest/SharedDataIssueSolverTests.swift index d17f61ea8ddc..fb45820d88e1 100644 --- a/WordPress/WordPressTest/SharedDataIssueSolverTests.swift +++ b/WordPress/WordPressTest/SharedDataIssueSolverTests.swift @@ -129,10 +129,15 @@ private extension SharedDataIssueSolverTests { final class CoreDataStackMock: CoreDataStack { private(set) var mainContext: NSManagedObjectContext + private static var shared: CoreDataStack? + init(mainContext: NSManagedObjectContext) { self.mainContext = mainContext + CoreDataStackMock.shared = self } + static func sharedInstance() -> any CoreDataStack { CoreDataStackMock.shared! } + func newDerivedContext() -> NSManagedObjectContext { return mainContext }