How to manage Login Items in Swift
• 2 minute read
There is a Mac App Store and Sandbox compatible method to implement automatic launch on login. Checking the enablement status before macOS 13 Ventura is a bit tricky, though.
In a major rewrite I had to reimplement Objective-C code in Swift. That also included the management of automatically starting the app on login to a user session on macOS. I do not want to cover the full topic here as I think there are sufficient resources out there in the web. Instead I just want to focus on the nasty interaction between Swift and Core Foundation which is necessary to check for the current status.
What previously was written in Objective-C like this:
- (BOOL)autostart {
CFArrayRef cfJobDicts = SMCopyAllJobDictionaries(kSMDomainUserLaunchd);
NSArray* jobDicts = CFBridgingRelease(cfJobDicts);
if (jobDicts && [jobDicts count] > 0) {
for (NSDictionary* job in jobDicts) {
if ([HELPER_APP_BUNDLE_ID isEqualToString:[job objectForKey:@"Label"]]) {
return [[job objectForKey:@"OnDemand"] boolValue];
}
}
}
return NO;
}
Can now be written in Swift like this:
var autostarterIsEnabled: Bool {
let identifier = autostarterBundleIdentifier
let array = SMCopyAllJobDictionaries(kSMDomainUserLaunchd).takeRetainedValue()
let count = CFArrayGetCount(array)
for iteration in 0..<count {
let job = unsafeBitCast(CFArrayGetValueAtIndex(array, iteration), to: NSDictionary.self)
guard let label = job["Label"] as? String else {
assertionFailure()
continue
}
if identifier == label {
guard let result = job["OnDemand"] as? Bool else {
assertionFailure()
return false
}
return result
}
}
return false
}
Accessing Core Foundation means leaving some of the safety and comforts of Swift. This is the territory of unmanaged memory where we have to take care again. It is more straight forward in Objective-C.
Actually changing the status is quite simple with this single call, as in Objective-C:
SMLoginItemSetEnabled(autostarterBundleIdentifier as CFString, true)
Luckily, macOS 13 Ventura introduced the new SMAppService API.
This simplifies and replaces the way described above.
However, if you have an existing codebase or need to support older macOS releases, too, then you will have to stick with the old method.
SMCopyAllJobDictionaries()
was deprecated for years without any replacement API and caused compiler warnings.
Now it has arrived.