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

如何在ViewModel中结合@Published使用@AppStorage存储自定义类型数组

实现方案

因为你定义的MyModel已经遵守Codable协议,有两种常用的方式实现自定义数组持久化+@Published联动:

方案1:简洁版,通过扩展让@AppStorage直接支持Codable数组

该方案无需引入Combine监听,代码量最少,适合小项目使用。

第一步:扩展Array适配@AppStorage要求的RawRepresentable协议

extension Array: RawRepresentable where Element: Codable {
    public init?(rawValue: String) {
        guard let data = rawValue.data(using: .utf8),
              let result = try? JSONDecoder().decode([Element].self, from: data) else {
            return nil
        }
        self = result
    }

    public var rawValue: String {
        guard let data = try? JSONEncoder().encode(self),
              let result = String(data: data, encoding: .utf8) else {
            return "[]"
        }
        return result
    }
}

第二步:修改ViewModel的属性定义

直接把原来的@Published属性替换为@AppStorage即可,@AppStorage本身会自动触发视图刷新,原有业务逻辑完全不用改:

class ViewModel: ObservableObject {
    @Published private(set) var myModels = [MyModel]()
    // 直接用@AppStorage修饰数组,自动实现持久化+变更通知
    @AppStorage("subscribedMyModels") private(set) var subscribedMyModels = [MyModel]()

    func fetchMyModels() {
        myModels = [
            MyModel(text: "Setting", number: 1),
            MyModel(text: "Setting", number: 2),
            MyModel(text: "Setting", number: 3),
            MyModel(text: "Setting", number: 4),
            MyModel(text: "Setting", number: 5),
            MyModel(text: "Setting", number: 6),
            MyModel(text: "Setting", number: 7),
            MyModel(text: "Setting", number: 8)
        ]
    }

    func toggleSubscription(for myModel: MyModel) {
        if subscribedMyModels.contains(myModel) {
            subscribedMyModels.removeAll(where: { $0 == myModel } )
        } else {
            subscribedMyModels.append(myModel)
        }
    }
}

方案2:无全局扩展版,通过Combine监听实现持久化

如果不想全局扩展Array,可通过监听@Published属性变更实现自动持久化,对现有代码侵入性也很低。

第一步:扩展UserDefaults实现Codable数组读写

extension UserDefaults {
    func save<T: Codable>(_ items: [T], forKey key: String) {
        guard let data = try? JSONEncoder().encode(items) else { return }
        set(data, forKey: key)
    }
    
    func load<T: Codable>(forKey key: String) -> [T] {
        guard let data = data(forKey: key),
              let items = try? JSONDecoder().decode([T].self, from: data) else {
            return []
        }
        return items
    }
}

第二步:修改ViewModel添加监听逻辑

import Combine

class ViewModel: ObservableObject {
    @Published private(set) var myModels = [MyModel]()
    @Published private(set) var subscribedMyModels = [MyModel]()
    
    private let subscribedKey = "subscribedMyModels"
    private var cancellables = Set<AnyCancellable>()
    
    init() {
        // 初始化读取缓存
        subscribedMyModels = UserDefaults.standard.load(forKey: subscribedKey)
        // 监听变更自动存到UserDefaults
        $subscribedMyModels
            .sink { [weak self] items in
                self?.saveSubscribedItems(items)
            }
            .store(in: &cancellables)
    }
    
    private func saveSubscribedItems(_ items: [MyModel]) {
        UserDefaults.standard.save(items, forKey: subscribedKey)
    }

    // 原有业务方法完全不用修改
    func fetchMyModels() {
        myModels = [
            MyModel(text: "Setting", number: 1),
            MyModel(text: "Setting", number: 2),
            MyModel(text: "Setting", number: 3),
            MyModel(text: "Setting", number: 4),
            MyModel(text: "Setting", number: 5),
            MyModel(text: "Setting", number: 6),
            MyModel(text: "Setting", number: 7),
            MyModel(text: "Setting", number: 8)
        ]
    }

    func toggleSubscription(for myModel: MyModel) {
        if subscribedMyModels.contains(myModel) {
            subscribedMyModels.removeAll(where: { $0 == myModel } )
        } else {
            subscribedMyModels.append(myModel)
        }
    }
}

两种方案都不需要修改View层的代码,运行后勾选的选项会自动持久化,重启App会自动恢复之前的选择状态。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.03 22:06:04