Check whether app is built in debug or release mode

Currently, if as a library author you are shipping dependencies as code, you can use the #if DEBUG preprocessor check to execute logic based on whether app is being built for Debug or Release.

My concern is more about the approach that should be taken when distributing frameworks/xcframeworks. One approach I am thinking of using is checking the presence of {CFBundleName}.debug.dylib in the main bundle. Is this approach reliable? Do you suggest any other approach?

Answered by DTS Engineer in 867275022

Earlier I wrote:

But, honestly, it sounds like a fun weekend project

And indeed it was (-:

Pasted below is some iOS code that is able to detect how your code is signed using only public APIs. To do this, it uses a sneaky combination of XPC loopback and XPC peer requirement checking.

This code comes with a bunch of caveats. Read the doc comment before you use it [1].

Share and Enjoy

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

[1] By my count the doc comments represent well over half the total number of lines (-:


import Foundation

extension CheckSelfEntitlement {

    /// Checks whether the current process claims the get-task-allow
    /// entitlement.
    ///
    /// - warning: As explained below, you shouldn’t use this routine but
    /// instead should use ``isGetTaskAllowTrue()``.  This routine exists solely
    /// to illustrate the following point.
    ///
    /// This routine checks for the presence of the entitlement, rather than
    /// checking for it being present with a particular value. In most cases
    /// checking for the presence of a Boolean entitlement is a mistake.  What
    /// if it’s present but has the the default value? In general we advise [1]
    /// against claiming an entitlement with the default value — you might as
    /// well just not claim it — but it does happen. In this case specifically,
    /// when you export an Ad Hoc signed build from Xcode, the build claims
    /// `get-task-allow` with a value of `false`. Given that reality, it’s
    /// better to check for the entitlement and its value, as illustrated by
    /// ``isGetTaskAllowTrue()``.
    ///
    /// [1] For example, this quote from [Hardened
    /// Runtime](https://developer.apple.com/documentation/security/hardened-runtime):
    ///
    /// _The default value of these Boolean entitlements is false. When Xcode
    /// signs your code, it includes an entitlement only if the value is true.
    /// If you’re manually signing code, follow this convention to ensure
    /// maximum compatibility. Don’t include an entitlement if the value is
    /// false._
    ///
    /// - Returns: The result of the check, or `nil` if the check failed.

    static func isGetTaskAllowPresent() -> Bool? {
        CheckSelfEntitlement.hasEntitlement(named: getTaskAllowEntitlementName)
    }
    
    /// Checks whether the current process claims the get-task-allow entitlement
    /// with a value of true.
    ///
    /// The get-task-allow entitlement is what the system checks for when the
    /// debugger tries to attach to your process, so this is effectively
    /// equivalent to “Is the current process Development signed?”
    ///
    /// - important: Use this and not ``isGetTaskAllowPresent()``.
    ///
    /// The get-task-allow entitlement has different values on macOS
    /// (`com.apple.security.get-task-allow`) and iOS (`get-task-allow`). This
    /// code uses ``getTaskAllowEntitlementName``, which has a compile-time
    /// conditional to return the right value.
    ///
    /// macOS only requires the get-task-allow entitlement if the process is
    /// protected in some way, typically because it has the
    /// [hardened runtime](https://developer.apple.com/documentation/security/hardened-runtime)
    /// enabled.  If you find that this check fails unexpected on macOS, check
    /// that your development environment has added the get-task-allow
    /// entitlement.
    ///
    /// - Returns: The result of the check, or `nil` if the check failed.

    static func isGetTaskAllowTrue() -> Bool? {
        CheckSelfEntitlement.hasEntitlement(named: getTaskAllowEntitlementName, withValue: true)
    }

    /// Checks whether the current process claims the APNs entitlement with a
    /// value of `production`.
    ///
    /// The APNs entitlement grants the process access to the Apple Push
    /// Notification service. The specific value determines whether the process
    /// uses the production service or the sandbox service.
    ///
    /// The APNs entitlement has different values on macOS
    /// (`com.apple.developer.aps-environment`) and iOS (`aps-environment`).
    /// This code uses ``apnsEntitlementName``, which has a compile-time
    /// conditional to return the right value.
    ///
    /// - Returns: The result of the check, or `nil` if the check failed.

    static func isAPNsProduction() -> Bool? {
        CheckSelfEntitlement.hasEntitlement(named: apnsEntitlementName, withValue: "production")
    }

    #if os(macOS)
        private static let getTaskAllowEntitlementName = "com.apple.security.get-task-allow"
        private static let apnsEntitlementName = "com.apple.developer.aps-environment"
    #else
        private static let getTaskAllowEntitlementName = "get-task-allow"
        private static let apnsEntitlementName = "aps-environment"
    #endif
}

/// Groups together routines for checking the entitlements claimed by the
/// current process.
///
/// This code supports macOS and iOS.
///
/// This code does not support the iOS simulator platform.  The simulator uses a
/// system of fake entitlements that’s incompatible with the techniques used by
/// this code. If you run this code on the simulator, you’ll always get a `nil`
/// result.
///
/// This code is not necessary on macOS.  That platform has other, more direct
/// approaches, including:
///
/// * On macOS 14.4 and later, use the
///   [LightweightCodeRequirements](https://developer.apple.com/documentation/lightweightcoderequirements)
///   framework.  Specifically, create a constraint using
///   LightweightCodeRequirements and then check that the current process meets
///   that constraint by combining
///   `SecTaskValidateForRequirement(task:requirement:)` with
///   `SecTaskCreateFromSelf(_:)`.
///
/// * On earlier versions of macOS, use
///   `SecTaskCopyValueForEntitlement(_:_:_:)`.
///
/// * Or craft a code-signing requirement and test that requirement using
///   `SecCodeCheckValidityWithErrors(_:_:_:_:)`.  For more about code-signing
///   requirements, see
///   TN3127 [Inside Code Signing: Requirements](https://developer.apple.com/documentation/technotes/tn3127-inside-code-signing-requirements).
///
/// Unfortunately none of those APIs are available on iOS (r. 165263770), which
/// is why this code exists.  This code achieves its goal by sneakily combining
/// XPC peer requirement checking with XPC loopback.
///
/// This code doesn’t support tvOS, watchOS, or visionOS because the relevant
/// XPC APIs aren’t present there (r. 165264387).
///
/// This code uses the old low-level C API because the necessary bits of the new
/// Swift API, specifically `XPCPeerRequirement`, aren’t available on iOS (r.
/// 165264387).
///
/// This code isn’t optimised for performance; if you need these results often,
/// cache it yourself.
///
/// - warning: The techniques shown here are used for introspection.  If you
/// adapt them for security purposes on macOS, make sure you understand the
/// concept of the entitlements-validate flag.  See the *Entitlements-Validated
/// Flag* section of [App Groups: macOS vs iOS: Working Towards
/// Harmony](https://developer.apple.com/forums/thread/721701).

enum CheckSelfEntitlement {
    
    /// Checks whether the current process claims the named entitlement.
    ///
    /// - warning: If you’re checking for a Boolean value, use
    /// ``hasEntitlement(named:withValue:)-(_,Bool)`` instead.  For an
    /// explanation as to why, see ``isGetTaskAllowPresent()``.
    ///
    /// - Parameter entitlement: The entitlement to check for.
    /// - Returns: The result of the check, or `nil` if the check failed.

    static func hasEntitlement(named entitlement: String) -> Bool? {
        checkEntitlement(configurator: { listener in
            xpc_connection_set_peer_entitlement_exists_requirement(listener, entitlement)
        })
    }
    
    /// Checks whether the current process claims the named entitlement with the
    /// supplied Boolean value.
    ///
    /// - Parameters:
    ///   - entitlement: The entitlement to check for.
    ///   - value: The value to check for.
    /// - Returns: The result of the check, or `nil` if the check failed.

    static func hasEntitlement(named entitlement: String, withValue value: Bool) -> Bool? {
        checkEntitlement(configurator: { listener in
            xpc_connection_set_peer_entitlement_matches_value_requirement(listener, entitlement, xpc_bool_create(value))
        })
    }
    
    /// Checks whether the current process claims the named entitlement with the
    /// supplied string value.
    ///
    /// - Parameters:
    ///   - entitlement: The entitlement to check for.
    ///   - value: The value to check for.
    /// - Returns: The result of the check, or `nil` if the check failed.

    static func hasEntitlement(named entitlement: String, withValue value: String) -> Bool? {
        checkEntitlement(configurator: { listener in
            xpc_connection_set_peer_entitlement_matches_value_requirement(listener, entitlement, xpc_string_create(value))
        })
    }
    
    /// Runs an entitlement check on the current process.
    ///
    /// This is the common implementation used by all of the `hasEntitlement(…)`
    /// methods.  It calls the `configurator` closure to configure the XPC
    /// listener to enforce the specific entitlement check.
    ///
    /// - Parameter configurator: A callback to configure the listener to
    /// implement the specific check. Typically this just calls one of the
    /// `xpc_connection_set_peer_entitlement_xyz(…)` routines.
    /// - Returns: The result of the check, or `nil` if the check failed.

    private static func checkEntitlement(configurator: (_ listener: xpc_object_t) -> Int32) -> Bool? {
    
        // Configure and start an anonymous listener.
        
        let queue = DispatchQueue(label: "check-entitlement-queue")
        let listener = xpc_connection_create(nil, queue)
        let err = configurator(listener)
        guard err == 0 else {
            // If we release a suspended listener, XPC traps )-:  So we have to
            // activate it with a dummy event handler, then cancel it, then
            // release it (well, it’s released by Swift’s ARC machinery).
            xpc_connection_set_event_handler(listener, { _ in })
            xpc_connection_activate(listener)
            xpc_connection_cancel(listener)
            return nil
        }
        startListener(listener)
        
        // Create an endpoint for that listener and connect to it.
        
        let listenerEndpoint = xpc_endpoint_create(listener)
        let client = xpc_connection_create_from_endpoint(listenerEndpoint)
        xpc_connection_set_event_handler(client) { event in
            // Ignore any events on the client connection.  We expect to only
            // receive errors here.  The exact pattern of
            // `XPC_ERROR_CONNECTION_INTERRUPTED` and
            // `XPC_ERROR_CONNECTION_INVALID` errors depends on whether the
            // connection goes through or not.
            assert(xpc_get_type(event) == XPC_TYPE_ERROR)
        }
        xpc_connection_activate(client)

        // Send an empty message to that connection, and then clean up.
        
        let message = xpc_dictionary_create(nil, nil, 0)
        let reply = xpc_connection_send_message_with_reply_sync(client, message)

        xpc_connection_cancel(client)
        xpc_connection_cancel(listener)

        // Look at the reply.  If the message went through, the entitlement
        // check succeeded and we are claiming the entitlement.  If the message
        // failed, we assume it was blocked by the entitlement check and thus we
        // are not claiming the entitlement.

        switch xpc_get_type(reply) {
        case XPC_TYPE_DICTIONARY:
            return true
        case XPC_TYPE_ERROR:
            return false
        default:
            // Should never happen…
            assert(false)
            // … but return `nil` in Release builds.
            return nil
        }
    }
    
    /// Starts the XPC listener.
    ///
    /// This listener calls ``startServer(_:)`` with any incoming connections
    /// and ignores any errors.

    private static func startListener(_ listener: xpc_object_t) {
        xpc_connection_set_event_handler(listener) { event in
            switch xpc_get_type(event) {
            case XPC_TYPE_CONNECTION:
                startServer(event)
            case XPC_TYPE_ERROR:
                // Ignore any error coming from the listener.  We expect a
                // `XPC_ERROR_CONNECTION_INVALID` error to arrive when the
                // client cancels the listener.
                break
            default:
                assert(false)
            }
        }
        xpc_connection_activate(listener)
    }
    
    /// Starts an XPC server connection.
    ///
    /// This server replies to any incoming message with an empty reply and
    /// ignores any errors.

    private static func startServer(_ server: xpc_object_t) {
        xpc_connection_set_event_handler(server) { event in
            switch xpc_get_type(event) {
            case XPC_TYPE_DICTIONARY:
                let reply = xpc_dictionary_create_reply(event)!
                xpc_connection_send_message(server, reply)
            case XPC_TYPE_ERROR:
                // Ignore any error coming from the client.  We expect a
                // `XPC_ERROR_CONNECTION_INVALID` error to arrive when the
                // client cancels its connection.
                break
            default:
                assert(false)
            }
        }
        xpc_connection_activate(server)
    }
}
Is this approach reliable?

No. You can disable preview support using a build settings, even in the Debug build configuration. Indeed, that’s something I do all the time.

Lemme confirm I understand your requirements:

  • You’re working on a framework.
  • You want to create an XCFramemwork from that.
  • And give that XCFramework to other developers.
  • And then inside that framework you want to check whether the host app was built with the Debug build configuration.

Is that right?

If so, to what end?

And are you sure that you want to check the build configuration? A lot of the time it’s not the build configuration that matters, it’s the way that the app was signed. Or the way that’s it’s run (from Xcode or like a user would). And those questions have different answers.

Is this iOS? Or macOS? Or both?

Share and Enjoy

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

Thanks for the replay  @DTS Engineer. You are right on my requirement, currently I am looking for iOS but I would be happy with a solution that works across apple platforms as well.

If so, to what end?

To answer your query on why I want to detect debug build I won't be able to share my exact use-case but I will share a parallel use-case that currently Apple uses:

Currently Apple allows developers to send push notifications to iOS with APNS. From what I understand, APNS exposes sandbox and production environments which are controlled with aps-environment entitlement. Since, Apple controls the tooling side of iOS development, they can inject entitlement based on the provisioning profile.

I want to achieve something similar to this where the REST API domain used by my framework is decided based on either provisioning profile or build type.

A lot of the time it’s not the build configuration that matters, it’s the way that the app was signed. Or the way that’s it’s run (from Xcode or like a user would). And those questions have different answers.

I think more appropriate solution for me will be deciding based on the way app was signed, something like what Apple does for APNS. I did look into it but was unable to find any way to parse the included provisioning profile. Would appreciate any help with this.

I think more appropriate solution for me will be deciding based on the way app was signed

OK.

That’s easy to do on macOS, using SecCodeCopySigningInformation. On iOS the story isn’t as rosey. Historically there’s been no supported way to do this on iOS.

Thinking about this today, I believe that recent iOS API additions make it possible, albeit in a non-obvious way. I got a proof of concept working today, and it looks promising. Unfortunately I don’t have time to flesh it out. But, honestly, it sounds like a fun weekend project so, with any luck, I’ll have an answer for you on Monday.

Share and Enjoy

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

Earlier I wrote:

But, honestly, it sounds like a fun weekend project

And indeed it was (-:

Pasted below is some iOS code that is able to detect how your code is signed using only public APIs. To do this, it uses a sneaky combination of XPC loopback and XPC peer requirement checking.

This code comes with a bunch of caveats. Read the doc comment before you use it [1].

Share and Enjoy

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

[1] By my count the doc comments represent well over half the total number of lines (-:


import Foundation

extension CheckSelfEntitlement {

    /// Checks whether the current process claims the get-task-allow
    /// entitlement.
    ///
    /// - warning: As explained below, you shouldn’t use this routine but
    /// instead should use ``isGetTaskAllowTrue()``.  This routine exists solely
    /// to illustrate the following point.
    ///
    /// This routine checks for the presence of the entitlement, rather than
    /// checking for it being present with a particular value. In most cases
    /// checking for the presence of a Boolean entitlement is a mistake.  What
    /// if it’s present but has the the default value? In general we advise [1]
    /// against claiming an entitlement with the default value — you might as
    /// well just not claim it — but it does happen. In this case specifically,
    /// when you export an Ad Hoc signed build from Xcode, the build claims
    /// `get-task-allow` with a value of `false`. Given that reality, it’s
    /// better to check for the entitlement and its value, as illustrated by
    /// ``isGetTaskAllowTrue()``.
    ///
    /// [1] For example, this quote from [Hardened
    /// Runtime](https://developer.apple.com/documentation/security/hardened-runtime):
    ///
    /// _The default value of these Boolean entitlements is false. When Xcode
    /// signs your code, it includes an entitlement only if the value is true.
    /// If you’re manually signing code, follow this convention to ensure
    /// maximum compatibility. Don’t include an entitlement if the value is
    /// false._
    ///
    /// - Returns: The result of the check, or `nil` if the check failed.

    static func isGetTaskAllowPresent() -> Bool? {
        CheckSelfEntitlement.hasEntitlement(named: getTaskAllowEntitlementName)
    }
    
    /// Checks whether the current process claims the get-task-allow entitlement
    /// with a value of true.
    ///
    /// The get-task-allow entitlement is what the system checks for when the
    /// debugger tries to attach to your process, so this is effectively
    /// equivalent to “Is the current process Development signed?”
    ///
    /// - important: Use this and not ``isGetTaskAllowPresent()``.
    ///
    /// The get-task-allow entitlement has different values on macOS
    /// (`com.apple.security.get-task-allow`) and iOS (`get-task-allow`). This
    /// code uses ``getTaskAllowEntitlementName``, which has a compile-time
    /// conditional to return the right value.
    ///
    /// macOS only requires the get-task-allow entitlement if the process is
    /// protected in some way, typically because it has the
    /// [hardened runtime](https://developer.apple.com/documentation/security/hardened-runtime)
    /// enabled.  If you find that this check fails unexpected on macOS, check
    /// that your development environment has added the get-task-allow
    /// entitlement.
    ///
    /// - Returns: The result of the check, or `nil` if the check failed.

    static func isGetTaskAllowTrue() -> Bool? {
        CheckSelfEntitlement.hasEntitlement(named: getTaskAllowEntitlementName, withValue: true)
    }

    /// Checks whether the current process claims the APNs entitlement with a
    /// value of `production`.
    ///
    /// The APNs entitlement grants the process access to the Apple Push
    /// Notification service. The specific value determines whether the process
    /// uses the production service or the sandbox service.
    ///
    /// The APNs entitlement has different values on macOS
    /// (`com.apple.developer.aps-environment`) and iOS (`aps-environment`).
    /// This code uses ``apnsEntitlementName``, which has a compile-time
    /// conditional to return the right value.
    ///
    /// - Returns: The result of the check, or `nil` if the check failed.

    static func isAPNsProduction() -> Bool? {
        CheckSelfEntitlement.hasEntitlement(named: apnsEntitlementName, withValue: "production")
    }

    #if os(macOS)
        private static let getTaskAllowEntitlementName = "com.apple.security.get-task-allow"
        private static let apnsEntitlementName = "com.apple.developer.aps-environment"
    #else
        private static let getTaskAllowEntitlementName = "get-task-allow"
        private static let apnsEntitlementName = "aps-environment"
    #endif
}

/// Groups together routines for checking the entitlements claimed by the
/// current process.
///
/// This code supports macOS and iOS.
///
/// This code does not support the iOS simulator platform.  The simulator uses a
/// system of fake entitlements that’s incompatible with the techniques used by
/// this code. If you run this code on the simulator, you’ll always get a `nil`
/// result.
///
/// This code is not necessary on macOS.  That platform has other, more direct
/// approaches, including:
///
/// * On macOS 14.4 and later, use the
///   [LightweightCodeRequirements](https://developer.apple.com/documentation/lightweightcoderequirements)
///   framework.  Specifically, create a constraint using
///   LightweightCodeRequirements and then check that the current process meets
///   that constraint by combining
///   `SecTaskValidateForRequirement(task:requirement:)` with
///   `SecTaskCreateFromSelf(_:)`.
///
/// * On earlier versions of macOS, use
///   `SecTaskCopyValueForEntitlement(_:_:_:)`.
///
/// * Or craft a code-signing requirement and test that requirement using
///   `SecCodeCheckValidityWithErrors(_:_:_:_:)`.  For more about code-signing
///   requirements, see
///   TN3127 [Inside Code Signing: Requirements](https://developer.apple.com/documentation/technotes/tn3127-inside-code-signing-requirements).
///
/// Unfortunately none of those APIs are available on iOS (r. 165263770), which
/// is why this code exists.  This code achieves its goal by sneakily combining
/// XPC peer requirement checking with XPC loopback.
///
/// This code doesn’t support tvOS, watchOS, or visionOS because the relevant
/// XPC APIs aren’t present there (r. 165264387).
///
/// This code uses the old low-level C API because the necessary bits of the new
/// Swift API, specifically `XPCPeerRequirement`, aren’t available on iOS (r.
/// 165264387).
///
/// This code isn’t optimised for performance; if you need these results often,
/// cache it yourself.
///
/// - warning: The techniques shown here are used for introspection.  If you
/// adapt them for security purposes on macOS, make sure you understand the
/// concept of the entitlements-validate flag.  See the *Entitlements-Validated
/// Flag* section of [App Groups: macOS vs iOS: Working Towards
/// Harmony](https://developer.apple.com/forums/thread/721701).

enum CheckSelfEntitlement {
    
    /// Checks whether the current process claims the named entitlement.
    ///
    /// - warning: If you’re checking for a Boolean value, use
    /// ``hasEntitlement(named:withValue:)-(_,Bool)`` instead.  For an
    /// explanation as to why, see ``isGetTaskAllowPresent()``.
    ///
    /// - Parameter entitlement: The entitlement to check for.
    /// - Returns: The result of the check, or `nil` if the check failed.

    static func hasEntitlement(named entitlement: String) -> Bool? {
        checkEntitlement(configurator: { listener in
            xpc_connection_set_peer_entitlement_exists_requirement(listener, entitlement)
        })
    }
    
    /// Checks whether the current process claims the named entitlement with the
    /// supplied Boolean value.
    ///
    /// - Parameters:
    ///   - entitlement: The entitlement to check for.
    ///   - value: The value to check for.
    /// - Returns: The result of the check, or `nil` if the check failed.

    static func hasEntitlement(named entitlement: String, withValue value: Bool) -> Bool? {
        checkEntitlement(configurator: { listener in
            xpc_connection_set_peer_entitlement_matches_value_requirement(listener, entitlement, xpc_bool_create(value))
        })
    }
    
    /// Checks whether the current process claims the named entitlement with the
    /// supplied string value.
    ///
    /// - Parameters:
    ///   - entitlement: The entitlement to check for.
    ///   - value: The value to check for.
    /// - Returns: The result of the check, or `nil` if the check failed.

    static func hasEntitlement(named entitlement: String, withValue value: String) -> Bool? {
        checkEntitlement(configurator: { listener in
            xpc_connection_set_peer_entitlement_matches_value_requirement(listener, entitlement, xpc_string_create(value))
        })
    }
    
    /// Runs an entitlement check on the current process.
    ///
    /// This is the common implementation used by all of the `hasEntitlement(…)`
    /// methods.  It calls the `configurator` closure to configure the XPC
    /// listener to enforce the specific entitlement check.
    ///
    /// - Parameter configurator: A callback to configure the listener to
    /// implement the specific check. Typically this just calls one of the
    /// `xpc_connection_set_peer_entitlement_xyz(…)` routines.
    /// - Returns: The result of the check, or `nil` if the check failed.

    private static func checkEntitlement(configurator: (_ listener: xpc_object_t) -> Int32) -> Bool? {
    
        // Configure and start an anonymous listener.
        
        let queue = DispatchQueue(label: "check-entitlement-queue")
        let listener = xpc_connection_create(nil, queue)
        let err = configurator(listener)
        guard err == 0 else {
            // If we release a suspended listener, XPC traps )-:  So we have to
            // activate it with a dummy event handler, then cancel it, then
            // release it (well, it’s released by Swift’s ARC machinery).
            xpc_connection_set_event_handler(listener, { _ in })
            xpc_connection_activate(listener)
            xpc_connection_cancel(listener)
            return nil
        }
        startListener(listener)
        
        // Create an endpoint for that listener and connect to it.
        
        let listenerEndpoint = xpc_endpoint_create(listener)
        let client = xpc_connection_create_from_endpoint(listenerEndpoint)
        xpc_connection_set_event_handler(client) { event in
            // Ignore any events on the client connection.  We expect to only
            // receive errors here.  The exact pattern of
            // `XPC_ERROR_CONNECTION_INTERRUPTED` and
            // `XPC_ERROR_CONNECTION_INVALID` errors depends on whether the
            // connection goes through or not.
            assert(xpc_get_type(event) == XPC_TYPE_ERROR)
        }
        xpc_connection_activate(client)

        // Send an empty message to that connection, and then clean up.
        
        let message = xpc_dictionary_create(nil, nil, 0)
        let reply = xpc_connection_send_message_with_reply_sync(client, message)

        xpc_connection_cancel(client)
        xpc_connection_cancel(listener)

        // Look at the reply.  If the message went through, the entitlement
        // check succeeded and we are claiming the entitlement.  If the message
        // failed, we assume it was blocked by the entitlement check and thus we
        // are not claiming the entitlement.

        switch xpc_get_type(reply) {
        case XPC_TYPE_DICTIONARY:
            return true
        case XPC_TYPE_ERROR:
            return false
        default:
            // Should never happen…
            assert(false)
            // … but return `nil` in Release builds.
            return nil
        }
    }
    
    /// Starts the XPC listener.
    ///
    /// This listener calls ``startServer(_:)`` with any incoming connections
    /// and ignores any errors.

    private static func startListener(_ listener: xpc_object_t) {
        xpc_connection_set_event_handler(listener) { event in
            switch xpc_get_type(event) {
            case XPC_TYPE_CONNECTION:
                startServer(event)
            case XPC_TYPE_ERROR:
                // Ignore any error coming from the listener.  We expect a
                // `XPC_ERROR_CONNECTION_INVALID` error to arrive when the
                // client cancels the listener.
                break
            default:
                assert(false)
            }
        }
        xpc_connection_activate(listener)
    }
    
    /// Starts an XPC server connection.
    ///
    /// This server replies to any incoming message with an empty reply and
    /// ignores any errors.

    private static func startServer(_ server: xpc_object_t) {
        xpc_connection_set_event_handler(server) { event in
            switch xpc_get_type(event) {
            case XPC_TYPE_DICTIONARY:
                let reply = xpc_dictionary_create_reply(event)!
                xpc_connection_send_message(server, reply)
            case XPC_TYPE_ERROR:
                // Ignore any error coming from the client.  We expect a
                // `XPC_ERROR_CONNECTION_INVALID` error to arrive when the
                // client cancels its connection.
                break
            default:
                assert(false)
            }
        }
        xpc_connection_activate(server)
    }
}

Thanks @DTS Engineer for the detailed snippet.

The usage of get-task-allow entitlement gave me idea for updating my previous parsing entitlement approach and I came up with this:

struct MobileProvision: Codable {
    static let current: MobileProvision = {
        let profileExtension = "mobileprovision"

        guard
            let profilePath = Bundle.main.path(forResource: "embedded", ofType: profileExtension),
            let profileString = try? String(contentsOfFile: profilePath, encoding: .isoLatin1),
            case let scanner = Scanner(string: profileString),
            scanner.scanUpToString("<plist") != nil,
            let extractedPlist = scanner.scanUpToString("</plist>"),
            let plist = extractedPlist.appending("</plist>").data(using: .isoLatin1)
        else { return .simulatorDefault() }

        let decoder = PropertyListDecoder()
        do {
            return try decoder.decode(MobileProvision.self, from: plist)
        } catch {
            return .simulatorDefault()
        }
    }()

    static func simulatorDefault() -> Self {
        return Self(entitlements: Entitlements(isDebuggable: true, apsEnvironment: .development))
    }

    let entitlements: Entitlements

    enum CodingKeys: String, CodingKey {
        case entitlements = "Entitlements"
    }

    struct Entitlements: Codable {
        let isDebuggable: Bool
        let apsEnvironment: APSEnvironment

        enum APSEnvironment: String, Codable {
            case development, production
        }

        enum CodingKeys: String, CodingKey {
            case isDebuggable = "get-task-allow"
            case apsEnvironment = "aps-environment"
        }
    }
}

This snippet has the same drawback as your snippet in the sense that it will not work on simulators (which is an acceptable compromise for me), but this works with tvOS as well (which is more priority for me than macOS). Do you think any drawback for this approach?

Personally the one drawback I see is this relies on un-documented provisioning profile structure which might break in future if Apple changes it but I doubt that will ever happen.

I will try to compare performance of both approaches and come up with some numbers.

Check whether app is built in debug or release mode
 
 
Q