iOS 复制 NSArray 时崩溃 EXC_BAD_ACCESS KERN_PROTECTION_FAILURE

iOS Crash on copying an NSArray EXC_BAD_ACCESS KERN_PROTECTION_FAILURE

这是来自 Crashlytics 的两个堆栈跟踪,都包含我的同一行代码,但导致了两次不同的崩溃。

# OS Version: 10.3.2 (14F90)
# Device: iPad 5
# RAM Free: 3.8%
# Disk Free: 90.7%

#0. Crashed: com.apple.main-thread
0  libsystem_platform.dylib       0x18d365090 _platform_memset + 126
1  libsystem_malloc.dylib         0x18d2ebd00 _nano_malloc_check_clear + 584
2  libsystem_malloc.dylib         0x18d2eacb0 nano_calloc + 80
3  libsystem_malloc.dylib         0x18d2dc4e8 malloc_zone_calloc + 168
4  libsystem_malloc.dylib         0x18d2dc41c calloc + 40
5  libobjc.A.dylib                0x18cd18160 class_createInstance + 76
6  CoreFoundation                 0x18e2b2928 __CFAllocateObject + 28
7  CoreFoundation                 0x18e29c064 +[__NSSingleObjectArrayI __new::] + 28
8  CoreFoundation                 0x18e18cd18 -[NSArray initWithArray:range:copyItems:] + 400
9  MyApp                          0x10010003c -[ConstituentDownload currentProgress] (ConstituentDownload.m:117)
10 MyApp                          0x1001000b4 -[ConstituentDownload currentProgress] (ConstituentDownload.m:118)
11 MyApp                          0x1001000b4 -[ConstituentDownload currentProgress] (ConstituentDownload.m:118)
12 MyApp                          0x1001000b4 -[ConstituentDownload currentProgress] (ConstituentDownload.m:118)
13 MyApp                          0x1001000b4 -[ConstituentDownload currentProgress] (ConstituentDownload.m:118)
14 MyApp                          0x1001000b4 -[ConstituentDownload currentProgress] (ConstituentDownload.m:118)
15 MyApp                          0x1001000b4 -[ConstituentDownload currentProgress] (ConstituentDownload.m:118)
16 MyApp                          0x1001000b4 -[ConstituentDownload currentProgress] (ConstituentDownload.m:118)
17 MyApp                          0x1001000b4 -[ConstituentDownload currentProgress] (ConstituentDownload.m:118)
18 MyApp                          0x1001000b4 -[ConstituentDownload currentProgress] (ConstituentDownload.m:118)
19 MyApp                          0x1001000b4 -[ConstituentDownload currentProgress] (ConstituentDownload.m:118)
20 MyApp                          0x100100234 -[ConstituentDownload sendProgressNotification] (ConstituentDownload.m:141)
... button press stuff...

和:

# OS Version: 10.3.1 (14E304)
# Device: iPad 4
# RAM Free: 4.7%
# Disk Free: 12.2%

#0. Crashed: com.apple.main-thread
0  libobjc.A.dylib                0x1bc38692 objc_retain + 1
1  CoreFoundation                 0x1c8acf39 +[__NSArrayI __new:::] + 74
2  CoreFoundation                 0x1c8ae9f1 -[__NSArrayM copyWithZone:] + 174
3  MyApp                          0x189407 -[ConstituentDownload currentProgress] (ConstituentDownload.m:117)
4  MyApp                          0x18999f -[ConstituentDownload userInfoForProgressNotification] (ConstituentDownload.m:180)
5  MyApp                          0x189611 -[ConstituentDownload sendProgressNotification] (ConstituentDownload.m:144)
...button press stuff...

这里是导致崩溃的方法:

- (float)currentProgress {

    float sections = [self.childDownloads count] + 1;
    float referencedProgress = 0.;
    // This line causes the crash - where we copy the property array
    for(ConstituentDownload *d in [self.childDownloads copy]) {
        referencedProgress += d.currentProgress;
    }
    float progress = (super.currentProgress + referencedProgress) / sections;
    return progress;
}

self.childDownloads 是一个 NSMutableArray,它包含与此方法相同类型的对象:ConstituentDownload。它可以从其他线程访问,并且可以向其中添加元素,这就是为什么我在遍历它之前首先复制它。该数组往往包含 0-20 个对象。

即使我是在此处复制它,此崩溃是否可能是由于在不同线程中改变数组造成的?

会不会是某种内存损坏引起的?如果是这样,你能指出正确的方向来解决这个问题吗?

会不会是 运行 设备内存不足导致的?如果是这样,为什么不将其报告为内存不足错误而不是生成崩溃报告?

是的,从某种意义上说,这个方法是递归的。父 ConstituentDownload 对其每个子下载调用 currentProgress,子下载又对每个子下载调用它。崩溃堆栈有时只有 1 个递归调用,有时大约有 10 个左右,如这两个崩溃堆栈所示。

此崩溃仅在设备 运行 iOS 9 和 iOS 10 上出现,但这可能是因为我的几乎所有用户都在这两个设备上 OS版本。

KERN_PROTECTION_FAILURE 到底是什么意思?

我找不到准确说明它的具体参考资料,但我认为您应该假设以这种方式复制可变数组是不安全的。

一般来说,在一个线程中迭代 NSMutableArray 是不安全的,而它正在从另一个线程进行变异。无论 copy 是如何在幕后实现的,它都必须以某种方式遍历数组才能完成它的工作。

根据您的描述,听起来您可能是来自多个不同线程的 adding/removing 项。您是否使用某种锁定或序列化来确保线程安全?如果不是,这也可能导致此时发生崩溃。

如果是,则应使用相同的锁定来保护此 copy 操作。