Swift中存储含String的结构体为字节数组,如何避免其被释放?
在Swift中将含String的结构体存储为字节数组的解决方案
问题背景
你当前的代码可以正确处理仅包含值类型(如Int)的结构体,但当结构体包含String类型时,直接拷贝结构体内存字节的方案会失效——因为String的实际内容存储在堆上,结构体本身仅持有指向堆内容的指针、长度和引用计数。当原结构体实例被销毁时,String的引用计数归零,堆上的内容会被释放,导致你存入字节数组的指针变成无效的野指针。
为什么原方案不可行
MemoryLayout<Test>.size获取的是结构体在栈上的内存大小,而String的真实数据并不在这片栈内存里。你拷贝的只是String的元数据(指针、长度、引用计数),而非实际字符串内容。一旦原结构体被销毁,这些元数据指向的堆内存会被回收,字节数组里的内容也就失去了意义。
解决方案
方案1:使用Codable序列化(推荐)
这是最安全可靠的方式,能完整序列化结构体的所有内容,包括String的实际值,而非内存指针。通过Codable协议可以将结构体转换为Data(本质就是字节数组),需要时再反序列化回来:
import Foundation struct Test: Codable { var val: String } // 序列化结构体为字节数组(Data) let test = Test(val: "Hello, Swift") if let data = try? JSONEncoder().encode(test) { // data就是可存储/传输的字节数组 print("序列化后的字节数:\(data.count)") // 反序列化还原结构体 if let decodedTest = try? JSONDecoder().decode(Test.self, from: data) { print("还原后的字符串:\(decodedTest.val)") } }
方案2:临时保留结构体实例(仅适用于短期使用)
如果只是临时需要字节数组,且能保证结构体实例在字节数组使用期间不被销毁,那么原代码可以正常工作——因为结构体存活时,String的引用计数不会归零,堆上的内容不会被释放:
var byteArray = [UInt8](repeating: 0, count: MemoryLayout<Test>.size) var test = Test(val: "临时字符串") // 用var声明,确保实例不会被提前销毁 // 执行存储操作 store(&test, MemoryLayout<Test>.size) // 在test实例被销毁前,byteArray中的内容是有效的 // 注意:test一旦被销毁,byteArray里的String相关指针立即失效,不能再使用
关于Unmanaged的说明
Unmanaged是专门用于类(引用类型)的内存管理工具,无法直接用于结构体。结构体是值类型,其生命周期由作用域决定,当结构体被销毁时,它包含的所有成员(包括String)都会被自动清理。你无法像保留类实例那样,用Unmanaged来强制延长结构体或其成员的生命周期。
内容的提问来源于stack exchange,提问作者Jomy
相关产品推荐
相关产品推荐

