From 271167d50d6c8b94ec377f403260007fbc6fcee8 Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Mon, 2 Nov 2020 14:41:23 -0500 Subject: [PATCH] Move to ready logic instead of page dependency --- .../MVMCoreAlertHandler+Extension.swift | 112 ++++++------------ MVMCoreUI/Alerts/MVMCoreAlertHandler.h | 3 - MVMCoreUI/Alerts/MVMCoreAlertHandler.m | 15 +-- MVMCoreUI/TopAlert/MVMCoreTopAlertOperation.h | 3 + MVMCoreUI/TopAlert/MVMCoreTopAlertOperation.m | 27 +++++ .../MVMCoreUITopAlertView+Extension.swift | 43 +------ 6 files changed, 71 insertions(+), 132 deletions(-) diff --git a/MVMCoreUI/Alerts/MVMCoreAlertHandler+Extension.swift b/MVMCoreUI/Alerts/MVMCoreAlertHandler+Extension.swift index 309ea8f5..364ee6cb 100644 --- a/MVMCoreUI/Alerts/MVMCoreAlertHandler+Extension.swift +++ b/MVMCoreUI/Alerts/MVMCoreAlertHandler+Extension.swift @@ -8,88 +8,50 @@ import Foundation -// Temporary, until we can move page checking logic into isReady of topAlertOperation. -@objcMembers public class NotificationPagesOperation: Operation { - public let pages: [String] - - public init(with pages: [String]) { - self.pages = pages - } - - public override var isReady: Bool { - get { - return super.isReady && isCancelled - } - } -} - -// TODO: Move this type of logic into isReady for the top alert operation... then can remove this dependency operation. public extension MVMCoreAlertHandler { - /// Adds a page type dependency to the operation - @objc func addPagesDependency(to operation: MVMCoreTopAlertOperation) { - // This notification may only show for certain pages. - guard let pages = operation.topAlertObject.json?.optionalArrayForKey("pages") as? [String], - pages.count > 0 else { return } - let pagesOperation = NotificationPagesOperation(with: pages) - pageOperations.addOperation(pagesOperation) - operation.addDependency(pagesOperation) - } - - /// Updates the pages requirement for the operation - @objc func updatePages(for operation: MVMCoreTopAlertOperation, with topAlertObject: MVMCoreTopAlertObject) { - // Add new dependencies, remove old. - let previousPages = operation.dependencies.filter { $0 as? NotificationPagesOperation != nil } - addPagesDependency(to: operation) - for pageOperation in previousPages { - pageOperation.cancel() - operation.removeDependency(pageOperation) - } - } - - /// Handles page dependencies for all top alerts with the new pageType - @objc func handleAllPagesDependency(for pageType: String?) { - var currentOperation: MVMCoreTopAlertOperation? = nil - for case let operation as MVMCoreTopAlertOperation in topAlertQueue.operations { - // Handle the currently executing opration last to avoid race conditions. - guard !operation.isExecuting else { - currentOperation = operation - continue - } - handlePageDependency(for: operation, with: pageType) - } - if let operation = currentOperation { - handlePageDependency(for: operation, with: pageType) - } - } - - /// Updates the operation based on the page type and its dependencies. - @objc func handlePageDependency(for operation: MVMCoreTopAlertOperation, with pageType: String?) { - guard !operation.isCancelled else { return } - - if let pages = operation.topAlertObject.json?.optionalArrayForKey("pages") as? [String], - pages.count > 0, - (pageType == nil || !pages.contains(pageType!)) { - if let dependency = operation.dependencies.first(where: { $0.isFinished && ($0 as? NotificationPagesOperation)?.pages == pages }) { - // Re-add the dependency if it was previously cancelled. - let pagesOperation = NotificationPagesOperation(with: pages) - pageOperations.addOperation(pagesOperation) - operation.addDependency(pagesOperation) - operation.removeDependency(dependency) + /// Re-evaluates the queue operations + @objc func reevaluteQueue() { + var highestReadyOperation: MVMCoreTopAlertOperation? + var executingOperation: MVMCoreTopAlertOperation? + let sortedOperations = topAlertQueue.operations.sorted { $0.queuePriority.rawValue > $1.queuePriority.rawValue } + for case let operation as MVMCoreTopAlertOperation in sortedOperations { + guard !operation.isCancelled, + !operation.isFinished else { continue } + operation.refreshReady() + if operation.isReady, + highestReadyOperation == nil { + highestReadyOperation = operation } if operation.isExecuting { - // If the current running operation should not show on the current page, cancel it. If it's persistent, re-add it - operation.reAddAfterCancel = operation.topAlertObject.persistent - operation.cancel() + executingOperation = operation } } + guard let currentOperation = executingOperation else { return } - // Cancel any dependency if it contains the current page. - guard let pageType = pageType else { return } - for case let operation as NotificationPagesOperation in operation.dependencies { - if operation.pages.contains(pageType) { - operation.cancel() - } + // Cancel the executing operation if it is no longer ready to run. Re-add for later if it is persistent. + guard currentOperation.isReady else { + currentOperation.reAddAfterCancel = currentOperation.topAlertObject.persistent + currentOperation.cancel() + return + } + + // If the highest priority operation is not executing, and the executing operation is persistent, cancel it. + if let newOperation = highestReadyOperation, + currentOperation != newOperation, + currentOperation.topAlertObject.persistent { + currentOperation.reAddAfterCancel = true + currentOperation.cancel() } } + + /// Registers with the notification center to know when pages change. + @objc func registerWithNotificationCenter() { + NotificationCenter.default.addObserver(self, selector: #selector(viewControllerChanged(notification:)), name: NSNotification.Name(rawValue: MVMCoreNotificationViewControllerChanged), object: nil) + } + + /// When a detail page changes, check top alerts. + @objc private func viewControllerChanged(notification: Notification) { + reevaluteQueue() + } } diff --git a/MVMCoreUI/Alerts/MVMCoreAlertHandler.h b/MVMCoreUI/Alerts/MVMCoreAlertHandler.h index f3b30efd..522c5740 100644 --- a/MVMCoreUI/Alerts/MVMCoreAlertHandler.h +++ b/MVMCoreUI/Alerts/MVMCoreAlertHandler.h @@ -23,9 +23,6 @@ // An operation queue for top alerts @property (nonnull, strong, nonatomic) NSOperationQueue *topAlertQueue; -/// Stores any page dependencies for top alerts -@property (nonnull, strong, nonatomic) NSOperationQueue *pageOperations; - /// Returns the shared instance of this singleton + (nullable instancetype)sharedAlertHandler; diff --git a/MVMCoreUI/Alerts/MVMCoreAlertHandler.m b/MVMCoreUI/Alerts/MVMCoreAlertHandler.m index 4e7be1f7..f65a1db0 100644 --- a/MVMCoreUI/Alerts/MVMCoreAlertHandler.m +++ b/MVMCoreUI/Alerts/MVMCoreAlertHandler.m @@ -14,7 +14,7 @@ #import #import #import -#import +#import @interface MVMCoreAlertHandler () @@ -42,7 +42,7 @@ self.popupAlertQueue.maxConcurrentOperationCount = 1; self.topAlertQueue = [[NSOperationQueue alloc] init]; self.topAlertQueue.maxConcurrentOperationCount = 1; - self.pageOperations = [[NSOperationQueue alloc] init]; + [self registerWithNotificationCenter]; } return self; } @@ -173,16 +173,7 @@ }]; [self.topAlertQueue addOperation:alertOperation]; - - // If the current running operation is persistent and has a lower queue priority then the added operation, cancel it and re-add it. - for (MVMCoreTopAlertOperation *operation in self.topAlertQueue.operations) { - - if (operation.isExecuting && !operation.isCancelled && operation.topAlertObject.persistent && operation.queuePriority < alertOperation.queuePriority && alertOperation.isReady) { - operation.reAddAfterCancel = YES; - [operation cancel]; - break; - } - } + [self reevaluteQueue]; } - (void)showTopAlertWithObject:(nullable MVMCoreTopAlertObject *)topAlertObject { diff --git a/MVMCoreUI/TopAlert/MVMCoreTopAlertOperation.h b/MVMCoreUI/TopAlert/MVMCoreTopAlertOperation.h index 73acec1e..86831e89 100644 --- a/MVMCoreUI/TopAlert/MVMCoreTopAlertOperation.h +++ b/MVMCoreUI/TopAlert/MVMCoreTopAlertOperation.h @@ -30,4 +30,7 @@ // Unpauses the operation, resuming any alert. - (void)unpause; +// NSOperationQueue uses KVO, this forces it to aknowledge changes +- (void)refreshReady; + @end diff --git a/MVMCoreUI/TopAlert/MVMCoreTopAlertOperation.m b/MVMCoreUI/TopAlert/MVMCoreTopAlertOperation.m index bcf9515a..f721734a 100644 --- a/MVMCoreUI/TopAlert/MVMCoreTopAlertOperation.m +++ b/MVMCoreUI/TopAlert/MVMCoreTopAlertOperation.m @@ -10,6 +10,7 @@ #import "MVMCoreTopAlertObject.h" #import "MVMCoreAlertHandler.h" #import +#import @interface MVMCoreTopAlertOperation () { __block BOOL _paused; @@ -108,6 +109,27 @@ }); } +// Ensure the current page is supported for this top alert. +- (BOOL)isReady { + BOOL isReady = [super isReady]; + if (self.isCancelled || self.isFinished) { + return isReady; + } + + NSArray *pages = [self.topAlertObject.json array:@"pages"]; + if (pages.count == 0) { + return isReady; + } + + UIViewController *detailViewController = [[MVMCoreUISplitViewController mainSplitViewController] getCurrentDetailViewController]; + if (![detailViewController conformsToProtocol:@protocol(MVMCoreViewControllerProtocol)]) { + return isReady; + } + + NSString *pageType = ((UIViewController *)detailViewController).pageType; + return isReady && [pages containsObject:pageType]; +} + - (void)main { // Always check for cancellation before launching the task. @@ -232,6 +254,11 @@ } } +- (void)refreshReady { + [self willChangeValueForKey:@"isReady"]; + [self didChangeValueForKey:@"isReady"]; +} + #pragma mark - Delegate functions - (void)topAlertViewBeginAnimation { diff --git a/MVMCoreUI/TopAlert/MVMCoreUITopAlertView+Extension.swift b/MVMCoreUI/TopAlert/MVMCoreUITopAlertView+Extension.swift index 9bceb57a..cb53b18a 100644 --- a/MVMCoreUI/TopAlert/MVMCoreUITopAlertView+Extension.swift +++ b/MVMCoreUI/TopAlert/MVMCoreUITopAlertView+Extension.swift @@ -18,7 +18,6 @@ public extension MVMCoreUITopAlertView { /// Registers with the notification center to know when json is updated. @objc func registerWithNotificationCenter() { NotificationCenter.default.addObserver(self, selector: #selector(responseJSONUpdated(notification:)), name: NSNotification.Name(rawValue: NotificationResponseLoaded), object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(viewControllerChanged(notification:)), name: NSNotification.Name(rawValue: MVMCoreNotificationViewControllerChanged), object: nil) } private func getDelegateObject() -> MVMCoreUIDelegateObject { @@ -42,42 +41,6 @@ public extension MVMCoreUITopAlertView { showTopAlert(with: model) } - /// When a detail page changes, check top alerts. - @objc private func viewControllerChanged(notification: Notification) { - guard let controller = MVMCoreUISplitViewController.main()?.getCurrentDetailViewController() as? MVMCoreViewControllerProtocol else { return } - MVMCoreAlertHandler.shared()?.handleAllPagesDependency(for: controller.pageType) - reevalutePriority() - } - - /// Re-evaluates the queue priority - private func reevalutePriority() { - guard let operations = MVMCoreAlertHandler.shared()?.topAlertQueue.operations else { return } - var highestReadyOperation: Operation? - var executingOperation: Operation? - for operation in operations { - guard !operation.isCancelled, - !operation.isFinished else { - continue - } - if operation.isReady, - highestReadyOperation == nil || operation.queuePriority.rawValue > highestReadyOperation!.queuePriority.rawValue { - highestReadyOperation = operation - } - if operation.isExecuting { - executingOperation = operation - } - } - - // If the highest priority operation is not executing, and the executing operation is persistent, cancel it. - if let newOperation = highestReadyOperation, - let currentOperation = executingOperation as? MVMCoreTopAlertOperation, - currentOperation != newOperation, - currentOperation.topAlertObject.persistent { - currentOperation.reAddAfterCancel = true - currentOperation.cancel() - } - } - /// Decodes the json into a TopNotificationModel private func decodeTopNotification(with json: [AnyHashable: Any], delegateObject: MVMCoreUIDelegateObject?) -> TopNotificationModel? { do { @@ -95,8 +58,6 @@ public extension MVMCoreUITopAlertView { let object = model.createTopAlertObject() guard !checkAndUpdateExisting(with: object), let operation = MVMCoreTopAlertOperation(topAlertObject: object) else { return } - MVMCoreAlertHandler.shared()?.addPagesDependency(to: operation) - MVMCoreAlertHandler.shared()?.handlePageDependency(for: operation, with: (MVMCoreUISplitViewController.main()?.getCurrentDetailViewController() as? MVMCoreViewControllerProtocol)?.pageType) MVMCoreAlertHandler.shared()?.add(operation) } @@ -107,9 +68,7 @@ public extension MVMCoreUITopAlertView { guard topAlertObject.json != nil, operation.topAlertObject.type == topAlertObject.type else { continue } operation.update(with: topAlertObject) - MVMCoreAlertHandler.shared()?.updatePages(for: operation, with: topAlertObject) - MVMCoreAlertHandler.shared()?.handlePageDependency(for: operation, with: (MVMCoreUISplitViewController.main()?.getCurrentDetailViewController() as? MVMCoreViewControllerProtocol)?.pageType) - reevalutePriority() + MVMCoreAlertHandler.shared()?.reevaluteQueue() return true } return false