从 UserDefaults 解码 JSON 数据并将解码的数据分配给数组时应用崩溃

App crashing when decoding JSON data from UserDefaults and assigning decoded data to an array

提问人:mickeyt 提问时间:11/10/2023 最后编辑:HangarRashmickeyt 更新时间:11/11/2023 访问量:80

问:

我是学习编码的新手。我给自己设定了一个挑战,即创建一个应用程序,并一直在关注书籍,并使用 ChatGPT 来帮助为我目前不知道该怎么做的事情创建代码。设法让我的应用程序正常工作,当它在填充数组时打开时,我会创建一些数据,但是我要求它帮助我下一步在设备上读取和写入数据。它帮助我编写的代码说应该可以工作,但是当应用程序启动时,我总是崩溃。下面是两个对象和函数,用于处理保存数据和在启动时加载数据:

struct Player : Codable {
    
    let name: String
    var handicap: Int
    let userID: String
    
    private init(name: String, handicap: Int, userID: String) {
        
        self.name = name
        self.handicap = handicap
        self.userID = userID
        
    }
    
    static func createNewPlayer(name: String, handicap: Int) -> Player {
        
        let uuid = UUID().uuidString
        let newPlayer = Player(name: name, handicap: handicap, userID: uuid)
        return newPlayer
        
    }
    
    static func createNewPlayer(name: String, handicap: Int, userID: String) -> Player {
        
        let newPlayer = Player(name: name, handicap: handicap, userID: userID)
        return newPlayer
    }

}

class CreatedPlayers {
    
    static var shared = CreatedPlayers()
    
    var players: [Player] = [] {
        
        didSet {
            
            CreatedPlayers.savePlayers()
            
        }
    }
    
    private init(players: [Player] = []) {
        
        self.players = players
        CreatedPlayers.loadPlayers()
        
    }
    
    static func savePlayers() {
        
        do {
            
            let encodedData = try JSONEncoder().encode(shared.players)
            
            UserDefaults.standard.set(encodedData, forKey: "createdPlayers")
            
        } catch {
            
            print("Error encoding players: \(error.localizedDescription)")
            
        }
        
    }
    
    static func loadPlayers() {
        
        //if let encodedData = UserDefaults.standard.data(forKey: "createdPlayers") {
            
            //if let jsonString = String(data: encodedData, encoding: .utf8) {
                //print("JSON String: \(jsonString)")
                
                //do {
                    
                    //shared.players = try JSONDecoder().decode([Player].self, from: encodedData)
                    
                //} catch let decodingError {
                    
                    //print("Error decoding players: \(decodingError)")
                    
                //}
                
            //} else {
                
                //print("No data found")
                
            //}
            
        //}
        
        if let encodedData = UserDefaults.standard.data(forKey: "createdPlayers") {
                
            do {
                    
                let json = try JSONSerialization.jsonObject(with: encodedData, options: [])
                    
                if let playerDataArray = json as? [[String: Any]] {
                        
                    // Process each player dictionary
                    //var parsedPlayers: [Player] = []
                    for playerData in playerDataArray {
                        
                        if
                            let userID = playerData["userID"] as? String,
                            let name = playerData["name"] as? String,
                            let handicap = playerData["handicap"] as? Int {
                            
                            let player = Player.createNewPlayer(name: name, handicap: handicap, userID: userID)
                            self.AddPlayer(player: player)
                            
                        }
                        
                    }
                    
                    } else {
                        
                        print("Error: Unable to parse player data from JSON")
                    }
                } catch {
                    print("Error decoding players: \(error.localizedDescription)")
                }
            } else {
                print("No data found for key 'createdPlayers'")
            }
        
    }
    
    static func AddPlayer(player: Player) {
        
        shared.players.append(player) //*THE CRASH HAPPENS HERE*
        
    }
    
    static func PlayerCount() -> Int {
        
        return shared.players.count
        
    }
    
    static func RetrievePlayers(keyValue: String) -> Player? {
        
        if let retrievedPlayer = shared.players.first(where: { $0.userID == keyValue}) {
            
            return retrievedPlayer
            
        } else {
            
            return nil
            
        }
       
    }
    
    static func RetrievePlayersWithIndex(index: Int) -> Player? {
        
        if index >= 0 && index < shared.players.count {
            
            return shared.players[index]
            
        } else {
            
            return nil
            
        }
        
    }
    
    static func DeletePlayer(userID: String) {
            
        shared.players.removeAll(where: { $0.userID == userID})
        
    }
    
}

loadPlayers() 中所有注释掉的部分都是因为我首先使用了内置的解码方法,但是当它崩溃时,我试图手动解码数据并手动创建一个新播放器,所以看看这是否有所作为(根据我知道所有这些函数在从应用程序本身触发时都可以正常工作), 但它仍然崩溃了,我什至无法捕获错误消息。

当我调用自己的函数时,会发生崩溃,该函数将新玩家添加到玩家数组中,以便在应用程序启动后,有一个数组,其中包含写入设备的相同玩家数据。

调试器中的错误只是说:

线程 1:EXC_BREAKPOINT(代码=1,子代码=0x1013ee3f4)

有人可以帮我理解为什么会这样吗?

数组 JSON Swift 可编码

评论

0赞 derpirscher 11/10/2023
也许你应该从标记你正在使用的语言开始......
0赞 mickeyt 11/10/2023
抱歉,我为 swift 添加了一个标签。
0赞 HangarRash 11/10/2023
用来编码,然后用来解码是没有意义的。用于解码。此外,使用调试器并单步执行代码,看看它实际崩溃的位置以及实际遇到的错误。JSONEncoderJSONSerializationJSONDecoder
0赞 lorem ipsum 11/10/2023
首先不要问 ChatGPT......它可能是一个很好的资源,但前提是您知道如何检测它何时完全偏离轨道,您不能在开始时这样做。搜索 SO 了解如何将数组保存到 UserDefaults,有很多示例,但数组首先不属于那里。UserDefaults 应该只用于设置等小东西。
0赞 lorem ipsum 11/10/2023
stackoverflow.com/questions/69148980/......

答:

0赞 mickeyt 11/11/2023 #1

我自己对此进行了故障排除,并认为这可能与 CreatedPlayers 在开始被要求提供数据之前从未初始化有关(例如,当第一个应用程序屏幕上的表格视图开始加载信息时的玩家计数)。当我考虑应用程序中事件的顺序时,init 函数从未被调用,loadPlayers() 函数就位于此处。

我将对 loadPlayers() 的调用移动到处理 tableview 填充的视图控制器中的 viewDidLoad 函数,删除了 CreatedPlayers init 函数中的调用,它有效!根据 HangarRash 之前的评论,还将解码功能更改为 JSONDecoder。

所以这与解码不起作用无关;这与我用来存储保存的玩家信息的对象有关,该对象从未被初始化,因此在我开始向它询问数据之前,里面的数组也没有初始化。

这是更正后的代码 - 如果我认为 viewDidLoad 函数与它有任何关系,我最初会包含它。

struct Player : Codable {
    
    let name: String
    var handicap: Int
    let userID: String
    
    private init(name: String, handicap: Int, userID: String) {
        
        self.name = name
        self.handicap = handicap
        self.userID = userID
        
    }
    
    static func createNewPlayer(name: String, handicap: Int) -> Player {
        
        let uuid = UUID().uuidString
        let newPlayer = Player(name: name, handicap: handicap, userID: uuid)
        return newPlayer
        
    }
    
    static func createNewPlayer(name: String, handicap: Int, userID: String) -> Player {
        
        let newPlayer = Player(name: name, handicap: handicap, userID: userID)
        return newPlayer
    }
    
}

class CreatedPlayers {
    
    static var shared = CreatedPlayers()
    
    var players: [Player] = [] {
        
        didSet {
            
            CreatedPlayers.savePlayers()
            
        }
    }
    
    private init(players: [Player] = []) {
        
        self.players = players
        //CreatedPlayers.loadPlayers()
        
    }
    
    static func savePlayers() {
        
        do {
            
            let encodedData = try JSONEncoder().encode(shared.players)
            
            UserDefaults.standard.set(encodedData, forKey: "createdPlayers")
            
        } catch {
            
            print("Error encoding players: \(error.localizedDescription)")
            
        }
        
    }
    
    static func loadPlayers() {
        
        if let encodedData = UserDefaults.standard.data(forKey: "createdPlayers") {
            
            if let jsonString = String(data: encodedData, encoding: .utf8) {
                print("JSON String: \(jsonString)")
                
                do {
                    
                    shared.players = try JSONDecoder().decode([Player].self, from: encodedData)
                    
                } catch let decodingError {
                    
                    print("Error decoding players: \(decodingError)")
                    
                }
                
            } else {
                
                print("No data found")
                
            }
            
        }
        
    }

class ViewController: UIViewController, AddPlayerDelegate {
    
    @IBOutlet weak var PlayerTableView: UITableView!
    @IBOutlet weak var AddPlayerPlusBarButton: UIBarButtonItem!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        
        self.PlayerTableView.dataSource = self
        self.PlayerTableView.isEditing = false
        
        CreatedPlayers.loadPlayers() //Added this to ensure array is initialised and players loaded before subsequent requests for player count and player data
        
    }

谢谢 HangerRash, lorem ipsum 的回答。现在将查看核心数据,而不是 UserDefaults!