I can confirm that the workaround prepared by @JyHu works perfect. Thank you! Your fix is going to make life easier for many users! :)
Here is Swift version:
enum RemoteViewCrashGuard {
private static var isInstalled = false
static func install() {
guard shouldInstall, !isInstalled else { return }
isInstalled = true
swizzleContainingWindowWillOrderOnScreen()
}
// MARK: - Private
/// Only macOS 27 is affected by the beta bug.
private static var shouldInstall: Bool {
ProcessInfo.processInfo.operatingSystemVersion.majorVersion == 27
}
private static func swizzleContainingWindowWillOrderOnScreen() {
guard let remoteViewClass = NSClassFromString("NSRemoteView") else { return }
let selector = NSSelectorFromString("containingWindowWillOrderOnScreen:")
guard let method = class_getInstanceMethod(remoteViewClass, selector) else { return }
// Wrap the original implementation so the assertion is caught instead of
// crashing. Only NSRemoteView's inconsistency is swallowed; anything
// else is re-raised so we don't mask unrelated bugs.
typealias OriginalFunction = @convention(c) (AnyObject, Selector, NSWindow?) -> ()
let originalIMP = method_getImplementation(method)
let callOriginal = unsafeBitCast(originalIMP, to: OriginalFunction.self)
let guardedBlock: @convention(block) (AnyObject, NSWindow?) -> () = { receiver, window in
let exception = ObjCExceptionCatcher.catchException {
callOriginal(receiver, selector, window)
}
guard let exception else { return }
if exception.name == .internalInconsistencyException,
exception.reason?.contains("NSRemoteView") == true {
print("[RemoteViewCrashGuard] Suppressed NSRemoteView assertion: \(exception.reason ?? "")")
} else {
exception.raise()
}
}
method_setImplementation(method, imp_implementationWithBlock(guardedBlock))
print(
"[RemoteViewCrashGuard] Installed for macOS \(ProcessInfo.processInfo.operatingSystemVersion.majorVersion).x"
)
}
}
ObjCExceptionCatcher.h:
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/// Bridges Objective-C exception handling to Swift, which cannot catch
/// `NSException`. Runs `tryBlock` inside an `@try/@catch` and returns any
/// raised exception instead of letting it unwind and crash the app.
@interface ObjCExceptionCatcher : NSObject
/// Runs `tryBlock`, returning the `NSException` it raised, or `nil` if it
/// completed normally.
+ (nullable NSException *)catchException:(NS_NOESCAPE void (^)(void))tryBlock;
@end
NS_ASSUME_NONNULL_END
ObjCExceptionCatcher.m
#import "ObjCExceptionCatcher.h"
@implementation ObjCExceptionCatcher
+ (NSException *)catchException:(NS_NOESCAPE void (^)(void))tryBlock {
@try {
tryBlock();
return nil;
} @catch (NSException *exception) {
return exception;
}
}
@end
Topic:
UI Frameworks
SubTopic:
AppKit