Move to ready logic instead of page dependency

This commit is contained in:
Pfeil, Scott Robert 2020-11-02 14:41:23 -05:00
parent c6709dd59c
commit 271167d50d
6 changed files with 71 additions and 132 deletions

View File

@ -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()
}
}

View File

@ -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;

View File

@ -14,7 +14,7 @@
#import <MVMCore/MVMCoreJSONConstants.h>
#import <MVMCore/NSDictionary+MFConvenience.h>
#import <MVMCore/NSArray+MFConvenience.h>
#import <MVMCore/MVMCore-Swift.h>
#import <MVMCoreUI/MVMCoreUI-Swift.h>
@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 {

View File

@ -30,4 +30,7 @@
// Unpauses the operation, resuming any alert.
- (void)unpause;
// NSOperationQueue uses KVO, this forces it to aknowledge changes
- (void)refreshReady;
@end

View File

@ -10,6 +10,7 @@
#import "MVMCoreTopAlertObject.h"
#import "MVMCoreAlertHandler.h"
#import <MVMCoreUI/MVMCoreUI-Swift.h>
#import <MVMCoreUI/MVMCoreUISplitViewController.h>
@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<NSString *> *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<MVMCoreViewControllerProtocol> *)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 {

View File

@ -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