macOS app 开机自启动虽然有很多种方法,但是在需要上架APPStore的情况下,访问沙盒外的文件会被拒绝.
苹果官方提供了两种方式: Service Management framework 和 shared file list
There are two ways to add a login item: using the Service Management framework, and using a shared file list
Login items installed using the Service Management framework are not visible in System Preferences and can only be removed by the application that installed them.
Login items installed using a shared file list are visible in System Preferences; users have direct control over them. If you use this API, your login item can be disabled by the user, so any other application that communicates with it it should have reasonable fallback behavior in case the login item is disabled.
Service Management framework
Service Management framework 在系统的登录项中是不可见的。只有卸载App才能移除登录项
1.这里认为你已经有了一个将要被启动的主工程与主Target
我的app名为iSimulator, ServiceManagement.framework
2.在主工程添加自动启动Target
命名最好是主Target名+Helper 比如iSimulatorHelper
3.配置XXXHelper
- 删除XXXHelper中的windows与Menu,让它没有可展示的Window。
- 设置XXXHelper的Info中Application is background only为YES
- 设置XXXHelper中Build Setting下skip install为YES
4.在主APP Target中添加CopyFile到Contents/Library/LoginItems
在主APP Target中设置Build Setting 下Strip Debug Symbols During Copy为NO, 这个是默认的为No
分别开启主APP Target和Helper的App Sandbox
代码
主APP Target的自启动开关实践中
1 2 3 4 5 6 7 8 9 10 11 |
- (IBAction)switchChanged:(ITSwitch *)itSwitch { NSLog(@"Switch (%@) is %@", itSwitch, itSwitch.checked ? @"checked" : @"unchecked"); if(itSwitch.checked) { [self daemon:YES]; } else { [self daemon:NO]; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
-(void)daemon:(Boolean)install{ NSString *helperPath = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:@"Contents/Library/LoginItems/iSimulatorHelper.app"]; if (![[NSFileManager defaultManager] fileExistsAtPath:helperPath]) { return; } NSURL *helperUrl = [NSURL fileURLWithPath:helperPath]; // Registering helper app if (LSRegisterURL((__bridge CFURLRef)helperUrl, true) != noErr) { NSLog(@"LSRegisterURL failed!"); } // Setting login // com.xxx.xxx为Helper的BundleID,ture/false设置开启还是关闭 if (!SMLoginItemSetEnabled((CFStringRef)@"org.skyfox.iSimulatorHelper",install)) { NSLog(@"SMLoginItemSetEnabled failed!"); } NSString *mainAPP = [NSBundle mainBundle].bundleIdentifier?:@"org.skyfox.iSimulator"; BOOL alreadRunning = NO; NSArray *runnings = [NSWorkspace sharedWorkspace].runningApplications; for (NSRunningApplication *app in runnings) { if ([app.bundleIdentifier isEqualToString:mainAPP]) { alreadRunning = YES; break; } } if (alreadRunning) { [[NSDistributedNotificationCenter defaultCenter]postNotificationName:@"killme" object:[NSBundle mainBundle].bundleIdentifier]; } } |
Helper的AppDelegate
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification { NSString *mainAPP = @"org.skyfox.iSimulator"; // NSArray *runningArray = [NSRunningApplication runningApplicationsWithBundleIdentifier:mainAPP]; BOOL alreadRunning = NO; NSArray *runnings = [NSWorkspace sharedWorkspace].runningApplications; for (NSRunningApplication *app in runnings) { if ([app.bundleIdentifier isEqualToString:mainAPP]) { alreadRunning = YES; break; } } if (!alreadRunning) { [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(terminate) name:@"killme" object:mainAPP]; NSString *appPath = [[NSBundle mainBundle] bundlePath]; appPath = [appPath stringByReplacingOccurrencesOfString:@"/Contents/Library/LoginItems/iSimulatorHelper.app" withString:@""]; appPath = [appPath stringByAppendingPathComponent:@"Contents/MacOS/iSimulator"]; if (![[NSFileManager defaultManager] fileExistsAtPath:appPath]) { return; } [[NSWorkspace sharedWorkspace] launchApplication:appPath]; }else{ [self terminate]; } } - (void)terminate{ [NSApp terminate:nil]; } |
判断是不是开机自启动
1 2 3 4 5 6 |
- (BOOL)isStartAtLogin { NSDictionary *dict = (__bridge NSDictionary*)SMJobCopyDictionary(kSMDomainUserLaunchd, CFSTR("org.skyfox.iSimulatorHelper")); BOOL contains = (dict!=NULL); return contains; } |
对应的终端查看命令
1 |
launchctl print-disabled "user/$(id -u)" |
其他问题
当关闭开机启动的时候,发现console.app中还会显示这些log
1 2 |
Feb 24 10:51:57 Jakey-mini com.apple.xpc.launchd[1] (org.skyfox.iSimulatorHelper[797]): Could not resolve CFBundleIdentifier specified by service: -10814: org.skyfox.iSimulatorHelper Feb 24 10:51:57 Jakey-mini com.apple.xpc.launchd[1] (org.skyfox.iSimulatorHelper): Service only ran for 0 seconds. Pushing respawn out by 10 seconds. |
手动编辑配置文件删除:
1 2 |
sudo /Applications/Xcode.app/Contents/MacOS/Xcode "/private/var/db/com.apple.xpc.launchd/loginitems.$(id -u).plist" sudo /Applications/Xcode.app/Contents/MacOS/Xcode "/private/var/db/com.apple.xpc.launchd/disabled.$(id -u).plist" |
1 |
launchctl remove com.annoying.service |
重启, 运行
1 |
launchctl print-disabled "user/$(id -u)" |
确认列表中没有你的helper
注意:你的主程序必须在Application的目录下,开机启动的设置才会生效,否则会失败。并且主app要用开发者证书打包。
https://developer.apple.com/library/content/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingLoginItems.html
https://developer.apple.com/library/content/documentation/Security/Conceptual/AppSandboxDesignGuide/DesigningYourSandbox/DesigningYourSandbox.html
转载请注明:天狐博客 » Cocoa开发之APP开机自启动