iOS -[UIImage initWithCGImage:scale:orientation:] 在后台线程上崩溃
iOS -[UIImage initWithCGImage:scale:orientation:] crashes on a background thread
我正在使用 -[UIImage initWithCGImage:scale:orientation:]
在后台队列上创建一个 UIImage
,它因此崩溃了:
崩溃 1
Crashed: com.apple.root.background-qos
0 libsystem_kernel.dylib 0x183778140 __pthread_kill + 8
1 libsystem_pthread.dylib 0x183840ef8 pthread_kill + 112
2 libsystem_c.dylib 0x1836e9dac abort + 140
3 libsystem_malloc.dylib 0x1837acd34 free_list_checksum_botch + 438
4 libsystem_malloc.dylib 0x1837aced8 free_tiny_botch + 84
5 CoreFoundation 0x183c08038 __CFBasicHashRehash + 2448
6 CoreFoundation 0x183c09034 __CFBasicHashAddValue + 100
7 CoreFoundation 0x183ab49c4 CFDictionarySetValue + 248
8 UIKit 0x18926fd30 _UITraitCollectionCacheForBuiltinStorage + 152
9 UIKit 0x189272090 +[UITraitCollection traitCollectionWithDisplayScale:] + 52
10 UIKit 0x1888d5070 -[UIImage initWithCGImage:scale:orientation:] + 236
11 UIKit 0x1888d4f74 +[UIImage imageWithCGImage:scale:orientation:] + 72
12 MyApp 0x10001a168 -[UIImage(Additions) foo_scaledImageData] (Foo.m:1527)
13 MyApp 0x100017304 -[Foo bar] (Foo.m:731)
14 MyApp 0x100017094 __95-[Foo bar]_block_invoke_3 (Foo.m:691)
15 libdispatch.dylib 0x183629630 _dispatch_call_block_and_release + 24
16 libdispatch.dylib 0x1836295f0 _dispatch_client_callout + 16
17 libdispatch.dylib 0x183637a88 _dispatch_root_queue_drain + 2140
18 libdispatch.dylib 0x183637224 _dispatch_worker_thread3 + 112
19 libsystem_pthread.dylib 0x18383d470 _pthread_wqthread + 1092
20 libsystem_pthread.dylib 0x18383d020 start_wqthread + 4
我也遇到了以下两个线程的崩溃:
崩溃 2,线程 1:
Crashed: com.apple.root.background-qos
0 libsystem_kernel.dylib 0x237d3c5c __pthread_kill + 8
1 libsystem_pthread.dylib 0x23879b47 pthread_kill + 62
2 libsystem_c.dylib 0x237680c5 abort + 108
3 libsystem_malloc.dylib 0x238040e9 free_list_checksum_botch + 362
4 libsystem_malloc.dylib 0x23804105 free_list_checksum_botch + 28
5 libsystem_malloc.dylib 0x237fbbff tiny_malloc_from_free_list + 202
6 libsystem_malloc.dylib 0x237fa987 szone_malloc_should_clear + 218
7 libsystem_malloc.dylib 0x237fa879 malloc_zone_malloc + 88
8 CoreFoundation 0x23a4e291 _CFRuntimeCreateInstance + 236
9 CoreGraphics 0x24e792a9 CGTypeCreateInstance + 20
10 CoreGraphics 0x24db7d23 CGColorTransformCreate + 246
11 ImageIO 0x2533c64b IIO_ConvertCGColorToColorComponents + 26
12 ImageIO 0x252f5775 CGImagePixelDataProviderCreate + 276
13 ImageIO 0x25319af1 CGImagePixelDataProviderCreateConforming + 1684
14 ImageIO 0x252f418d CGImageDestinationAddImage + 2852
15 UIKit 0x283fe4f5 _UIImageJPEGRepresentation + 620
16 MyApp 0xf6785 -[UIImage(Additions) foo_scaledImageData] (Foo.m:1538)
17 MyApp 0xf3ecd -[Foo bar] (Foo.m:731)
18 MyApp 0xf3d29 __95-[Foo bar]_block_invoke_3 (Foo.m:691)
19 libdispatch.dylib 0x236d7cbf _dispatch_call_block_and_release + 10
20 libdispatch.dylib 0x236e36a1 _dispatch_root_queue_drain + 1572
21 libdispatch.dylib 0x236e307b _dispatch_worker_thread3 + 94
22 libsystem_pthread.dylib 0x23876e0d _pthread_wqthread + 1024
23 libsystem_pthread.dylib 0x238769fc start_wqthread + 8
崩溃 2,线程 2:
com.apple.root.background-qos
0 libsystem_malloc.dylib 0x238041d8 free_tiny_botch
1 CoreFoundation 0x23b6f560 __CFBasicHashRehash + 2968
2 CoreFoundation 0x23b706d4 __CFBasicHashAddValue + 100
3 CoreFoundation 0x23a4f933 CFDictionarySetValue + 206
4 UIKit 0x28a87ffb _UITraitCollectionCacheForBuiltinStorage + 146
5 UIKit 0x28a8a2a7 +[UITraitCollection traitCollectionWithDisplayScale:] + 50
6 UIKit 0x2812390f -[UIImage initWithCGImage:scale:orientation:] + 214
7 UIKit 0x2812382b +[UIImage imageWithCGImage:scale:orientation:] + 62
8 MyApp 0xf676d -[UIImage(Additions) foo_scaledImageData] (Foo.m:1527)
9 MyApp 0xf3ecd -[Foo bar] (Foo.m:731)
10 MyApp 0xf3d29 __95-[Foo bar]_block_invoke_3 (Foo.m:691)
11 libdispatch.dylib 0x236d7cbf _dispatch_call_block_and_release + 10
12 libdispatch.dylib 0x236e36a1 _dispatch_root_queue_drain + 1572
13 libdispatch.dylib 0x236e307b _dispatch_worker_thread3 + 94
14 libsystem_pthread.dylib 0x23876e0d _pthread_wqthread + 1024
15 libsystem_pthread.dylib 0x238769fc start_wqthread + 8
崩溃 2,线程 2 看起来像崩溃 1,线程 1,这让我认为它们是相关的,但崩溃 2,线程 2 是异常值。我有很多其他崩溃看起来像没有线程 2 堆栈的崩溃 2,这让我认为 UIImageJPEGRepresentation
可能也不是线程安全的。
这看起来类似于 AFNetworking issue
I can't name the source, but it's being taken care of. (or at least a
radar has been filed by someone inside Apple)
Side note: I've looked at the UIKit disassembly and that API calls up
traitCollection and a few other things and really is everything but
thread safe; more so it really should only be called on the main
thread ideally. We could swizzle and add the lock directly into the
implementation, that would at least remove the fact that any
non-AFNetworking consumer would still call this without the lock and
might trigger the race condition; but it's also very much not pretty.
Using CGImageSource directly is more work but might be the better
solution.
正如 AFNetworking
问题提到的和堆栈跟踪显示的那样,这看起来像 -[UIImage initWithCGImage:scale:orientation:]
调用到 UITraitCollection
。我在 UITraitCollection.h
或 UITraitCollection
documentation 中找不到任何表明 UITraitCollection
是线程安全的内容。
因此,即使 UIImage
documentation 说 UIImage
是线程安全的:
Because image objects are immutable, you cannot change their
properties after creation. Most image properties are set automatically
using metadata in the accompanying image file or image data. The
immutable nature of image objects also means that they are safe to use
from any thread.
文档必须表明它们在主线程上创建后可以安全使用,或者如果它们只是连续创建则可以安全使用。 (这就是 AFNetworking
fixed their issue。)对于我的解决方法,我改用 CoreGraphics
:
CGImageRef image = ...
CGSize scaledSize = ...
CGContextRef context = CGBitmapContextCreate(NULL,
scaledSize.width,
scaledSize.height,
CGImageGetBitsPerComponent(image),
CGImageGetBytesPerRow(image),
CGImageGetColorSpace(image),
CGImageGetAlphaInfo(image));
CGImageRef scaledImage;
if (context)
{
CGContextDrawImage(context,
CGRectMake(0,
0,
scaledSize.width,
scaledSize.height),
image);
scaledImage = CGBitmapContextCreateImage(context);
CGContextRelease(context);
}
else
{
scaledImage = NULL;
}
然后,为了避免在后台线程上使用 UIImageJPEGRepresentation
,我使用 ImageIO
导出 JPEG 数据:
NSData *NSDataFromCGImage(CGImageRef scaledImage)
{
NSMutableData *scaledImageMutableData = scaledImage ? [NSMutableData new] : nil;
CGDataConsumerRef scaledImageDataConsumer = scaledImageMutableData ? CGDataConsumerCreateWithCFData((__bridge CFMutableDataRef) scaledImageMutableData) : NULL;
// from the iOS 9.3 header:
// The `options' dictionary is reserved for future use; currently, you
// should pass NULL for this parameter.
CGImageDestinationRef scaledImageDestination = scaledImageDataConsumer ? CGImageDestinationCreateWithDataConsumer(scaledImageDataConsumer,
kUTTypeJPEG,
1,
NULL) : NULL;
BOOL success = (^BOOL ()
{
if (scaledImageDestination)
{
NSDictionary *addImageOptions = @{(__bridge NSString *)kCGImageDestinationLossyCompressionQuality : @(0.9),
};
CGImageDestinationAddImage(scaledImageDestination,
scaledImage,
(__bridge CFDictionaryRef)addImageOptions);
return (BOOL) CGImageDestinationFinalize(scaledImageDestination);
}
else
{
return NO;
}
})();
if (scaledImageDestination)
{
CFRelease(scaledImageDestination);
}
CGDataConsumerRelease(scaledImageDataConsumer);
if (success)
{
return scaledImageMutableData;
}
else
{
return nil;
}
}
我正在使用 -[UIImage initWithCGImage:scale:orientation:]
在后台队列上创建一个 UIImage
,它因此崩溃了:
崩溃 1
Crashed: com.apple.root.background-qos
0 libsystem_kernel.dylib 0x183778140 __pthread_kill + 8
1 libsystem_pthread.dylib 0x183840ef8 pthread_kill + 112
2 libsystem_c.dylib 0x1836e9dac abort + 140
3 libsystem_malloc.dylib 0x1837acd34 free_list_checksum_botch + 438
4 libsystem_malloc.dylib 0x1837aced8 free_tiny_botch + 84
5 CoreFoundation 0x183c08038 __CFBasicHashRehash + 2448
6 CoreFoundation 0x183c09034 __CFBasicHashAddValue + 100
7 CoreFoundation 0x183ab49c4 CFDictionarySetValue + 248
8 UIKit 0x18926fd30 _UITraitCollectionCacheForBuiltinStorage + 152
9 UIKit 0x189272090 +[UITraitCollection traitCollectionWithDisplayScale:] + 52
10 UIKit 0x1888d5070 -[UIImage initWithCGImage:scale:orientation:] + 236
11 UIKit 0x1888d4f74 +[UIImage imageWithCGImage:scale:orientation:] + 72
12 MyApp 0x10001a168 -[UIImage(Additions) foo_scaledImageData] (Foo.m:1527)
13 MyApp 0x100017304 -[Foo bar] (Foo.m:731)
14 MyApp 0x100017094 __95-[Foo bar]_block_invoke_3 (Foo.m:691)
15 libdispatch.dylib 0x183629630 _dispatch_call_block_and_release + 24
16 libdispatch.dylib 0x1836295f0 _dispatch_client_callout + 16
17 libdispatch.dylib 0x183637a88 _dispatch_root_queue_drain + 2140
18 libdispatch.dylib 0x183637224 _dispatch_worker_thread3 + 112
19 libsystem_pthread.dylib 0x18383d470 _pthread_wqthread + 1092
20 libsystem_pthread.dylib 0x18383d020 start_wqthread + 4
我也遇到了以下两个线程的崩溃:
崩溃 2,线程 1:
Crashed: com.apple.root.background-qos
0 libsystem_kernel.dylib 0x237d3c5c __pthread_kill + 8
1 libsystem_pthread.dylib 0x23879b47 pthread_kill + 62
2 libsystem_c.dylib 0x237680c5 abort + 108
3 libsystem_malloc.dylib 0x238040e9 free_list_checksum_botch + 362
4 libsystem_malloc.dylib 0x23804105 free_list_checksum_botch + 28
5 libsystem_malloc.dylib 0x237fbbff tiny_malloc_from_free_list + 202
6 libsystem_malloc.dylib 0x237fa987 szone_malloc_should_clear + 218
7 libsystem_malloc.dylib 0x237fa879 malloc_zone_malloc + 88
8 CoreFoundation 0x23a4e291 _CFRuntimeCreateInstance + 236
9 CoreGraphics 0x24e792a9 CGTypeCreateInstance + 20
10 CoreGraphics 0x24db7d23 CGColorTransformCreate + 246
11 ImageIO 0x2533c64b IIO_ConvertCGColorToColorComponents + 26
12 ImageIO 0x252f5775 CGImagePixelDataProviderCreate + 276
13 ImageIO 0x25319af1 CGImagePixelDataProviderCreateConforming + 1684
14 ImageIO 0x252f418d CGImageDestinationAddImage + 2852
15 UIKit 0x283fe4f5 _UIImageJPEGRepresentation + 620
16 MyApp 0xf6785 -[UIImage(Additions) foo_scaledImageData] (Foo.m:1538)
17 MyApp 0xf3ecd -[Foo bar] (Foo.m:731)
18 MyApp 0xf3d29 __95-[Foo bar]_block_invoke_3 (Foo.m:691)
19 libdispatch.dylib 0x236d7cbf _dispatch_call_block_and_release + 10
20 libdispatch.dylib 0x236e36a1 _dispatch_root_queue_drain + 1572
21 libdispatch.dylib 0x236e307b _dispatch_worker_thread3 + 94
22 libsystem_pthread.dylib 0x23876e0d _pthread_wqthread + 1024
23 libsystem_pthread.dylib 0x238769fc start_wqthread + 8
崩溃 2,线程 2:
com.apple.root.background-qos
0 libsystem_malloc.dylib 0x238041d8 free_tiny_botch
1 CoreFoundation 0x23b6f560 __CFBasicHashRehash + 2968
2 CoreFoundation 0x23b706d4 __CFBasicHashAddValue + 100
3 CoreFoundation 0x23a4f933 CFDictionarySetValue + 206
4 UIKit 0x28a87ffb _UITraitCollectionCacheForBuiltinStorage + 146
5 UIKit 0x28a8a2a7 +[UITraitCollection traitCollectionWithDisplayScale:] + 50
6 UIKit 0x2812390f -[UIImage initWithCGImage:scale:orientation:] + 214
7 UIKit 0x2812382b +[UIImage imageWithCGImage:scale:orientation:] + 62
8 MyApp 0xf676d -[UIImage(Additions) foo_scaledImageData] (Foo.m:1527)
9 MyApp 0xf3ecd -[Foo bar] (Foo.m:731)
10 MyApp 0xf3d29 __95-[Foo bar]_block_invoke_3 (Foo.m:691)
11 libdispatch.dylib 0x236d7cbf _dispatch_call_block_and_release + 10
12 libdispatch.dylib 0x236e36a1 _dispatch_root_queue_drain + 1572
13 libdispatch.dylib 0x236e307b _dispatch_worker_thread3 + 94
14 libsystem_pthread.dylib 0x23876e0d _pthread_wqthread + 1024
15 libsystem_pthread.dylib 0x238769fc start_wqthread + 8
崩溃 2,线程 2 看起来像崩溃 1,线程 1,这让我认为它们是相关的,但崩溃 2,线程 2 是异常值。我有很多其他崩溃看起来像没有线程 2 堆栈的崩溃 2,这让我认为 UIImageJPEGRepresentation
可能也不是线程安全的。
这看起来类似于 AFNetworking issue
I can't name the source, but it's being taken care of. (or at least a radar has been filed by someone inside Apple)
Side note: I've looked at the UIKit disassembly and that API calls up traitCollection and a few other things and really is everything but thread safe; more so it really should only be called on the main thread ideally. We could swizzle and add the lock directly into the implementation, that would at least remove the fact that any non-AFNetworking consumer would still call this without the lock and might trigger the race condition; but it's also very much not pretty.
Using CGImageSource directly is more work but might be the better solution.
正如 AFNetworking
问题提到的和堆栈跟踪显示的那样,这看起来像 -[UIImage initWithCGImage:scale:orientation:]
调用到 UITraitCollection
。我在 UITraitCollection.h
或 UITraitCollection
documentation 中找不到任何表明 UITraitCollection
是线程安全的内容。
因此,即使 UIImage
documentation 说 UIImage
是线程安全的:
Because image objects are immutable, you cannot change their properties after creation. Most image properties are set automatically using metadata in the accompanying image file or image data. The immutable nature of image objects also means that they are safe to use from any thread.
文档必须表明它们在主线程上创建后可以安全使用,或者如果它们只是连续创建则可以安全使用。 (这就是 AFNetworking
fixed their issue。)对于我的解决方法,我改用 CoreGraphics
:
CGImageRef image = ...
CGSize scaledSize = ...
CGContextRef context = CGBitmapContextCreate(NULL,
scaledSize.width,
scaledSize.height,
CGImageGetBitsPerComponent(image),
CGImageGetBytesPerRow(image),
CGImageGetColorSpace(image),
CGImageGetAlphaInfo(image));
CGImageRef scaledImage;
if (context)
{
CGContextDrawImage(context,
CGRectMake(0,
0,
scaledSize.width,
scaledSize.height),
image);
scaledImage = CGBitmapContextCreateImage(context);
CGContextRelease(context);
}
else
{
scaledImage = NULL;
}
然后,为了避免在后台线程上使用 UIImageJPEGRepresentation
,我使用 ImageIO
导出 JPEG 数据:
NSData *NSDataFromCGImage(CGImageRef scaledImage)
{
NSMutableData *scaledImageMutableData = scaledImage ? [NSMutableData new] : nil;
CGDataConsumerRef scaledImageDataConsumer = scaledImageMutableData ? CGDataConsumerCreateWithCFData((__bridge CFMutableDataRef) scaledImageMutableData) : NULL;
// from the iOS 9.3 header:
// The `options' dictionary is reserved for future use; currently, you
// should pass NULL for this parameter.
CGImageDestinationRef scaledImageDestination = scaledImageDataConsumer ? CGImageDestinationCreateWithDataConsumer(scaledImageDataConsumer,
kUTTypeJPEG,
1,
NULL) : NULL;
BOOL success = (^BOOL ()
{
if (scaledImageDestination)
{
NSDictionary *addImageOptions = @{(__bridge NSString *)kCGImageDestinationLossyCompressionQuality : @(0.9),
};
CGImageDestinationAddImage(scaledImageDestination,
scaledImage,
(__bridge CFDictionaryRef)addImageOptions);
return (BOOL) CGImageDestinationFinalize(scaledImageDestination);
}
else
{
return NO;
}
})();
if (scaledImageDestination)
{
CFRelease(scaledImageDestination);
}
CGDataConsumerRelease(scaledImageDataConsumer);
if (success)
{
return scaledImageMutableData;
}
else
{
return nil;
}
}