0x0 背景
原本是放到自己博客的,不怎么用了,把文章同步過(guò)來(lái),原文地址[iOS/OC]platform_memmove的Crash
這個(gè)問(wèn)題是今年1月底排查的,gif/apng動(dòng)圖播放時(shí)在iOS11以下會(huì)小概率閃退。是個(gè)比較有趣的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
該問(wèn)題在Google上能搜到的結(jié)果還是不少的,但是有效信息很少。比較有用的是openradar上提到的iOS10.3beta上已經(jīng)修復(fù)了該問(wèn)題??梢月晕⑺梢豢跉猓畈粷?jì)可以甩鍋給蘋(píng)果了。甚至如果你的APP只支持iOS11(可能性不大,但是萬(wàn)一有呢),都可以直接忽略了。
0x1 Crash原因
雖然是系統(tǒng)的BUG,但是還是要搞清楚Crash的原因,盡量在業(yè)務(wù)代碼上避免該問(wèn)題引發(fā)其他的風(fēng)險(xiǎn)。排查過(guò)程比較艱辛,只說(shuō)最終結(jié)果吧:
CGContextDrawImage();不再保證對(duì)imageRef的原子操作。
當(dāng)對(duì)于1個(gè)imageRef,有多個(gè)線(xiàn)程并發(fā)繪制時(shí),會(huì)觸發(fā)buffer的memcmp的Crash
根據(jù)問(wèn)題原因可以知道,其實(shí)該Crash在多線(xiàn)程同時(shí)解碼同1份imageRef時(shí)才會(huì)Crash,因此如果沒(méi)有引入SDWebImage/YYImage等三方圖片庫(kù),系統(tǒng)默認(rèn)主線(xiàn)程解碼是不會(huì)有改Crash的。
另外,靜圖解碼也是有概率Crash,但是網(wǎng)上反饋比較少,主要原因也是概率的問(wèn)題。和一般的多線(xiàn)程Crash一樣,次數(shù)多了概率才打,在相同的業(yè)務(wù)場(chǎng)景下,動(dòng)圖解碼的頻次遠(yuǎn)高于靜圖,因此動(dòng)圖更容易觸發(fā)該Crash。
0x2 Crash防護(hù)
知道Crash原因后,防護(hù)相對(duì)就比較簡(jiǎn)單了,只要保證不會(huì)對(duì)同一個(gè)image資源同時(shí)解碼就可以了。1個(gè)簡(jiǎn)單的方法是在解碼時(shí),使用UIImage作為input,對(duì)UIImage進(jìn)行多線(xiàn)程保護(hù)。如:
@synchronized(image) {
return [self decodedImageWithCGImageRef:image.CGImage];
}