The Intent Result

In the last article we discussed the main entry point into the App Intent world which is the AppIntent protocol. In this article we will break down the perform function of this protocol.

func perform() async throws -> some IntentResult

Now before we get into the App Intent parts of this function I just want to briefly touch on two parts of this function call that don’t relate directly to the App Intent library but maybe new to some people.

async and throws
  • async is a keyword used to indicate that a function is asynchronous, meaning it performs work that may take time to complete and doesn’t block the main thread while waiting. When a function is marked with async, it can be suspended and resumed later, allowing the app to handle other tasks concurrently. You call async functions with the await keyword, which pauses the function’s execution until the asynchronous task completes, helping to manage long-running operations without freezing the UI or blocking other operations.

    Key Points:

    • Non-blocking: Allows functions to run without freezing the main thread.

    • Suspension and Resumption: Async functions can pause execution and continue later, optimizing task management.

    • await Keyword: Used to call async functions, indicating where execution pauses until the task completes.

It's especially useful for network requests, file handling, and any operation that could delay the main thread in an iOS app.

  • throws is a keyword used in function declarations to indicate that the function can throw an error. A function marked with throws must handle errors internally or pass them up the call stack for the caller to handle. When calling a throws function, you use the try keyword to catch and manage potential errors, making it clear where failure might occur.

    Key Points:

    • Error Propagation: Functions with throws pass errors up the call chain if not handled directly.

    • try Keyword: Used to call throws functions, indicating error handling is required.

    • Error Handling Mechanism: Allows handling runtime issues gracefully, like failed network calls or invalid inputs.

This is essential for robust apps, as it keeps unexpected failures manageable and improves overall stability.

Now that those two keywords are out of the way, the next thing to dive into is the return parameter of the perform function.

some IntentResult

What is an IntentResult? Well, IntentResult is a protocol that Apple doesn’t intend for you to implement directly.

Instead of implementing this protocol, use the ReturnsValue, OpensAppIntent, ProvidesDialog, and ShowsSnippetView type aliases on your perform()implementation in combination with the result()

Apple then goes on to give this code example.

func perform() async throws -> some ReturnsValue<Int> & OpensAppIntent {
    .result(value: 1, opensIntent: MyOpensIntent())
}

So, what exactly then is the IntentResult protocol doing if we don’t actually implement it. Well, it guarantees that anything you return has to conform to IntentResult. This allows you to pick and choose which protocols you want to return (like in the previous example) as long as that protocol inherits from the IntentResult protocol.

some ReturnsValue<Int> & OpensAppIntent

Something with this return signature would conform to both ReturnsValue and OpensAppIntent while still maintaining original adherence to

func perform() async throws -> some IntentResult

and that is because both of those protocols inherit from IntentResult

The IntentResult also defines a bunch of static functions name result that seem to just return self, but that have a whole but of signatures. A few examples include:

static func result() -> Self where Self == IntentResultContainer<Never, Never, Never, Never>

static func result<Value, OpensAppIntent, Content>(
    value: Value,
    opensIntent: OpensAppIntent,
    dialog: IntentDialog,
    view: Content = EmptyView()
) -> Self where Self == IntentResultContainer<Value, OpensAppIntent, _SnippetViewContainer, IntentDialog>, Value : _IntentValue, OpensAppIntent : AppIntent, Content : View

From these two examples you can see that the static result function returns Self where Self is a IntentResultContainer. So, if we were to look at a very simple perform function we may see something like this:

func perform() async throws -> some IntentResult {
    return .result()
}

This would mean that perform() is returning an IntentResultContainer<Never, Never, Never, Never>. Which would be an IntentResultContainer that doesn't have a Value, OpensAppIntent, _SnippetContainer, or an IntentDialog. As another example we could have:

func perform() async throws -> some ReturnsValue<Int> & ProvidesDialog & ShowsSnippetView {
    return .result(value: 23, opensIntent: self , dialog: .init("Dialog"), view: Text("Snippet"))
}

This is a perform function that returns every possible option. It returns a value of type Int with value 23, an opensIntent of self (meaning it would just open itself again), a dialog that just says "dialog" and a SwiftUI Text view that just says "Snippet"

0
Subscribe to my newsletter

Read articles from Nicholas Hartman directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Nicholas Hartman
Nicholas Hartman