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

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文件,启动时不一次性加载所有卡牌,而是根据需求动态读取解析,这是内存占用最低的方案:

  1. 创建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": "弱小的骷髅弓箭手。"
    }
    // ... 其余卡牌数据
]
  1. 让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)
    }
}
  1. 实现卡牌管理类,支持预加载或按需加载:
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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 15:25:24