[SMAppService] Is there any useful debug log provided by the SMAppService APIs?

I'm trying to create a very simple project where an application contains a helper application and the helper application is defined as a LaunchAgent by the main application.

So far, when I call agentServiceWithPlistName:, and then request the status, I get the value 3 (i.e.SMAppServiceStatusNotFound).

I checked that the main .app bundle did not have obvious issues:

  • there is definitely a .plist in Contents/Library/LaunchAgents.
  • the .plist definitely defines the BundleProgram value.
  • there is definitely a .app helper application and the relative path pointed by the BundleProgram key looks definitely correct.

[Q] Are there some useful logs provided by the SMAppService APIs/framework that can provide an idea why a service is not found?

I haven't seen any so far in Console.app. I have not seen so far a hint in the documentation that would suggest that this SMAppService mechanism requires an app to be notarized or codesigned with a level above (Run locally).

Extra Question: Is there an official example for these new APIs?

Accepted Reply

then request the status, I get the value 3

I think you’re being mislead by that status value. It doesn’t mean that things have gone wrong, but rather than the system knows nothing about your agent yet. That is, status is returning information about the service as it’s installed in the system, not about your property list.

Is there an official example for these new APIs?

Not that I’m aware of. Having said that, assuming a familiarity with launchd agents, I’ve found that this API is pretty straightforward to start out with. Indeed, I just took it for a spin:

  1. I created a new Mac app test project.

  2. Within that, I created an agent target from the command-line tool template.

  3. I changed the code to look like this:

    import Foundation
    
    func main() {
        while true {
            print("Hello Cruel World!")
            sleep(10)
        }
    }
    
    main()
    
  4. I added an Embed Helper Tools build phase, per the Embed the helper tool section of Embedding a command-line tool in a sandboxed app.

  5. I added a com.example.apple-samplecode.Test721737.agent.plist property list to the project.

  6. I changed it to look like this:

    …
    <plist version="1.0">
    <dict>
        <key>Label</key>
        <string>com.example.apple-samplecode.Test721737.agent</string>
        <key>BundleProgram</key>
        <string>Contents/MacOS/com.example.apple-samplecode.Test721737.agent</string>
    </dict>
    </plist>
    
  7. I removed it from the app’s Copy Bundle Resources build phase and added it to a new Copy Agent Property Lists build phase, targeting Contents/Library/LaunchAgents.

  8. In the app I wired up a button to run this code:

    let service = SMAppService.agent(plistName: "com.example.apple-samplecode.Test721737.agent.plist")
    
    @IBAction
    private func registerAction(_ sender: Any) {
        do {
            print("will register, status: \(service.status.rawValue)")
            try service.register()
            print("did register")
        } catch {
            print("did not register, error: \(error)")
        }
    }    
    
  9. I built the app. It’s structure looks like this:

    % find Test721737.app
    Test721737.app
    Test721737.app/Contents
    Test721737.app/Contents/_CodeSignature
    Test721737.app/Contents/_CodeSignature/CodeResources
    Test721737.app/Contents/MacOS
    Test721737.app/Contents/MacOS/com.example.apple-samplecode.Test721737.agent
    Test721737.app/Contents/MacOS/Test721737
    Test721737.app/Contents/Resources
    Test721737.app/Contents/Resources/MainMenu.nib
    Test721737.app/Contents/Library
    Test721737.app/Contents/Library/LaunchAgents
    Test721737.app/Contents/Library/LaunchAgents/com.example.apple-samplecode.Test721737.agent.plist
    Test721737.app/Contents/Info.plist
    Test721737.app/Contents/PkgInfo
    
  10. I clicked on the button to register the agent:

    will register, status: 3
    did register
    
  11. In Terminal, I saw the agent registered:

    % launchctl list | grep com.example.apple-samplecode.Test721737.agent
    -      0   com.example.apple-samplecode.Test721737.agent
    
  12. The agent isn’t running because it’s configured to launch on demand, and there is no demand. I manually start it and then confirmed that it’s now running:

    % launchctl start com.example.apple-samplecode.Test721737.agent
    % launchctl list | grep com.example.apple-samplecode.Test721737.agent
    53068   0   com.example.apple-samplecode.Test721737.agent
    

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Add a Comment

Replies

then request the status, I get the value 3

I think you’re being mislead by that status value. It doesn’t mean that things have gone wrong, but rather than the system knows nothing about your agent yet. That is, status is returning information about the service as it’s installed in the system, not about your property list.

Is there an official example for these new APIs?

Not that I’m aware of. Having said that, assuming a familiarity with launchd agents, I’ve found that this API is pretty straightforward to start out with. Indeed, I just took it for a spin:

  1. I created a new Mac app test project.

  2. Within that, I created an agent target from the command-line tool template.

  3. I changed the code to look like this:

    import Foundation
    
    func main() {
        while true {
            print("Hello Cruel World!")
            sleep(10)
        }
    }
    
    main()
    
  4. I added an Embed Helper Tools build phase, per the Embed the helper tool section of Embedding a command-line tool in a sandboxed app.

  5. I added a com.example.apple-samplecode.Test721737.agent.plist property list to the project.

  6. I changed it to look like this:

    …
    <plist version="1.0">
    <dict>
        <key>Label</key>
        <string>com.example.apple-samplecode.Test721737.agent</string>
        <key>BundleProgram</key>
        <string>Contents/MacOS/com.example.apple-samplecode.Test721737.agent</string>
    </dict>
    </plist>
    
  7. I removed it from the app’s Copy Bundle Resources build phase and added it to a new Copy Agent Property Lists build phase, targeting Contents/Library/LaunchAgents.

  8. In the app I wired up a button to run this code:

    let service = SMAppService.agent(plistName: "com.example.apple-samplecode.Test721737.agent.plist")
    
    @IBAction
    private func registerAction(_ sender: Any) {
        do {
            print("will register, status: \(service.status.rawValue)")
            try service.register()
            print("did register")
        } catch {
            print("did not register, error: \(error)")
        }
    }    
    
  9. I built the app. It’s structure looks like this:

    % find Test721737.app
    Test721737.app
    Test721737.app/Contents
    Test721737.app/Contents/_CodeSignature
    Test721737.app/Contents/_CodeSignature/CodeResources
    Test721737.app/Contents/MacOS
    Test721737.app/Contents/MacOS/com.example.apple-samplecode.Test721737.agent
    Test721737.app/Contents/MacOS/Test721737
    Test721737.app/Contents/Resources
    Test721737.app/Contents/Resources/MainMenu.nib
    Test721737.app/Contents/Library
    Test721737.app/Contents/Library/LaunchAgents
    Test721737.app/Contents/Library/LaunchAgents/com.example.apple-samplecode.Test721737.agent.plist
    Test721737.app/Contents/Info.plist
    Test721737.app/Contents/PkgInfo
    
  10. I clicked on the button to register the agent:

    will register, status: 3
    did register
    
  11. In Terminal, I saw the agent registered:

    % launchctl list | grep com.example.apple-samplecode.Test721737.agent
    -      0   com.example.apple-samplecode.Test721737.agent
    
  12. The agent isn’t running because it’s configured to launch on demand, and there is no demand. I manually start it and then confirmed that it’s now running:

    % launchctl start com.example.apple-samplecode.Test721737.agent
    % launchctl list | grep com.example.apple-samplecode.Test721737.agent
    53068   0   com.example.apple-samplecode.Test721737.agent
    

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Add a Comment

Thank you very much for the detailed answer. I will check on my side if it's just the registering call that is missing.

Regarding the status value, I'm apparently not the only one that is mislead because the documentation clearly states that this means there is an error:

Both inline and online documentations:

SMAppServiceNotFound An error occurred and no such service could be found

While I'm looking into this new framework, I must confess that I find the documentation to be rather:

  • confusing with important pieces of information being available in the inline documentation but not, AFAIK, in the online documentation. e.g the requirement for applications to be notarized to embed LaunchDaemons.
  • incoherent with the returned values, whether a value is actually returned or not. e.g. the registerAndReturnError: method where the inline documentation states that true and false are returned, or where both the inline and online documentation state that CoreFoundation enum values are returned by this method whereas these are just possible error codes of the NSError * out parameter.
  • confusing when it comes to the case where an embedded LaunchAgent or LaunchDaemon, or their corresponding PropertyList are updated. This is a part where a project example would provide some clarification. From the way it looks so far, the documentation seems to suggest that the version management of embedded agents or daemons could be worse than the embedded System Extensions' one and could make the whole thing unusable.

I'm also surprised by the method prototypes for register and unregister which, unless I'm mistaken, do not follow the Cocoa pattern and are strongly suggesting that a NSError * is being returned.

I'm surprised that there are no recommendations in the documentation regarding the POSIX permissions for the property list or embedded launch agents/daemons considering that these are usual show stoppers for the legacy agents or daemons.

I'm disappointed that there are no Cocoa error domain string or error code values and that it is required to use CoreFoundation values.

I filed a few Feedback assistant tickets about this.

I filed a few Feedback assistant tickets about this.

Thanks.

Where were the bug numbers?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Feedback IDs:

FB11875368 - deprecated constants being used

FB11873325 - registerAndReturnError: weird prototype and incorrect documentation

FB11873316 - registerAndReturnError: incorrect documentation

FB11873303 - missing information in online documentation

FB11873295 - documentation is scary when it comes to updated plist or items.

It also looks like, from what I'm observing, that the register and unregister APIs do no behave as documented. In the headers, it is written that when trying to register an already registered item or unregistering an already unregistered item, an error will be returned but the returned BOOL is YES and the out NSError * is nil.

If the service is already registered, this API will return error kSMErrorAlreadyRegistered

If the service is already unregistered, this API will return error kSMErrorJobNotFound

Side note: it looks like that the forum code is not ready for FB# greater than 9999999 when it creates the helpful link.