Cocoa Launch at Login

Cocoa Launch at Login

Mac Developer Library中查看文档:

有两种方式可以添加登录启动项:使用Service Management framework 或 使用 shared file list

这两种方式有差别:

  • 使用Service Management framework 在系统的登录项中是不可见的。只有卸载App才能移除登录项

  • 使用 shared file list 在系统的登录项中是可见的。用户可以直接在面板上控制他们。(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.) 原文还有一句大意是指这个API有隐患,所以在OS X 10.10系统上 API被大量Deprecated

下面具体介绍一下 这两种的实现

Adding Login Item Using the Service Management Framework

应用要包含一个 Helper Target,类型也是应用。设置路径为Contents/Library/LoginItems。设置这个Helper Target info.plist LSBackgroundOnly 为 YES。

使用 SMLoginItemSetEnabled方法授权Helper,需要传入两个参数:CFStringRef指的是Helper Target的bundle identifier. Boolean 表示期望状态。 传入 true Helper 会每次在用户登录的时候启动。 传入false 来注销这个登录项,不会在用户登录的时候启动。 这个方法会返回一个bool值,如果是ture的话,说明期望状态设置成功。如果是false的话,可能存在多个helper

比如 一个公司发布过很多App,但其中的helper bundle identifier都是相同的。 这样会导致在同一个电脑上只有一个App(greatest bundle version number)能成功注册登录项。

这个方式,我未实践过。贴几个相关的博文吧:

The Launch At Login Sandbox Project

Adding a preference to launch sandboxed app on login

在SandBox沙盒下实现程序的开机启动

Adding Login Items Using a Shared File List

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
- (BOOL)isLaunchAtStartup {
// See if the app is currently in LoginItems.
LSSharedFileListItemRef itemRef = [self itemRefInLoginItems];
// Store away that boolean.
BOOL isInList = itemRef != nil;
// Release the reference if it exists.
if (itemRef != nil) CFRelease(itemRef);

return isInList;
}

- (IBAction)toggleLaunchAtStartup:(id)sender {
// Toggle the state.
BOOL shouldBeToggled = ![self isLaunchAtStartup];
// Get the LoginItems list.
LSSharedFileListRef loginItemsRef = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL);
if (loginItemsRef == nil) return;
if (shouldBeToggled) {
// Add the app to the LoginItems list.
CFURLRef appUrl = (__bridge CFURLRef)[NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]];
LSSharedFileListItemRef itemRef = LSSharedFileListInsertItemURL(loginItemsRef, kLSSharedFileListItemLast, NULL, NULL, appUrl, NULL, NULL);
if (itemRef) CFRelease(itemRef);
[[self.statusMenu itemAtIndex:0] setTitle:@"取消开机自启动"];
}
else {
// Remove the app from the LoginItems list.
LSSharedFileListItemRef itemRef = [self itemRefInLoginItems];
LSSharedFileListItemRemove(loginItemsRef,itemRef);
if (itemRef != nil) CFRelease(itemRef);
[[self.statusMenu itemAtIndex:0] setTitle:@"设置开机自启动"];
}
CFRelease(loginItemsRef);
}

- (LSSharedFileListItemRef)itemRefInLoginItems {
LSSharedFileListItemRef res = nil;

// Get the app's URL.
NSURL *bundleURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]];
// Get the LoginItems list.
LSSharedFileListRef loginItemsRef = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL);
if (loginItemsRef == nil) return nil;
// Iterate over the LoginItems.
NSArray *loginItems = (__bridge NSArray *)LSSharedFileListCopySnapshot(loginItemsRef, nil);
for (id item in loginItems) {
LSSharedFileListItemRef itemRef = (__bridge LSSharedFileListItemRef)(item);
CFURLRef itemURLRef;
if (LSSharedFileListItemResolve(itemRef, 0, &itemURLRef, NULL) == noErr) {
// Again, use toll-free bridging.
NSURL *itemURL = (__bridge NSURL *)itemURLRef;
if ([itemURL isEqual:bundleURL]) {
res = itemRef;
break;
}
}
}
// Retain the LoginItem reference.
if (res != nil) CFRetain(res);
CFRelease(loginItemsRef);
CFRelease((__bridge CFTypeRef)(loginItems));

return res;
}

launch on startup

主要特别注意的是以上方法大都是 Deprecated in OS X V10.10 所以如果你要开发Deployment Target 10.10 以上的App的话不推荐使用这个方法。显然这些API在之后的OS X更新版本中,将会无法调用。导致你的App出现编译问题。

当然介于这个方式比较简单,你仍然想使用的话。你可以设置Deployment Target为 10.9 来使用这些在 10.10被废弃的API。是没有问题的。我就是这么干的,😊

Deprecated APIs

In previous versions of OS X, it is possible to add login items by sending an Apple event, by using the CFPreferences API, and by manually editing a property list file. These approaches are deprecated.

If you need to maintain compatibility with versions of OS X prior to v10.5, the preferred approach is to use Apple events; for details, see LoginItemsAE. Using the CFPreferences API is an acceptable alternative. You should not directly edit the property list file on any version of OS X.

早些 OS X版本的API 和实现方式,现在也许行不通了。我就不翻译了。