在SDWebImage版本替换时遇见了让人捉狂的crash,旧版本为2011年左右的版本,新版本为4.0版本。替换之后,批量下载图片出现了非常大概率的crash,并且该crash在iOS8,iOS9中没有复现,iOS10.0~iOS10.1.1中大概率复现,iOS10.2中概率下降,但是也是非常容易复现出该问题。
iOS10.1.1中,crash时报错:“freeing unallocated pointer”。iOS10.2中crash报错为:“reallocated pointer was not allocated”。开始的时候不明确是否是SDWebImage库的问题,堆栈中有nanon_free字样。后查询资料,看到微信团队关于该crash的分析文章与解决方案,终于算找到方向了(链接:https://mp.weixin.qq.com/s/hnwj24xqrtOhcjEt_TaQ9w)。下面就沿着该方案解决问题的过程,总结一下吧,虽然最终也没有算解决掉这个问题。
按照该方案(方案1)敲出代码进行测试(代码附在后面),用定时器去跑该文章的测试代码,发现仍然crash。
分析该文章,认为helper分区分配的内存空间,地址处于了nano分区的地址区段,释放时由nano分区释放,导致nano_free方法中,检查认为该内存空间没有在nano分区中分配过,因此报错“指针过度释放”。因此,尝试方案2,方案二替换helper分区的内存分配代码,当内存分配的地址处于nano分区的区段时,则释放该内存,重新申请内存,直到地址区段正确。测试该方案发现,当helper分区分配的内存空间地址不正确后,重新申请的地址空间也大概率不正确,导致分配空间性能严重下降。不可行~
鉴于第二个方案不可行,因此尝试第三个方案。方案3为,替换nano分区的内存释放函数,替换后,当nano分区释放内存时,检查该指针是否属于helper分区分配,如果是,则把该内存的释放转至helper zone。测试该方案,该方案在iOS10.1.1中跑图片下载功能,crash率下降,下降至和iOS10.2的情况比较类似。根据该方案的crash提示报错分析,应该是iOS10中,不止存在helper分区分配了nano分区地址段的内存,也存在nano分区分配了非nano分区地址区段的内存的情况(感觉iOS10.2就是部分解决了该问题,只是没考虑后一种情况),因此按照这个思路,helper zone释放空间时也进行检测,可能能完全解决这个问题。但是由于每次释放空间之前都检查内存是否由另一个分区分配,会导致性能下降,另外也有项目进度原因,我便没有进行进一步尝试。而是选择了减少内存操作的方法来解决了该功能引起的crash。 附上测试的代码,以供参考。
代码中若有不对之处,请指正,多谢。
哪位看到有更好的解决办法的,麻烦告知一下,多谢。
//方案1:微信团队方案,使用guard zone
//效果,只要地址错误一次之后就经常申请错误,导致会反复的申请释放空间
malloc_zone_t *NanoCrashGuardInitialize3();
static malloc_zone_t *s_default_zone = NULL;
static malloc_zone_t *s_guard_zone = NanoCrashGuardInitialize3();
typedef void *(*GuardMalloc)(struct _malloc_zone_t *zone, size_t size);
typedef void (*GuardFree)(struct _malloc_zone_t *zone, void *ptr);
typedef size_t (*GuardSize)(struct _malloc_zone_t *zone, const void *ptr);
typedef void * (*GuardCalloc)(struct _malloc_zone_t *zone, size_t num_items, size_t size);
typedef void * (*GuardValloc)(struct _malloc_zone_t *zone, size_t size);
typedef void *(*GuardRealloc)(struct _malloc_zone_t *zone, void *ptr, size_t size);
typedef void (*GuardBatchFree)(struct _malloc_zone_t *zone, void **to_be_freed, unsigned num_to_be_freed);
typedef void *(*GuardMemalign)(struct _malloc_zone_t *zone, size_t alignment, size_t size);
typedef void (*GuardFreeDefiniteSize)(struct _malloc_zone_t *zone, void *ptr, size_t size);
typedef size_t (*GuardPressureRelief)(struct _malloc_zone_t *zone, size_t goal);
typedef unsigned (*GuardBatchMalloc)(struct _malloc_zone_t *zone, size_t size, void **results, unsigned num_requested);
GuardMalloc s_default_zone_origin_malloc = NULL;
GuardFree s_default_zone_origin_free = NULL;
GuardSize s_default_zone_origin_size = NULL;
GuardCalloc s_default_zone_origin_calloc = NULL;
GuardValloc s_default_zone_origin_valloc = NULL;
GuardRealloc s_default_zone_origin_realloc = NULL;
GuardBatchMalloc s_default_zone_origin_batch_malloc = NULL;
GuardBatchFree s_default_zone_origin_batch_free = NULL;
GuardMemalign s_default_zone_origin_memalign = NULL;
GuardFreeDefiniteSize s_default_zone_origin_free_definite_size = NULL;
GuardPressureRelief s_default_zone_origin_pressure_relief = NULL;
void *default_zone_malloc(struct _malloc_zone_t *zone, size_t size){
return s_guard_zone->malloc(s_guard_zone, size);
}
void default_zone_free(struct _malloc_zone_t *zone, void *ptr){
size_t s = s_guard_zone->size(s_guard_zone, ptr);
if (s) {
return s_guard_zone->free(s_guard_zone, ptr);
}
return s_default_zone_origin_free(zone, ptr);
}
size_t default_zone_size(struct _malloc_zone_t *zone, const void *ptr){
size_t s = s_guard_zone->size(s_guard_zone, ptr);
if (s) {
return s;
}
return s_default_zone_origin_size(zone,