Xcode更新后NSDictionary因对象数与键数不匹配导致应用崩溃的排查求助
问题分析与解决方案
首先咱们来定位崩溃的核心原因:
你遇到的报错-[NSDictionary initWithObjects:forKeys:]: count of objects (0) differs from count of keys (1),直接指向这行代码:
NSDictionary *outerDict = [NSDictionary dictionaryWithObjects: [NSArray arrayWithObjects: notes, nil] forKeys:[NSArray arrayWithObjects:@"Questions", nil]];
当notes是nil的时候,[NSArray arrayWithObjects: notes, nil]会创建一个空数组(因为这个方法遇到第一个nil就会终止元素添加),但对应的keys数组有1个元素(@"Questions"),字典初始化要求对象和键的数量必须完全一致,所以直接触发崩溃。
那notes为什么会是nil?主要有两个场景:
- 第一次运行时,
manuallyData33.plist文件还不存在,[[NSMutableDictionary alloc]initWithContentsOfFile:plistPath]返回nil,后续[plistdictionary objectForKey:@"Questions"]自然也是nil。 - 如果plist文件存在,但里面没有
@"Questions"这个键,同样会导致notes为nil。
另外你的代码还有几个潜在隐患:每次循环都重复读写plist文件(效率极低)、没有处理文本分割后可能出现的数组越界情况(比如文本末尾的空行导致listArray出现空元素,进而items数组长度不足5)。
修复方案
我们分步骤解决这些问题,下面是修改后的完整代码:
-(void)editTextFile { NSError *error = nil; NSString *filepath = [[NSBundle mainBundle] pathForResource:@"JOSHUASTUDY" ofType:@"txt"]; NSString *fileContents = [NSString stringWithContentsOfFile:filepath encoding:NSUTF8StringEncoding error:&error]; if (error) { NSLog(@"Error reading file: %@", error.localizedDescription); return; // 文件读取失败直接返回,避免后续无效操作 } NSArray *listArray = [fileContents componentsSeparatedByString:@"\n\n"]; // 先处理plist文件的初始化逻辑,放在循环外只执行一次 NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *documentsPath = [paths firstObject]; NSString *plistPath = [documentsPath stringByAppendingPathComponent:@"manuallyData33.plist"]; NSMutableDictionary *plistDictionary = [[NSMutableDictionary alloc] initWithContentsOfFile:plistPath]; if (!plistDictionary) { // 如果plist不存在,初始化一个空字典 plistDictionary = [NSMutableDictionary dictionary]; } NSMutableArray *notes = [plistDictionary objectForKey:@"Questions"]; if (!notes) { // 如果Questions数组不存在,初始化一个空的可变数组 notes = [NSMutableArray array]; [plistDictionary setObject:notes forKey:@"Questions"]; } // 遍历处理每个问题 for (NSString *listItem in listArray) { // 跳过空的元素(比如文本末尾的空行分割出来的内容) NSString *trimmedItem = [listItem stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; if (trimmedItem.length == 0) { continue; } NSArray *items = [listItem componentsSeparatedByString:@"\n"]; // 检查items数组长度是否足够,避免数组越界崩溃 if (items.count < 5) { NSLog(@"Invalid question format, skipping: %@", trimmedItem); continue; } NSString *theQuestion = items[0]; NSString *answerA = items[1]; NSString *answerB = items[2]; NSString *answerC = items[3]; NSString *answerD = items[4]; // 用现代字面量语法组装内部字典,更简洁且自动校验元素匹配 NSDictionary *innerDict = @{ @"Answer": @"0", @"duration_in_seconds": @"30", @"negative_points": @"5", @"options": @[answerA, answerB, answerC, answerD], @"points": @"20", @"question": theQuestion, @"question_type": @"1" }; [notes addObject:innerDict]; } // 最后统一写入plist文件,避免循环内反复读写 BOOL writeSuccess = [plistDictionary writeToFile:plistPath atomically:YES]; if (!writeSuccess) { NSLog(@"Failed to write plist file to path: %@", plistPath); } }
关键修改点说明
- plist初始化逻辑:当文件不存在或字典为空时,主动创建空字典和
Questions数组,确保notes永远不会是nil,从根源解决崩溃问题。 - 循环外读写文件:只在开始时读取一次plist,循环结束后统一写入,大幅提升执行效率。
- 空元素过滤:跳过文本分割后产生的空内容,避免无效处理。
- 数组越界检查:每次分割后检查
items的长度,防止因为格式错误导致的越界崩溃。 - 现代语法替换:用字面量语法
@{}和@[]替代旧的dictionaryWithObjects:forKeys:,代码更简洁且编译器会自动检查元素数量匹配,减少人为失误。
这样修改后,不仅能解决你当前的崩溃问题,还能修复潜在的其他bug,让代码更稳定可靠。
内容的提问来源于stack exchange,提问作者user717452
相关产品推荐
相关产品推荐

