You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为适配@AppStorage实现RawRepresentable导致JSONEncoder无限循环问题

问题原因与解决办法

这不是系统Bug,是你实现RawRepresentable时触发了无限递归,导致栈溢出崩溃。

崩溃根源

如果你的rawValue getter里直接用JSONEncoder().encode(self),JSON编码器在编码Printer实例时,会递归处理其所有属性逻辑;而因为Printer遵循RawRepresentable,编码过程会再次触发rawValue的获取,形成rawValue→编码self→rawValue的无限循环,最终耗尽栈空间崩溃。

解决办法

核心是把RawRepresentable的rawValue转换逻辑与结构体自身的编码逻辑解耦,避免循环调用。通过一个中间的可编码容器(元组或内部结构体)来中转属性,再进行编码:

示例实现(以RawValue为Data为例)

import Foundation
import SwiftUI

struct Printer: Codable, RawRepresentable {
    let name: String
    let id: UUID
    // 实际项目中的其他复杂属性
    
    typealias RawValue = Data
    
    // 从rawValue解码实例
    init?(rawValue: RawValue) {
        do {
            // 直接解码当前类型,解码过程不会触发rawValue调用,无递归风险
            self = try JSONDecoder().decode(Self.self, from: rawValue)
        } catch {
            return nil
        }
    }
    
    // 生成rawValue的关键:用中间容器中转属性
    var rawValue: RawValue {
        // 用元组包装所有属性,避免直接编码self
        let tempContainer = (name: name, id: id)
        do {
            return try JSONEncoder().encode(tempContainer)
        } catch {
            // 根据实际需求处理编码错误,这里返回空Data
            return Data()
        }
    }
}

// 测试与@AppStorage使用
let testPrinter = Printer(name: "Office Printer", id: UUID())
print(testPrinter.rawValue) // 正常输出Data,无递归

@AppStorage("savedPrinter") var savedPrinter: Printer? = nil

复杂结构体优化方案

如果你的Printer属性很多,推荐用内部结构体做中转,可读性更强:

struct Printer: Codable, RawRepresentable {
    let name: String
    let id: UUID
    let location: String
    let maxPaperSize: String
    
    typealias RawValue = String
    
    // 内部映射结构体,与Printer属性一一对应
    private struct CodingWrapper: Codable {
        let name: String
        let id: UUID
        let location: String
        let maxPaperSize: String
    }
    
    init?(rawValue: RawValue) {
        guard let data = rawValue.data(using: .utf8) else { return nil }
        do {
            let wrapper = try JSONDecoder().decode(CodingWrapper.self, from: data)
            self.name = wrapper.name
            self.id = wrapper.id
            self.location = wrapper.location
            self.maxPaperSize = wrapper.maxPaperSize
        } catch {
            return nil
        }
    }
    
    var rawValue: RawValue {
        let wrapper = CodingWrapper(name: name, id: id, location: location, maxPaperSize: maxPaperSize)
        do {
            let data = try JSONEncoder().encode(wrapper)
            return String(data: data, encoding: .utf8) ?? ""
        } catch {
            return ""
        }
    }
}

关键注意点

  • 绝对不要在rawValue的getter中直接编码self,必须通过中间容器中转属性
  • 解码逻辑可以直接解析当前类型,因为解码过程不会触发rawValue的获取,不会形成递归

内容的提问来源于stack exchange,提问作者vacawama

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 23:42:55