0x0 背景
原本是放到自己博客的,不怎么用了,把文章同步过来,原文地址[iOS/OC]platform_memmove的Crash
这个问题是今年1月底排查的,gif/apng动图播放时在iOS11以下会小概率闪退。是个比较有趣的Crash,重新记录一下。
典型堆栈:
#13. Crashed: PINAnimatedImage disk write queue
0 libsystem_platform.dylib 0x1915d4e60 _platform_memmove + 96
1 ImageIO 0x1943c0a74 GIFReadPlugin::copyImageBlockSet(InfoRec*, CGImageProvider*, CGRect, CGSize, __CFDictionary const*) + 3192
2 ImageIO 0x1943c0a74 GIFReadPlugin::copyImageBlockSet(InfoRec*, CGImageProvider*, CGRect, CGSize, __CFDictionary const*) + 3192
3 ImageIO 0x1943bf7c4 GIFReadPlugin::CopyImageBlockSetProc(void*, CGImageProvider*, CGRect, CGSize, __CFDictionary const*) + 124
4 ImageIO 0x19422073c IIOImageProviderInfo::copyImageBlockSetWithOptions(CGImageProvider*, CGRect, CGSize, __CFDictionary const*) + 496
5 ImageIO 0x19421e640 IIOImageProviderInfo::CopyImageBlockSetWithOptions(void*, CGImageProvider*, CGRect, CGSize, __CFDictionary const*) + 356
6 CoreGraphics 0x1939eeb24 CGImageProviderCopyImageBlockSet + 220
7 CoreGraphics 0x193c833ac imageProvider_getBytes + 88
8 CoreGraphics 0x193ab30e0 CGDataProviderCopyData + 280
9 Pinterest 0x100a46238 __94+[PINAnimatedImageManager processAnimatedImage:temporaryDirectory:infoCompletion:decodedPath:]_block_invoke.225 (PINAnimatedImageManager.m:397)
10 libdispatch.dylib 0x1913d21fc _dispatch_call_block_and_release + 24
11 libdispatch.dylib 0x1913d21bc _dispatch_client_callout + 16
12 libdispatch.dylib 0x1913e012c _dispatch_queue_serial_drain + 240
13 libdispatch.dylib 0x1913d59a4 _dispatch_queue_invoke + 652
14 libdispatch.dylib 0x1913e08d8 _dispatch_queue_override_invoke + 360
15 libdispatch.dylib 0x1913e234c _dispatch_root_queue_drain + 572
16 libdispatch.dylib 0x1913e20ac _dispatch_worker_thread3 + 124
17 libsystem_pthread.dylib 0x1915db2a0 _pthread_wqthread + 1288
18 libsystem_pthread.dylib 0x1915dad8c start_wqthread + 4
该问题在Google上能搜到的结果还是不少的,但是有效信息很少。比较有用的是openradar上提到的iOS10.3beta上已经修复了该问题。可以略微松一口气,最不济可以甩锅给苹果了。甚至如果你的APP只支持iOS11(可能性不大,但是万一有呢),都可以直接忽略了。
0x1 Crash原因
虽然是系统的BUG,但是还是要搞清楚Crash的原因,尽量在业务代码上避免该问题引发其他的风险。排查过程比较艰辛,只说最终结果吧:
CGContextDrawImage();不再保证对imageRef的原子操作。
当对于1个imageRef,有多个线程并发绘制时,会触发buffer的memcmp的Crash
根据问题原因可以知道,其实该Crash在多线程同时解码同1份imageRef时才会Crash,因此如果没有引入SDWebImage/YYImage等三方图片库,系统默认主线程解码是不会有改Crash的。
另外,静图解码也是有概率Crash,但是网上反馈比较少,主要原因也是概率的问题。和一般的多线程Crash一样,次数多了概率才打,在相同的业务场景下,动图解码的频次远高于静图,因此动图更容易触发该Crash。
0x2 Crash防护
知道Crash原因后,防护相对就比较简单了,只要保证不会对同一个image资源同时解码就可以了。1个简单的方法是在解码时,使用UIImage作为input,对UIImage进行多线程保护。如:
@synchronized(image) {
return [self decodedImageWithCGImageRef:image.CGImage];
}