iOS物理设备初始化超48张卡牌触发EXC_BAD_ACCESS崩溃求助
问题分析与解决方案
崩溃原因
你当前的实现每次调用HordeGameData.Cards.init().MonsterName都会创建一个全新的Cards实例,而这个实例会一次性初始化所有61张MonsterCard。物理设备的栈内存远小于模拟器,当实例内的常量数量超过阈值(48张)时,就会触发栈溢出,导致EXC_BAD_ACCESS(code=2)错误。
优化方案
方案1:单例+懒加载,避免重复初始化
把Cards改成单例模式,同时将所有卡牌属性设为懒加载,这样只会初始化一次Cards实例,且只有被访问的卡牌才会被初始化,大幅降低启动时的内存占用:
extension HordeGameData { struct Cards { // 全局唯一的单例实例 static let shared = Cards() // 懒加载属性,仅第一次访问时初始化 lazy var SkeletonWarrior: MonsterCard = MonsterCard( id: 1, name: "骷髅战士", image: "🥺", strength: 6, life: 1, type: .Undead, location: .Plains, images: .init( Normal: "SkeletonWarriorNormal", Attack: "SkeletonWarriorAttack", Defend: "SkeletonWarriorDefend", Hit: "SkeletonWarriorHit", Dead: "SkeletonWarriorDead" ), scaleFactor: 1.1, description: "弱小的骷髅战士。" ) lazy var SkeletonArcher: MonsterCard = MonsterCard( id: 2, name: "骷髅弓箭手", image: "🥳", strength: 5, fleeChance: 15, giant: false, type: .Undead, location: .Plains, images: .init( Normal: "SkeletonArcherNormal", Attack: "SkeletonArcherAttack", Defend: "SkeletonArcherDefend", Hit: "SkeletonArcherHit", Dead: "SkeletonArcherDead" ), scaleFactor: 1.1, description: "弱小的骷髅弓箭手。" ) // ... 其余卡牌定义 } } // 调用方式改为: HordeGameData.Cards.shared.SkeletonWarrior
方案2:配置文件存储(JSON/Plist),按需加载
将卡牌数据从代码中剥离,存入JSON/Plist文件,启动时不一次性加载所有卡牌,而是根据需求动态读取解析,这是内存占用最低的方案:
- 创建
monster_cards.json资源文件:
[ { "id": 1, "name": "骷髅战士", "image": "🥺", "strength": 6, "life": 1, "type": "Undead", "location": "Plains", "images": { "Normal": "SkeletonWarriorNormal", "Attack": "SkeletonWarriorAttack", "Defend": "SkeletonWarriorDefend", "Hit": "SkeletonWarriorHit", "Dead": "SkeletonWarriorDead" }, "scaleFactor": 1.1, "description": "弱小的骷髅战士。" }, { "id": 2, "name": "骷髅弓箭手", "image": "🥳", "strength": 5, "fleeChance": 15, "giant": false, "type": "Undead", "location": "Plains", "images": { "Normal": "SkeletonArcherNormal", "Attack": "SkeletonArcherAttack", "Defend": "SkeletonArcherDefend", "Hit": "SkeletonArcherHit", "Dead": "SkeletonArcherDead" }, "scaleFactor": 1.1, "description": "弱小的骷髅弓箭手。" } // ... 其余卡牌数据 ]
- 让
MonsterCard及关联类型支持解码:
enum MonsterType: String, Decodable { case Undead // ... 其他类型 } enum Location: String, Decodable { case Plains // ... 其他位置 } struct CardImages: Decodable { let Normal: String let Attack: String let Defend: String let Hit: String let Dead: String } struct MonsterCard: Decodable { let id: Int let name: String let image: String let strength: Int let life: Int? let fleeChance: Int? let giant: Bool? let type: MonsterType let location: Location let images: CardImages let scaleFactor: Double let description: String enum CodingKeys: String, CodingKey { case id, name, image, strength, life, fleeChance, giant, type, location, images, scaleFactor, description } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = try container.decode(Int.self, forKey: .id) name = try container.decode(String.self, forKey: .name) image = try container.decode(String.self, forKey: .image) strength = try container.decode(Int.self, forKey: .strength) life = try container.decodeIfPresent(Int.self, forKey: .life) fleeChance = try container.decodeIfPresent(Int.self, forKey: .fleeChance) giant = try container.decodeIfPresent(Bool.self, forKey: .giant) type = try container.decode(MonsterType.self, forKey: .type) location = try container.decode(Location.self, forKey: .location) images = try container.decode(CardImages.self, forKey: .images) scaleFactor = try container.decode(Double.self, forKey: .scaleFactor) description = try container.decode(String.self, forKey: .description) } }
- 实现卡牌管理类,支持预加载或按需加载:
extension HordeGameData { class CardManager { private var monsterCards: [Int: MonsterCard]? static let shared = CardManager() // 预加载所有卡牌(可选) func preloadAllMonsterCards() throws { guard let url = Bundle.main.url(forResource: "monster_cards", withExtension: "json") else { fatalError("找不到monster_cards.json资源文件") } let data = try Data(contentsOf: url) let cards = try JSONDecoder().decode([MonsterCard].self, from: data) monsterCards = Dictionary(uniqueKeysWithValues: cards.map { ($0.id, $0) }) } // 根据ID获取单张卡牌 func getMonsterCard(id: Int) throws -> MonsterCard? { if let cards = monsterCards { return cards[id] } else { guard let url = Bundle.main.url(forResource: "monster_cards", withExtension: "json") else { return nil } let data = try Data(contentsOf: url) let cards = try JSONDecoder().decode([MonsterCard].self, from: data) return cards.first { $0.id == id } } } } } // 使用示例: // 启动时预加载(可选) do { try HordeGameData.CardManager.shared.preloadAllMonsterCards() } catch { print("预加载卡牌失败:\(error)") } // 获取指定ID的卡牌 if let skeletonWarrior = try? HordeGameData.CardManager.shared.getMonsterCard(id: 1) { // 使用卡牌 }
方案3:字典映射替代结构体常量
如果不想改动太多代码,可将卡牌存入静态字典,避免结构体实例的栈内存压力:
extension HordeGameData { struct Cards { static let monsterCards: [Int: MonsterCard] = [ 1: MonsterCard( id: 1, name: "骷髅战士", image: "🥺", strength: 6, life: 1, type: .Undead, location: .Plains, images: .init( Normal: "SkeletonWarriorNormal", Attack: "SkeletonWarriorAttack", Defend: "SkeletonWarriorDefend", Hit: "SkeletonWarriorHit", Dead: "SkeletonWarriorDead" ), scaleFactor: 1.1, description: "弱小的骷髅战士。" ), 2: MonsterCard( id: 2, name: "骷髅弓箭手", image: "🥳", strength: 5, fleeChance: 15, giant: false, type: .Undead, location: .Plains, images: .init( Normal: "SkeletonArcherNormal", Attack: "SkeletonArcherAttack", Defend: "SkeletonArcherDefend", Hit: "SkeletonArcherHit", Dead: "SkeletonArcherDead" ), scaleFactor: 1.1, description: "弱小的骷髅弓箭手。" ) // ... 其余卡牌 ] } } // 调用方式: HordeGameData.Cards.monsterCards[1]
优先级建议
优先采用方案2(配置文件+按需加载),既彻底解决内存问题,又方便后续卡牌数据的修改和扩展,无需重新编译代码;如果不想做大改动,**方案1(单例+懒加载)**是最小成本的修复方案,能快速解决崩溃问题。
内容的提问来源于stack exchange,提问作者Nathanael Tse
相关产品推荐
相关产品推荐

