如何通过C++调用Objective-C Runtime实现macOS本地通知功能
macOS纯C++调用UNUserNotificationCenter发本地通知的修复方案
核心错误排查
你原有代码存在以下几个关键问题:
- 没有提前申请通知权限:UNUserNotificationCenter要求必须先向用户请求通知授权,获得许可后才能正常推送通知
- 类对象和实例对象混用:修改
title、body属性、构造通知请求时,你错误传入了UNMutableNotificationContent的类对象contentClass,实际应该传入你创建的实例对象content - 直接访问实例变量不可靠:苹果系统类的内部实例变量名不保证稳定,应该调用官方公开的setter方法设置属性,不要通过
object_setIvar直接改ivar objc_msgSend未做类型强转:不同架构下可变参数的函数调用约定和固定参数不同,直接调用多参数的objc_msgSend会出现参数传递错误,必须先强转为对应参数类型的函数指针- 进程提前退出:添加通知请求是异步操作,你在调用后直接退出进程,通知还没来得及触发就被系统回收了,需要增加等待逻辑保证进程存活到通知触发
修复后的完整代码
#define OBJC_OLD_DISPATCH_PROTOTYPES 1 #include <CoreFoundation/CoreFoundation.h> #include <objc/objc-runtime.h> #include <unistd.h> int main(int argc, char** argv) { // 1. 先申请通知权限 id center = objc_msgSend((id)objc_getClass("UNUserNotificationCenter"), sel_registerName("currentNotificationCenter")); typedef void (*AuthorizationCompletionHandler)(bool granted, id error); void auth_handler(bool granted, id error) { if (!granted) return; // 权限被拒绝直接退出 } // 强转objc_msgSend为固定参数的函数指针再调用 ((void (*)(id, SEL, unsigned long long, AuthorizationCompletionHandler))objc_msgSend)( center, sel_registerName("requestAuthorizationWithOptions:completionHandler:"), 1 << 0 | 1 << 1, // UNAuthorizationOptionAlert + UNAuthorizationOptionSound auth_handler ); // 等待权限申请回调完成 sleep(1); // 2. 构造通知内容 Class contentClass = objc_getClass("UNMutableNotificationContent"); id content = objc_msgSend((id)contentClass, sel_registerName("alloc")); content = objc_msgSend(content, sel_registerName("init")); // 调用setter方法设置属性,不要直接改ivar id title = objc_msgSend((id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), "通知标题"); ((void (*)(id, SEL, id))objc_msgSend)(content, sel_registerName("setTitle:"), title); id body = objc_msgSend((id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), "这是纯C++调用Objective-C Runtime发送的本地通知"); ((void (*)(id, SEL, id))objc_msgSend)(content, sel_registerName("setBody:"), body); // 3. 构造通知触发器 Class triggerClass = objc_getClass("UNTimeIntervalNotificationTrigger"); id trigger = ((id (*)(id, SEL, NSTimeInterval, bool))objc_msgSend)( triggerClass, sel_registerName("triggerWithTimeInterval:repeats:"), 5, false ); // 4. 构造通知请求 id requestId = objc_msgSend((id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), "test_notification_id"); Class requestClass = objc_getClass("UNNotificationRequest"); id request = ((id (*)(id, SEL, id, id, id))objc_msgSend)( requestClass, sel_registerName("requestWithIdentifier:content:trigger:"), requestId, content, trigger ); // 5. 添加通知请求到通知中心 ((void (*)(id, SEL, id, void (^)(id error)))objc_msgSend)( center, sel_registerName("addNotificationRequest:withCompletionHandler:"), request, nil ); // 等待通知触发 sleep(6); // 释放资源 objc_msgSend(content, sel_registerName("release")); return 0; }
编译运行说明
- 编译时需要链接依赖框架,编译参数示例:
clang++ main.cpp -o notify_test -framework CoreFoundation -framework Foundation -framework UserNotifications -lobjc - 首次运行会弹出权限申请弹窗,点击允许后,后续运行就能正常收到通知
- 如果收不到通知,可以到系统设置>通知>找到你编译的程序,确认通知权限已经开启
- 如果你要集成到现有C++项目中,可以把通知相关逻辑封装为单独的macOS平台适配模块,不影响跨平台代码架构
内容的提问来源于stack exchange,提问作者hrn
相关产品推荐
相关产品推荐

