如何让AppKit macOS应用无阻塞检查AppleScript控制其他应用权限
无阻塞检查Apple事件自动化权限并触发授权弹窗
1. 无阻塞权限检查
你可以通过Security框架的SMCopyAuthorizationRights接口直接查询当前应用是否拥有对目标App的Automation权限,这个操作耗时极短,也可以放到后台队列执行完全不影响主线程。
Objective-C示例:
#import <Security/Security.h> BOOL hasAutomationPermissionForTargetApp(NSString *targetBundleID) { AuthorizationRef authRef; OSStatus status = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &authRef); if (status != errAuthorizationSuccess) { AuthorizationFree(authRef, kAuthorizationFlagDefaults); return NO; } AuthorizationItem authItem = { .name = "com.apple.security.automation.apple-events", .valueLength = targetBundleID.length, .value = (void *)targetBundleID.UTF8String, .flags = 0 }; AuthorizationRights authRights = {1, &authItem}; AuthorizationFlags flags = kAuthorizationFlagExtendRights | kAuthorizationFlagPreAuthorize | kAuthorizationFlagInteractionAllowed; status = AuthorizationCopyRights(authRef, &authRights, NULL, flags, NULL); BOOL hasPermission = (status == errAuthorizationSuccess); AuthorizationFree(authRef, kAuthorizationFlagDefaults); return hasPermission; }
把检查逻辑放到后台队列,避免阻塞主线程:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ BOOL hasPermission = hasAutomationPermissionForTargetApp(@"com.apple.Safari"); // 替换为目标App的Bundle ID dispatch_async(dispatch_get_main_queue(), ^{ // 回到主线程处理结果 if (!hasPermission) { triggerAutomationAuthorizationForTargetApp(@"com.apple.Safari"); } }); });
2. 异步触发授权弹窗
如果检测到无权限,你可以在后台队列执行一段极简的AppleScript(比如仅获取目标App名称),这样即使系统弹出授权对话框,阻塞的也只是后台线程,完全不影响主应用的UI交互。
Objective-C示例:
void triggerAutomationAuthorizationForTargetApp(NSString *targetBundleID) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSString *scriptSource = [NSString stringWithFormat:@"tell application id \"%@\" to name", targetBundleID]; NSAppleScript *script = [[NSAppleScript alloc] initWithSource:scriptSource]; NSDictionary *errorInfo = nil; [script executeAndReturnError:&errorInfo]; // 无需处理执行结果,只要触发授权弹窗即可 }); }
3. Swift版本参考
import Security func hasAutomationPermission(for targetBundleID: String) -> Bool { var authRef: AuthorizationRef? let status = AuthorizationCreate(nil, nil, [], &authRef) guard status == errAuthorizationSuccess, let auth = authRef else { return false } defer { AuthorizationFree(auth, []) } let authItem = AuthorizationItem( name: "com.apple.security.automation.apple-events", valueLength: targetBundleID.utf8.count, value: UnsafeMutableRawPointer(mutating: targetBundleID.utf8CString), flags: 0 ) var authRights = AuthorizationRights(count: 1, items: &authItem) let flags: AuthorizationFlags = [.extendRights, .preAuthorize, .interactionAllowed] let authStatus = AuthorizationCopyRights(auth, &authRights, nil, flags, nil) return authStatus == errAuthorizationSuccess } // 异步检查并触发授权 DispatchQueue.global().async { let hasPermission = self.hasAutomationPermission(for: "com.apple.Safari") DispatchQueue.main.async { if !hasPermission { self.triggerAuthorization(for: "com.apple.Safari") } } } func triggerAuthorization(for targetBundleID: String) { DispatchQueue.global().async { let scriptSource = "tell application id \"\(targetBundleID)\" to name" let script = NSAppleScript(source: scriptSource) var error: NSDictionary? script?.executeAndReturnError(&error) } }
关键注意事项
- 必须使用目标App的Bundle ID而非显示名称,避免同名App导致的识别错误。
- 后台执行AppleScript时,授权弹窗的阻塞范围仅限于后台线程,主应用的交互完全不受影响。
- 如果用户在系统设置中手动修改了权限,建议延迟几秒后重新检查权限状态,保证结果准确性。
内容的提问来源于stack exchange,提问作者Bri Bri
相关产品推荐
相关产品推荐

