iOS10的nano_free nano_relocated crash总结
来源:互联网 发布:年度工作总结 知乎 编辑:程序博客网 时间:2024/06/07 09:47
在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, ptr);}// same as malloc, but block returned is set to zero //void *default_zone_calloc(struct _malloc_zone_t *zone, size_t num_items, size_t size){ return s_guard_zone->calloc(s_guard_zone, num_items, size);}// same as malloc, but block returned is set to zero and is guaranteed to be page alignedvoid *default_zone_valloc(struct _malloc_zone_t *zone, size_t size){ return s_guard_zone->valloc(s_guard_zone, size);}void *default_zone_realloc(struct _malloc_zone_t *zone, void *ptr, size_t size){ size_t s = s_guard_zone->size(s_guard_zone, ptr); if (s) { return s_guard_zone->realloc(s_guard_zone, ptr, size); }else{ return s_default_zone_origin_realloc(zone, ptr, size); }}// Optional batch callbacks; these may be NULL// given a size, returns pointers capable of holding that size; returns the number of pointers allocated (maybe 0 or less than num_requested)unsigned default_zone_batch_malloc(struct _malloc_zone_t *zone, size_t size, void **results, unsigned num_requested){ return s_guard_zone->batch_malloc(s_guard_zone, size, results, num_requested);}// frees all the pointers in to_be_freed; note that to_be_freed may be overwritten during the processvoid default_zone_batch_free(struct _malloc_zone_t *zone, void **to_be_freed, unsigned num_to_be_freed){ size_t s = s_guard_zone->size(s_guard_zone, to_be_freed); if (s) { return s_guard_zone->batch_free(s_guard_zone, to_be_freed, num_to_be_freed); } return s_default_zone_origin_batch_free(zone, to_be_freed, num_to_be_freed);}// aligned memory allocation. The callback may be NULL. Present in version >= 5.void *default_zone_memalign(struct _malloc_zone_t *zone, size_t alignment, size_t size){ //guard zone一起执行 s_default_zone_origin_memalign(zone, alignment, size); s_guard_zone->memalign(s_guard_zone, alignment, size); //??返回值是否换这个 return NULL;}// free a pointer known to be in zone and known to have the given size. The callback may be NULL. Present in version >= 6.//void default_zone_free_definite_size(struct _malloc_zone_t *zone, void *ptr, size_t size){ size_t s = s_guard_zone->size(s_guard_zone, ptr); if (s) { return s_guard_zone->free_definite_size(s_guard_zone, ptr, size); } return s_default_zone_origin_free_definite_size(zone, ptr, size);}// Empty out caches in the face of memory pressure. The callback may be NULL. Present in version >= 8.size_t default_zone_pressure_relief(struct _malloc_zone_t *zone, size_t goal){ //guard zone一起执行 return s_guard_zone->pressure_relief(s_guard_zone, goal) + s_default_zone_origin_pressure_relief(zone, goal);}extern malloc_zone_t **malloc_zones; // 定义在malloc.c内extern int32_t malloc_num_zones; //定义在malloc.c内malloc_zone_t *NanoCrashGuardInitialize3(){ // return NULL; s_guard_zone = malloc_create_zone(getpagesize(), 0); malloc_set_zone_name(s_guard_zone, "GuardZone"); s_default_zone = malloc_default_zone(); mprotect(s_default_zone, sizeof(malloc_zone_t), PROT_READ | PROT_WRITE); OSMemoryBarrier(); //此时已有多线程,需用atomic OSAtomicCompareAndSwapPtr((void *)s_default_zone_origin_malloc, (void *)s_default_zone->malloc, (void *volatile *) &s_default_zone_origin_malloc); OSAtomicCompareAndSwapPtr((void *)s_default_zone->malloc, (void *)default_zone_malloc, (void *volatile *) &s_default_zone->malloc); OSAtomicCompareAndSwapPtr((void *)s_default_zone_origin_free, (void *)s_default_zone->free, (void *volatile *) &s_default_zone_origin_free); OSAtomicCompareAndSwapPtr((void *)s_default_zone->free, (void *)default_zone_free, (void *volatile *) &s_default_zone->free); OSAtomicCompareAndSwapPtr((void *)s_default_zone_origin_size, (void *)s_default_zone->size, (void *volatile *) &s_default_zone_origin_size); OSAtomicCompareAndSwapPtr((void *)s_default_zone->size, (void *)default_zone_size, (void *volatile *) &s_default_zone->size); //...替换malloc_zone_t的其它函数// OSAtomicCompareAndSwapPtr((void *)s_default_zone_origin_calloc, (void *)s_default_zone->calloc, (void *volatile *) &s_default_zone_origin_calloc);// OSAtomicCompareAndSwapPtr((void *)default_zone_calloc, (void *)s_default_zone->calloc, (void *volatile *) &s_default_zone->calloc);// OSAtomicCompareAndSwapPtr((void *)s_default_zone_origin_valloc, (void *)s_default_zone->valloc, (void *volatile *) &s_default_zone_origin_valloc);// OSAtomicCompareAndSwapPtr((void *)default_zone_valloc, (void *)s_default_zone->valloc, (void *volatile *) &s_default_zone->valloc);// OSAtomicCompareAndSwapPtr((void *)s_default_zone_origin_realloc, (void *)s_default_zone->realloc, (void *volatile *) &s_default_zone_origin_realloc);// OSAtomicCompareAndSwapPtr((void *)default_zone_realloc, (void *)s_default_zone->realloc, (void *volatile *) &s_default_zone->realloc);// OSAtomicCompareAndSwapPtr((void *)s_default_zone_origin_batch_malloc, (void *)s_default_zone->batch_malloc, (void *volatile *) &s_default_zone_origin_batch_malloc);// OSAtomicCompareAndSwapPtr((void *)default_zone_batch_malloc, (void *)s_default_zone->batch_malloc, (void *volatile *) &s_default_zone->batch_malloc);// OSAtomicCompareAndSwapPtr((void *)s_default_zone_origin_batch_free, (void *)s_default_zone->batch_free, (void *volatile *) &s_default_zone_origin_batch_free);// OSAtomicCompareAndSwapPtr((void *)default_zone_batch_free, (void *)s_default_zone->batch_free, (void *volatile *) &s_default_zone->batch_free);// OSAtomicCompareAndSwapPtr((void *)s_default_zone_origin_memalign, (void *)s_default_zone->memalign, (void *volatile *) &s_default_zone_origin_memalign);// OSAtomicCompareAndSwapPtr((void *)default_zone_memalign, (void *)s_default_zone->memalign, (void *volatile *) &s_default_zone->memalign);// OSAtomicCompareAndSwapPtr((void *)s_default_zone_origin_free_definite_size, (void *)s_default_zone->free_definite_size, (void *volatile *) &s_default_zone_origin_free_definite_size);// OSAtomicCompareAndSwapPtr((void *)default_zone_free_definite_size, (void *)s_default_zone->free_definite_size, (void *volatile *) &s_default_zone->free_definite_size);// OSAtomicCompareAndSwapPtr((void *)s_default_zone_origin_pressure_relief, (void *)s_default_zone->pressure_relief, (void *volatile *) &s_default_zone_origin_pressure_relief);// OSAtomicCompareAndSwapPtr((void *)default_zone_pressure_relief, (void *)s_default_zone->pressure_relief, (void *volatile *) &s_default_zone->pressure_relief); OSMemoryBarrier(); mprotect(s_default_zone, sizeof(malloc_zone_t), PROT_READ); return s_guard_zone;}
//方案2,替换helperzone的所有分配空间方法,当分配到的空间错误到0x17地址,则重新申请,并释放错误空间//效果,只要地址错误一次之后就经常申请错误,导致会反复的申请释放空间malloc_zone_t *NanoCrashGuardInitialize1();static malloc_zone_t *s_helper_zone = NanoCrashGuardInitialize1();typedef void *(*GuardMalloc)(struct _malloc_zone_t *zone, size_t size);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 unsigned (*GuardBatchMalloc)(struct _malloc_zone_t *zone, size_t size, void **results, unsigned num_requested);GuardMalloc s_helper_zone_origin_malloc = NULL;GuardCalloc s_helper_zone_origin_calloc = NULL;GuardValloc s_helper_zone_origin_valloc = NULL;GuardRealloc s_helper_zone_origin_realloc = NULL;GuardBatchMalloc s_helper_zone_origin_batch_malloc = NULL;void *helper_zone_malloc(struct _malloc_zone_t *zone, size_t size){ void *ptr = s_helper_zone_origin_malloc(zone, size); while ((uintptr_t)ptr>>28 == 0x17 ) { //地址申请出错,重新申请新的内存(先释放再申请的话,不确定是不是还是分配到原地址,所以先申请再释放) void *againPtr = s_helper_zone_origin_malloc(zone, size); //立即释放原内存 zone->free(zone, ptr); ptr = againPtr; } return ptr;}void *helper_zone_calloc(struct _malloc_zone_t *zone, size_t num_items, size_t size){ void *ptr = s_helper_zone_origin_calloc(zone, num_items, size); while ((uintptr_t)ptr>>28 == 0x17 ) { //地址申请出错,重新申请新的内存(先释放再申请的话,不确定是不是还是分配到原地址,所以先申请再释放) void *againPtr = s_helper_zone_origin_calloc(zone, num_items, size); //立即释放原内存 zone->free(zone, ptr); ptr = againPtr; } return ptr;}// same as malloc, but block returned is set to zero and is guaranteed to be page alignedvoid *helper_zone_valloc(struct _malloc_zone_t *zone, size_t size){ void *ptr = s_helper_zone_origin_valloc(zone, size); while ((uintptr_t)ptr>>28 == 0x17 ) { //地址申请出错,重新申请新的内存(先释放再申请的话,不确定是不是还是分配到原地址,所以先申请再释放) void *againPtr = s_helper_zone_origin_valloc(zone, size); //立即释放原内存 zone->free(zone, ptr); ptr = againPtr; } return ptr;}void *helper_zone_realloc(struct _malloc_zone_t *zone, void *ptr, size_t size){ void *reallocPtr = s_helper_zone_origin_realloc(zone, ptr, size); while ((uintptr_t)reallocPtr>>28 == 0x17 ) { //地址申请出错,重新申请新的内存 reallocPtr = s_helper_zone_origin_realloc(zone, reallocPtr, size); } return reallocPtr;}// Optional batch callbacks; these may be NULL // given a size, returns pointers capable of holding that size; returns the number of pointers allocated (maybe 0 or less than num_requested)unsigned helper_zone_batch_malloc(struct _malloc_zone_t *zone, size_t size, void **results, unsigned num_requested){ unsigned number = s_helper_zone_origin_batch_malloc(zone, size, results, num_requested); uintptr_t ptr = (uintptr_t)*results; while (ptr>>28 == 0x17 ) { //地址申请出错,重新申请新的内存 void *wrongResults = (void *)*results; unsigned againNumber = ptr = s_helper_zone_origin_batch_malloc(zone, size, results, num_requested); ptr = (uintptr_t)*results; //立即释放原内存 zone->batch_free(zone, &wrongResults, number); number = againNumber; } return number;}extern malloc_zone_t **malloc_zones; // 定义在malloc.c内extern int32_t malloc_num_zones; //定义在malloc.c内malloc_zone_t *NanoCrashGuardInitialize1(){ // return NULL; s_helper_zone = malloc_zones[1]; mprotect(s_helper_zone, sizeof(malloc_zone_t), PROT_READ | PROT_WRITE); OSMemoryBarrier(); //此时已有多线程,需用atomic OSAtomicCompareAndSwapPtr((void *)s_helper_zone_origin_malloc, (void *)s_helper_zone->malloc, (void *volatile *) &s_helper_zone_origin_malloc); OSAtomicCompareAndSwapPtr((void *)s_helper_zone->malloc, (void *)helper_zone_malloc, (void *volatile *) &s_helper_zone->malloc); OSAtomicCompareAndSwapPtr((void *)s_helper_zone_origin_calloc, (void *)s_helper_zone->calloc, (void *volatile *) &s_helper_zone_origin_calloc); OSAtomicCompareAndSwapPtr((void *)s_helper_zone->calloc, (void *)helper_zone_calloc, (void *volatile *) &s_helper_zone->calloc); OSAtomicCompareAndSwapPtr((void *)s_helper_zone_origin_valloc, (void *)s_helper_zone->valloc, (void *volatile *) &s_helper_zone_origin_valloc); OSAtomicCompareAndSwapPtr((void *)s_helper_zone->valloc, (void *)helper_zone_valloc, (void *volatile *) &s_helper_zone->valloc); OSAtomicCompareAndSwapPtr((void *)s_helper_zone_origin_realloc, (void *)s_helper_zone->realloc, (void *volatile *) &s_helper_zone_origin_realloc); OSAtomicCompareAndSwapPtr((void *)s_helper_zone->realloc, (void *)helper_zone_realloc, (void *volatile *) &s_helper_zone->realloc); OSAtomicCompareAndSwapPtr((void *)s_helper_zone_origin_batch_malloc, (void *)s_helper_zone->batch_malloc, (void *volatile *) &s_helper_zone_origin_batch_malloc); OSAtomicCompareAndSwapPtr((void *)s_helper_zone->batch_malloc, (void *)helper_zone_batch_malloc, (void *volatile *) &s_helper_zone->batch_malloc); OSMemoryBarrier(); mprotect(s_helper_zone, sizeof(malloc_zone_t), PROT_READ); return s_helper_zone;}
/*//方案3,替换nanozone的所有释放空间方法,每次释放空间前都先检查是否属于helper分区分配的空间,若属于,则转到helper zone释放//效果,目前是最好的效果,在iOS10.1.1测试#include <malloc/malloc.h>#include <unistd.h>#include <sys/mman.h>#include <stdatomic.h>#include <libkern/OSAtomicDeprecated.h>malloc_zone_t *NanoCrashGuardInitialize2();static malloc_zone_t *s_default_zone = NanoCrashGuardInitialize2();static malloc_zone_t *s_helper_zone = NULL;typedef void (*GuardFree)(struct _malloc_zone_t *zone, void *ptr);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 (*GuardFreeDefiniteSize)(struct _malloc_zone_t *zone, void *ptr, size_t size);GuardFree s_default_zone_origin_free = NULL;GuardRealloc s_default_zone_origin_realloc = NULL;GuardBatchFree s_default_zone_origin_batch_free = NULL;GuardFreeDefiniteSize s_default_zone_origin_free_definite_size = NULL;#pragma mark default zone methods++++++++++++++++void default_zone_free(struct _malloc_zone_t *zone, void *ptr){ size_t s = s_helper_zone->size(s_helper_zone, ptr); if (s) { return s_helper_zone->free(s_helper_zone, ptr); } return s_default_zone_origin_free(zone, ptr);}void *default_zone_realloc(struct _malloc_zone_t *zone, void *ptr, size_t size){ size_t s = s_helper_zone->size(s_helper_zone, ptr); if (s) { s_helper_zone->realloc(s_helper_zone, ptr, size); } return s_default_zone_origin_realloc(zone, ptr, size);}// frees all the pointers in to_be_freed; note that to_be_freed may be overwritten during the processvoid default_zone_batch_free(struct _malloc_zone_t *zone, void **to_be_freed, unsigned num_to_be_freed){ size_t s = s_helper_zone->size(s_helper_zone, to_be_freed); if (s) { return s_helper_zone->batch_free(s_helper_zone, to_be_freed, num_to_be_freed); } return s_default_zone_origin_batch_free(zone, to_be_freed, num_to_be_freed);}// free a pointer known to be in zone and known to have the given size. The callback may be NULL. Present in version >= 6.void default_zone_free_definite_size(struct _malloc_zone_t *zone, void *ptr, size_t size){ size_t s = s_helper_zone->size(s_helper_zone, ptr); if (s) { return s_helper_zone->free_definite_size(s_helper_zone, ptr, size); } return s_default_zone_origin_free_definite_size(zone, ptr, size);}extern malloc_zone_t **malloc_zones; // 定义在malloc.c内extern int32_t malloc_num_zones; //定义在malloc.c内malloc_zone_t *NanoCrashGuardInitialize2(){ s_default_zone = malloc_default_zone(); s_helper_zone = malloc_zones[1]; mprotect(s_default_zone, sizeof(malloc_zone_t), PROT_READ | PROT_WRITE); OSMemoryBarrier(); OSAtomicCompareAndSwapPtr((void *)s_default_zone_origin_free, (void *)s_default_zone->free, (void *volatile *) &s_default_zone_origin_free); OSAtomicCompareAndSwapPtr((void *)s_default_zone->free, (void *)default_zone_free, (void *volatile *) &s_default_zone->free); OSAtomicCompareAndSwapPtr((void *)s_default_zone_origin_realloc, (void *)s_default_zone->realloc, (void *volatile *) &s_default_zone_origin_realloc); OSAtomicCompareAndSwapPtr((void *)default_zone_realloc, (void *)s_default_zone->realloc, (void *volatile *) &s_default_zone->realloc); OSAtomicCompareAndSwapPtr((void *)s_default_zone_origin_batch_free, (void *)s_default_zone->batch_free, (void *volatile *) &s_default_zone_origin_batch_free); OSAtomicCompareAndSwapPtr((void *)default_zone_batch_free, (void *)s_default_zone->batch_free, (void *volatile *) &s_default_zone->batch_free); OSAtomicCompareAndSwapPtr((void *)s_default_zone_origin_free_definite_size, (void *)s_default_zone->free_definite_size, (void *volatile *) &s_default_zone_origin_free_definite_size); OSAtomicCompareAndSwapPtr((void *)default_zone_free_definite_size, (void *)s_default_zone->free_definite_size, (void *volatile *) &s_default_zone->free_definite_size); OSMemoryBarrier(); mprotect(s_default_zone, sizeof(malloc_zone_t), PROT_READ); return malloc_default_zone();}
用定时器去申请内存,测试代码如下:
NSTimer *timer = [NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) { while (1) { uintptr_t ptr = (uintptr_t)malloc(NANO_MAX+1); ptrs.push_back(ptr); if (ptr>>28 == 0x17) { NSLog(@"timer one time!!!!!"); break; } } }]; [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
- iOS10的nano_free nano_relocated crash总结
- 【腾讯Bugly干货分享】聊聊苹果的Bug - iOS 10 nano_free Crash
- iOS10 权限导致crash的问题解决方案
- iOS10解决权限Crash
- 升级Xcode8后的相机crash问题-IOS10权限问题
- iOS10 里面适配权限Crash的问题
- iOS - 适配iOS10以及由于权限crash的问题
- 升级Xcode8后的相机crash问题-IOS10权限问题
- iOS10 访问相册,相机或者联系人 Crash 的解决方法
- 关于iOS10 获取权限导致Crash问题的解决办法
- 升级Xcode8后的相机crash问题-IOS10权限问题
- UIImagePickerController在ios10环境一打开就crash的问题
- iOS10的适配总结
- iOS10 权限访问Crash问题
- ABPeoplePicker在iOS10中crash
- 关于iOS10 - iMessage App的总结
- iOS9和iOS10推送的问题总结
- iOS10的适配总结(转)
- javacv根据别人的修改的关于对图片操作的工具类
- PAT A1050
- Android简单的ViewPager指示器
- 批量删除含有指定关键词的行——EXCEL 宏
- 查询字符串参数
- iOS10的nano_free nano_relocated crash总结
- 第十六周 结构体---职工信息结构体
- 单源最短路径---Dijkstra算法
- iOS通过dSYM文件分析crash
- 左旋转字符串
- Java基础学习总结——equals方法
- (Java)面向对象编程六大基本原则
- c语言
- 使用百度地图定位当前位置并获取附近poi -- Android学习之路