Cocoa开发之APP开机自启动

原文: http://www.skyfox.org/cocoa-sandbox-app-launch-at-login.html

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


随后点击+ 把helper app添加进来.

在主APP Target中设置Build Setting 下Strip Debug Symbols During Copy为NO, 这个是默认的为No

分别开启主APP Target和Helper的App Sandbox

代码

主APP Target的自启动开关实践中

- (IBAction)switchChanged:(ITSwitch *)itSwitch {
    NSLog(@"Switch (%@) is %@", itSwitch, itSwitch.checked ? @"checked" : @"unchecked");
    if(itSwitch.checked)
    {
        [self daemon:YES];
    }
    else
    {
        [self daemon:NO];
    }
}
-(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

- (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];
}
  • 判断是不是开机自启动
- (BOOL)isStartAtLogin {
    NSDictionary *dict = (__bridge NSDictionary*)SMJobCopyDictionary(kSMDomainUserLaunchd,
                                                            CFSTR("org.skyfox.iSimulatorHelper"));
    BOOL contains = (dict!=NULL);
    return contains;
}

对应的终端查看命令 launchctl print-disabled "user/$(id -u)"

其他问题

当关闭开机启动的时候,发现console.app中还会显示这些log

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.

手动编辑配置文件删除:

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"
launchctl remove com.annoying.service

重启, 运行

确认列表中没有你的helper

 launchctl print-disabled "user/$(id -u)"

注意:你的主程序必须在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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值