Swift
Non-sendable types
Swift programming
Swift concurrency
Swift development

How do you work with Non-sendable types in swift?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Understanding Non-Sendable Types in Swift

When working with concurrency in Swift, understanding "sendable" types is crucial. Apple's Swift Concurrency model introduces the Sendable protocol to ensure data integrity across concurrent executions. While some types are inherently sendable, others are "non-sendable" or need special attention to make them conform to Sendable. This article delves into handling these non-sendable types, focusing on technical explanations and examples.

What are Sendable and Non-Sendable Types?

At its core, the Sendable protocol guarantees safe data passage across Swift's concurrency boundaries, such as among threads or asynchronous task hierarchies. A type conforming to Sendable ensures that using it concurrently doesn’t lead to data races or undefined behavior.

Sendable Protocol

swift
protocol Sendable {}
  • Sendable Types: Types that can safely be sent across concurrency domains without risking undefined behavior. For example, all value types like integers, floats, and strings are inherently Sendable.
  • Non-Sendable Types: Types that are not conforming to Sendable, typically due to references to shared mutable data, such as classes or external resources like file handles and network connections.

Identifying Non-Sendable Types

Swift 5.5 introduced compile-time checks to automatically assert the Sendable conformance of types used across actors or tasks. The compiler will produce errors if non-sendable types are used unsafely across concurrency boundaries.

swift
1class MyClass {
2    var data: [Int] = []
3}
4
5func example() async {
6    let myObject = MyClass()
7    await Task {
8        print(myObject.data) // Error: MyClass does not conform to Sendable
9    }
10}

In the example above, attempting to use an instance of MyClass within a Task causes a compile-time error because MyClass does not conform to Sendable.

Working with Non-Sendable Types

Custom Sendable Conformance

For types you control, consider conforming to Sendable explicitly, ensuring the structure doesn’t mutate or use isolation techniques.

swift
1import Foundation
2
3final class SafeClass: @unchecked Sendable {
4    private let queue = DispatchQueue(label: "com.example.safeclass.queue")
5    private var _data: [Int] = []
6
7    var data: [Int] {
8        queue.sync { _data }
9    }
10
11    func safeAdd(_ value: Int) {
12        queue.async {
13            self._data.append(value)
14        }
15    }
16}

Key Strategies:

  • Use of Isolation: Employ queues or other synchronization mechanisms to prevent data races.
  • Final and @unchecked Sendable: Mark classes as final to avoid subclassing issues, and use @unchecked Sendable judiciously to bypass the compiler’s checks, indicating you take full responsibility for ensuring safety.

Sendable Design Alternatives

Sometimes the right design option might be to avoid needing the type to be sendable:

  • Immutability: Convert mutable types into immutable variants using structs or enums.
  • Data Isolation: Use concurrent-safe containers or wrapper classes to encapsulate mutable data safely.

Common Pitfalls and Considerations

There are some potential pitfalls and considerations when working with non-sendable types:

IssueDescriptionSolution
Shared StateMutable shared state can lead to race conditions.Use locks or queues for synchronization.
Complexity OverheadConforming complex types with dependencies to Sendable can result in intricate code.Simplify design or refactor into multiple sendable parts.
PerformanceOver-synchronization can impact performance.Evaluate the necessity of synchronization on a case-by-case basis.

Conclusion

Swift's concurrency model facilitates safe multi-threaded operations, with Sendable types playing a pivotal role in ensuring data safety across task boundaries. Handling non-sendable types requires careful consideration of immutability, isolation, and synchronization strategies. Employ these practices effectively to leverage Swift's concurrency model, maintaining both safety and performance in your applications.

By understanding and applying these techniques, you can efficiently design applications that uphold concurrency safety, making the most of modern Swift's robust capabilities.


Course illustration
Course illustration

All Rights Reserved.