DEV Community

SameX
SameX

Posted on

HarmonyOS Next struct value type characteristics practical: copy semantics and state isolation

In HarmonyOS Next, struct is a typical representative of value types, and its replication semantics and state isolation characteristics are crucial in data modeling.Understanding the behavioral rules of value types can effectively avoid the hidden dangers caused by state sharing, especially in multi-threading, component communication and other scenarios.This article combines development practice to analyze the core characteristics and best practices of struct value type.

1. In-depth analysis of the copy behavior of value types

1.1 Copy semantics of assignment and parameter transfer

When an instance of struct is assigned, passed a parameter, or returned as a function, a complete copy will be generated, and the original instance and the state of the copy are isolated from each other.

Assignment scenario case

struct Point {  
  var x: Int64, y: Int64  
}  
var p1 = Point(x: 10, y: 20)  
var p2 = p1 // Copy the instance
p1.x = 5 // Modify the x value of p1
print(p2.x) // Output: 20 (the x value of p2 has not changed)
Enter fullscreen mode Exit fullscreen mode

1.2 The impact of member types on replication

  • Value type member: Recursively copy all member values ​​(such as Int64/String/struct)
  • Reference type member: Only copy the reference address, not the object itself (such as class instance)

Mixed Type Cases

class SharedData {  
  var value: Int64 = 0  
}  
struct Container {  
var intValue: Int64 // Value type member
var objValue: SharedData // Reference type member
}  
let obj = SharedData()  
var c1 = Container(intValue: 10, objValue: obj)  
var c2 = c1 // Copy the instance
c1.intValue = 20 // Modify the value type member, c2 will not be affected
c1.objValue.value = 30 // Modify the reference type member, and c2.objValue.value changes synchronously
print(c2.intValue) // Output: 10 (value type isolation)
print(c2.objValue.value) // Output: 30 (reference type sharing)
Enter fullscreen mode Exit fullscreen mode

1.3 Comparison of core differences with reference types

Properties struct (value type) class (reference type)
Assignment/Parameter transfer behavior Generate new copy, status isolation Share the same instance, status synchronization
Memory allocation Stack allocation (efficient in small data volume) Heap allocation (requires GC management)
Equality judgment equal member values ​​are equal equal reference addresses are equal
Applicable scenarios Lightweight, independent data Complex logic, state sharing

2. Immutability design of value types

2.1 Instance immutability of let declaration

All members of the struct instance declared using let cannot be modified, and the call to the mut function is prohibited during the compilation period.

Compilation period verification case

struct ImmutablePoint {  
  let x: Int64, y: Int64  
  public mut func move(dx: Int64) {  
x += dx // Error: The members declared by let are immutable
  }  
}  
let p = ImmutablePoint(x: 10, y: 20)  
// p.move(dx: 5) // Error: The mut function cannot be called in the instance declared by let
Enter fullscreen mode Exit fullscreen mode

2.2 Advantages of Immutable Design

  • Thread Safety: Avoid race conditions in multi-threaded environments
  • Semantic clear: Clearly identify read-only data through let
  • Performance Optimization: Reduce unnecessary copy generation (the compiler can optimize the replication of immutable instances)

Recommended practice: Read-only configuration structure

let appConfig = AppConfig(  
  apiUrl: "https://api.example.com",  
  timeout: 5000  
)  
// appConfig.timeout = 6000 // Error: The instance declared by let is immutable
Enter fullscreen mode Exit fullscreen mode

2.3 A mix of variable and immutable

In scenarios where partial mutability is required, the struct members can be divided into let and var to accurately control variability.

struct Buffer {  
let capacity: Int64 // Immutable: Fixed capacity
var data: [Int64] // Variable: Dynamic update of data
  public mut func append(value: Int64) {  
    if data.length < capacity {  
      data.append(value)  
    }  
  }  
}  
var buffer = Buffer(capacity: 10, data: [])  
buffer.append(value: 5) // Legal: Modify var member
// buffer.capacity = 20 // Error: let member is immutable
Enter fullscreen mode Exit fullscreen mode

3. Performance optimization strategy for value types

3.1 Avoid unnecessary copy generation

Scenario 1: Use inout when transferring parameters to function

Use the inout parameter to avoid copying large struct instances and directly modify the original value.

struct LargeData {  
var data: [Int64] // Assume that contains a large amount of data
}  
func processData(inout data: LargeData) {  
// Modify data directly to avoid copying
}  
var data = LargeData(data: Array(repeating: 0, count: 10000))  
processData(inout: &data) // Pass in reference to reduce memory overhead
Enter fullscreen mode Exit fullscreen mode

Scenario 2: Reuse existing instances instead of creating new replicas

struct Counter {  
  var count: Int64 = 0  
  public mut func increment() { count += 1 }  
}  
var counter = Counter()  
counter.increment() // directly modify the instance to avoid creating new copies
// let newCounter = counter.increment() // Counterexample: Create a copy and discard it
Enter fullscreen mode Exit fullscreen mode

3.2 Compilation period replication optimization

The compiler will optimize the replication of immutable struct and even eliminate redundant replicas (such as direct memory reuse on the stack).

Example: Copy optimization for immutable instances

const let point = Point(x: 10, y: 20)  
let copyPoint = point // The compiler may be optimized to refer to the same address (immutable scenario)
Enter fullscreen mode Exit fullscreen mode

3.3 The principle of splitting large structures

When struct contains a large number of members, it is split into multiple small struct to reduce the amount of data replicated in a single time.

Counterexample: Single large structure

struct MonolithicData {  
  var field1: Int64  
  var field2: String  
  var field3: Float64  
// More fields...
}  
// All fields need to be copied during copying, and the performance is low
Enter fullscreen mode Exit fullscreen mode

Optimization: Split into functional modules

struct MetaData { var field1: Int64 }  
struct ContentData { var field2: String, field3: Float64 }  
struct CombinedData {  
  var meta: MetaData  
  var content: ContentData  
}  
// Copy some data as needed to reduce overhead
Enter fullscreen mode Exit fullscreen mode

4. Typical application scenarios of value types

4.1 State management of UI components

Take advantage of the immutability of value types to achieve pure update of component state and avoid side effects.

@Entry  
struct CounterView {  
@State private counter: Counter = Counter() // Value type status
  build() {  
    Column {  
      Text("Count: \(counter.count)")  
      Button("Increment")  
        .onClick {  
// Create a new copy and update the status
          counter = Counter(count: counter.count + 1)  
        }  
    }  
}  
// Counter is the value type, and the state change triggers the UI to re-render
Enter fullscreen mode Exit fullscreen mode

4.2 Thread-safe delivery of log data

In a multithreaded environment, the replica mechanism of value types ensures the integrity of log data.

struct LogEntry {  
  let timestamp: Int64  
  let message: String  
}  
func logMessage(message: String) {  
  let entry = LogEntry(timestamp: now(), message: message)  
// Pass the entry copy across threads, the original data is not affected by modification
  ThreadPool.submit { processLog(entry) }  
}  
Enter fullscreen mode Exit fullscreen mode

4.3 Parameter encapsulation of network requests

Encapsulate the requested parameters into value types to avoid unexpected modification of the parameters before the request is sent.

struct ApiRequest {  
  let url: String  
  let method: String  
  let headers: [String: String]  
}  
func sendRequest(request: ApiRequest) {  
// Send a request, request is a copy, the original parameters remain unchanged
}  
let request = ApiRequest(  
  url: "https://api.example.com/data",  
  method: "GET",  
  headers: ["Authorization": "Bearer token"]  
)  
sendRequest(request: request)  
Enter fullscreen mode Exit fullscreen mode

5. Common traps and solutions

5.1 Shared traps for referencing type members

When the struct of the value type contains members of the reference type, you need to pay attention to the side effects of the shared state.

Problem Scenario

struct User {  
var profile: Profile // Profile is class type
}  
var user1 = User(profile: Profile())  
var user2 = user1 // Copy struct instance but share Profile reference
user1.profile.name = "Alice"  
print(user2.profile.name) // Output: Alice (reference type member synchronous change)
Enter fullscreen mode Exit fullscreen mode

Solution

  • Use immutable reference types (such as class members modified by let)
  • Deep copy reference type member (need to custom copy logic)
struct User {  
let profile: Profile // Use let to avoid unexpected modifications
  init(profile: Profile) {  
self.profile = profile.copy() // Deep copy
  }  
}  
Enter fullscreen mode Exit fullscreen mode

5.2 Cooperation between value types and responsive frameworks

In ArkUI, the change of the value type instance modified by @State will trigger the UI update, so you need to pay attention to the update timing.

Correct usage

@Entry  
struct ValueTypeState {  
  @State private point: Point = Point(x: 0, y: 0)  
  build() {  
    Button("Move")  
      .onClick {  
// New instance must be created to trigger responsive updates
        point = Point(x: point.x + 1, y: point.y + 1)  
      }  
  }  
}  
Enter fullscreen mode Exit fullscreen mode

5.3 Copy control for performance-sensitive scenarios

In high-frequency operations, avoid copying large struct instances in loops, and use indexes or references first.

Pre-optimization

for i in 0..<1000 {  
let copy = largeStruct // every loop replication, poor performance
  process(copy)  
}  
Enter fullscreen mode Exit fullscreen mode

Optimized

let reference = &largeStruct // Use pointer (if supported) or inout parameters
for i in 0..<1000 {  
process(inout: reference) // Avoid duplicate copying
}  
Enter fullscreen mode Exit fullscreen mode

Conclusion

The value type characteristic of struct is the key to achieving data independence and performance optimization in HarmonyOS Next.In development, the following principles must be followed:

  1. Clearly distinguish between value types and reference types: Choose the appropriate type according to whether the data needs to be shared;
  2. Priority immutable design: Declare instances with let and explicitly mark mutability through var/mut;
  3. Performance priority scenario: Use inout, splitting or deep copy strategies for large structures to avoid redundant replication.

By deeply understanding the replication semantics and state isolation rules of value types, developers can build more robust and efficient data models in Hongmeng applications, especially in real-time data processing and high concurrency scenarios, giving full play to the lightweight advantages of struct.

Top comments (0)