Swift

Unofficial AI summary of the WWDC26 group lab. May contain mistakes.

What are notable Swift 6.4 features worth knowing about?

  • Swift 6.4 includes general diagnostic improvements and the @diagnose attribute for fine-grained warning control.
  • @diagnose can suppress deprecation warnings in specific code regions or opt into warnings that are off by default (e.g., strict memory safety, strict concurrency).
  • async defer and cancellation shields improve resource cleanup in structured concurrency.
  • A new open-source subprocess package, Foundation performance improvements, and APIs like ProgressManager are also new.
  • Embedded Swift improvements make bare-metal and firmware targets easier to use with familiar Swift code.

How is data ownership transferred between isolation domains?

  • Swift concurrency uses region-based isolation for transferring non-sendable data between actors.
  • The original actor must no longer access the data after the transfer.
  • The sending keyword can be used to explicitly annotate parameters or return types for safe transfer.
  • For storing such values, unsafe opt-outs are currently available, with an active pitch for a disconnected data type to preserve transfer properties.

What are best practices and pitfalls when using Swift Structured Concurrency?

  • Lean into structured concurrency assertively; avoid escape hatches.
  • Refactor smaller code parts into single units.
  • Prefer task groups over task.detached or Task.init for mainline flow.
  • Use objects with life cycles that fit task groups' lexical scope.
  • Consider with-style functions or deinit-based cleanup for lexical scoping.
  • Avoid fanning out too much; write linear asynchronous code within task groups.
  • Use cancellation shields for asynchronous cleanup operations in canceled contexts, especially for resources like file I/O or database transactions.
  • async defer blocks can be placed within cancellation shields if they contain await calls that might suspend.
  • Non-sendable types help reason about linear data flow within asynchronous control flow, preventing accidental escapes.
  • Explicitly marking ephemeral data types as non-sendable can simplify data models and improve performance by ensuring cheap passing of intermediate types.
  • Don't overuse unchecked Sendable during Swift 6 migration — it deprives the project of the compiler's safety benefits.
  • Fork-join, scatter-gather, and similar patterns are useful models for structured concurrency when developers have repetitive parallel work.

What is the overhead of unnecessary protocol conformances like Sendable, Equatable, Hashable, Identifiable, Comparable?

  • Conformances like Equatable and Hashable incur a cost because the associated equality or hashing functions must be available, even if not directly used. The compiler keeps this code around for potential runtime discovery (e.g., via as? casts).
  • Sendable is a tag with no runtime representation and thus no overhead.
  • Adding generic overloads on protocols can potentially increase compilation time by widening the set of types that satisfy type-checking problems.
  • API design should prioritize meaningful conformances; don't conform just because it's possible.

What is the cleanest architectural pattern to stop @MainActor contagion in legacy apps without sacrificing Swift 6 safety?

  • Propagating @MainActor annotations requires making code async or adding the annotation elsewhere.
  • Option 1: Enable main actor by default mode for the entire module if most code belongs on the main actor. Annotate exceptions explicitly.
  • Option 2: Start with "leaf types" and work outwards.
  • Option 3: Mark specific methods as non-isolated if they don't touch mutable state, reducing the need for all uses of the type to be on @MainActor.
  • Consider making static variables immutable (let) if they are never mutated, as these don't require @MainActor annotation.
  • Refer to the "Migrate the app to Swift 6" session for code-along examples.

What are the most essential modern Swift features or official resources for high efficiency and great performance?

  • Profile first: Use tools like Instruments (especially the flame graph and top functions views) to identify performance bottlenecks.
  • Review WWDC sessions on Swift performance, such as the Swift performance using Instruments session.
  • Explore efficient data structures like Span and UniqueArray (available in the Swift Collections package).
  • Apply fundamental computer science principles: use correct algorithms and consider Big O notation.

Should redundant Swift 6 concurrency annotations be removed after the isolation model changes?

  • Redundant annotations are not harmful and can be left in place.
  • Some developers prefer explicit annotations for clarity, documentation, or to prevent future regressions.
  • Explicit annotations can serve as documentation of intent (e.g., "this type absolutely should not be sendable").
  • Removing redundant annotations is optional; when developers prefer explicitness, developers can keep them.
  • Developers can now put non-isolated directly on an extension (Swift 6.0/6.2), simplifying cleanup of per-method annotations.
  • Adding comments explaining the rationale behind explicit annotations can be helpful for future understanding.

Why is UserDefaults not Sendable if it's documented as thread-safe?

  • Some classes in Foundation, while thread-safe, are not marked Sendable due to potential subclassing issues (e.g., NSString is immutable and Sendable, but NSMutableString is a mutable subclass and not Sendable).
  • To avoid propagating a "not Sendable" status to all subclasses, some classes were marked as not Sendable by default.
  • Swift 6.4 introduces ~Sendable (tilde Sendable) which allows more accurate annotation. This indicates a lack of Sendable conformance, allowing subclasses to opt-in if they don't introduce mutable state.
  • The standard global UserDefaults is expected to have a Sendable conformance.

Is it good practice to migrate to using borrow and mutate instead of get and set altogether?

  • borrow and mutate are part of Swift's ownership model and can be more efficient as they avoid copies and extra code by directly referencing data.
  • borrow provides a reference to data held elsewhere.
  • mutate provides a mutable reference, requiring exclusive access to the data.
  • get and set are more general: get produces a value (potentially computed), and set can involve arbitrary code to make changes.
  • Recommendation: Use borrow and mutate for performance-sensitive scenarios where data is already sharing data already stored. Use get and set for all other cases.

How do app developers choose between app-focused and systems/embedded Swift features?

  • Swift is designed with progressive disclosure; do not need to learn all features immediately.
  • Measure first: Use profiling tools to identify performance issues before adopting advanced features.
  • Advanced features like non-copyable types add complexity and should only be used when a measured need exists.
  • Features are designed to work together; aim for complementary usage rather than mixing and matching unnecessarily.
  • When when issues arise a problem (e.g., a performance bottleneck), there's likely a Swift tool to help. Learn about that specific tool when the need arises.
  • Language features are not "collectibles"; focus on solving problems rather than collecting features.
  • Advanced performance features are often most effective in isolated, "hot path" areas of code.

How do type inference, generics, and associated types affect incremental build performance?

  • Features like type inference, generics, and associated types can affect build performance in extreme cases, but generally don't impact overall project build times.
  • Slow module emission is often related to the number of imported modules.
  • Use explicit module builds (now on by default in Xcode) and the build timeline in Xcode to diagnose where the compiler is spending its time.
  • Look for excess dependencies that might be causing rebuilds.
  • Performance tuning the build requires analyzing where the compiler is spending its time.

Would the Swift team approach concurrency differently if designing it today?

  • The behavior of non-isolated async functions is a key area of reflection.
  • Initially, non-isolated async functions were designed to always switch to the global concurrent thread pool.
  • Experience showed that this could lead to data race safety errors when passing non-sendable types between actor-isolated contexts and these functions.
  • The behavior was changed in Swift 6.2 to make non-isolated async functions stay on the context they were called from (e.g., remain on the main actor if called from there).
  • This change avoids issues with concurrent access to non-sendable values.
  • Offloading work to the global concurrent thread pool is still useful but should be explicit and opt-in.
  • The revised model is more approachable, mirroring non-concurrent behavior and making explicit choices about introducing concurrency.

What are notable Swift Package Manager build improvements in recent releases?

  • The primary improvement in Swift 6.4 is the unification of build system implementations for Swift Package Manager (SPM) in Xcode and the open-source swift build tool.
  • A preview shipped in Swift 6.3; unification is on by default in Swift 6.4.
  • Both now use the Swift build package, leading to a single point of maintenance for bug fixes and improvements.
  • This unification brings performance optimizations from the Swift build system to normal package builds.
  • Explicit modules are now available for package builds.
  • The build system can better parallelize module builds, improving overall build performance.
  • These improvements are under-the-hood and do not require opt-in.

What's the one Swift feature most developers don't know exists but should know?

  • inlineable attribute combined with inline never:
    • inlineable unlocks optimization opportunities beyond just inlining, such as generic specialization and effects propagation, especially across module boundaries.
    • Combining inlineable with inline never is a powerful performance tool for generic code with cold paths, preventing the compiler from inlining potentially large, infrequently used code while allowing the fast path to be inlined.
  • Type annotations with as:
    • Using as with a specific type can help resolve ambiguity errors from the compiler, influence overload resolution, infer type parameters, or provide more precise error messages.
  • Swift as an open-source project:
    • Development happens in the open on forums.swift.org. Participation is valuable for all developers, not just those on non-Apple platforms. Feedback on APIs, diagnostics, and proposals is welcome.
    • Confusing compiler diagnostics can be improved by describing what was unclear on the forums — no compiler expertise required.
    • SwiftSyntax is a good entry point for contributing to the open-source project.
  • ~Sendable (tilde Sendable):
    • This feature allows more accurate annotation of types that are not Sendable, distinguishing between types that are definitively not Sendable and those where subclasses might be Sendable.
  • Integer overflow APIs:
    • Swift provides APIs to carry overflow, allowing computations to complete and then checking for overflow at the end. This is useful for integers that might grow large.
  • Key Paths:
    • Key paths allow referring to properties in the abstract without an instance, useful for abstracting over properties and building elegant API constructions.
  • Swiftly:
    • A tool for easily installing newer toolchains and trying out experimental Swift features, usable directly from Xcode.

What's left in language evolution to get tuples to finally conform to Equatable, Hashable, Comparable etc. conditionally?

  • This requires an evolution of parameter packs.
  • The necessary components are:
    • The ability to write an extension over a tuple type where element types are represented by a parameter pack.
    • A where clause condition on each element in the parameter pack (e.g., where each T conforms to Protocol).
    • Syntax for writing a parameterized extension where the extension itself has a parameter pack.
  • An experimental implementation exists in the compiler repository.
  • This evolution aims to provide general conditional conformance for tuples to protocols, not just the three mentioned.

How should high-frequency sensor data reach an @Observable model on the main actor without blocking the UI?

  • The definition of "high frequency" is crucial; if it's significantly lower than UI update rates, less work may be needed.
  • Avoid excessive context switching: Minimize switches between the background actor and the main actor's observable data model.
  • Debouncing: Accumulate data and coalesce multiple updates into fewer events before sending them.
  • Data Loss: Consider acceptable data loss. Not every nanosecond update may need to be rendered.
  • SwiftUI and @Observable naturally coalesce updates for efficiency.
  • Ensure UI updates are only for what the user needs to see, potentially splitting the problem into halves if necessary.
  • Swift Async Algorithms offers a debounce implementation for async sequences.

Why is a tuple more expensive than a struct when returned from a method? Or is it a custom case and there's actually no difference?

  • Tuples and structs are handled differently by the compiler.
  • Structs are generally passed as a single entity.
  • Tuples are often "exploded" inside the compiler, meaning each element is treated as a separate parameter.
  • For very large tuples, this "exploding" might be less performant than passing a single, large struct.
  • Further investigation via actual code and optimizer output is needed for a definitive answer. A GitHub issue with a sample project is requested.

What are notable lesser-known Swift quality-of-life features?

  • Iterable Protocols (forward-looking): Aims to make complex topics like non-copyable/non-escapable types feel natural, enabling operations like for-in over spans.
  • Bidirectional Type Inference and Generics Diagnostics: The ability to write less explicit type information and receive error messages at the implementation site of generic code, rather than the use site.
  • Avoiding Mutable Shared State: Features like copy-on-write and the ownership model provide confidence that code is purely a function of its inputs and outputs, simplifying complex code.
  • Generic System and Protocol Extensions: Extending protocols to add operations creates reusable generic algorithms that are natural and simple to use, leading to better code patterns.
  • Integer Overflow APIs: APIs that allow carrying overflow, enabling computations to complete and then checking for errors, useful for integers that might grow large.
  • Key Paths: Abstracting over properties without an instance, enabling elegant API constructions.
  • Swiftly: A tool for easily installing and trying newer toolchains and experimental features.