sdweimage解码大图内存泄漏以及内存暴增的解决办法

如有朋友觉得我测试的不对或者是有问题,还请指正,谢谢!

最近在优化项目,遇到图片优化问题,由于cell中所用的图片比较大,所以会造成内粗爆增,在网上查了很多资料,包括官方的大图下载demo,然后又参考了sd关于大图的解码处理,发现两者基本相同,而且都有同样的问题那就是会有内存泄漏,对于大图来说秒,出现内存泄漏,带来的内存开销肯对会更大,所以使用中一定要注意。测试使用的是iphone6,ios10.2.1系统

关于使用sd导致内存暴增的一些建议

首先你要明白为什么使用sd会导致内存暴增

原因

简单来说因为,sd在下载完图片以后,会先解码图片,并将解码后的图片缓存在内存中,如果是从硬盘读取的图像数据,也会经过解码,并将解码后的图片缓存在内存中,然后如果一张图片是1000x1000,那么解码后的位图大小就是,1000x1000x4/1024/1024,如果图片再大一些,那么解码后的图片将会很大,我的项目中,解码后的图片是70M左右,所以运行起来以后,手机内存增长严重,多显示几张图片以后就会闪退,

是否可以不解码显示

答案当然是可以的,但是如果不解码直接显示,则会比较卡顿,解码大图默认是在主线程进行的!

解决思路

后来参考了苹果官方的demo,做了一些尝试,个人理解是,在解码大图时,将大图偷偷的转换成了小图,根据计算来的缩放比例来缩放图片,我的解决思路时重写了sd的解码方法,以及scaleimage 方法,给sd的解码方法,增加一个解码的缩放比例,这样就解决了内存问题,成功将内存从原来的几百兆降低到了100M左右,在你需要使用大图的原图时,你可以选择禁用解码图片,然后自己在异步解码,并将解码后的图片显示出来!

代码如下

代码可能会比较多
static inline void swizzleMethod(Class class, SEL oldSel, SEL newSel){
    
    //class_getInstanceMethod既可以获取实例方法,又可以获取类方法
    //class_getClassMethod只能获取类方法,不能获取实例方法
    Method oldMethod = class_getInstanceMethod(class, oldSel);
    Method newMethod = class_getInstanceMethod(class, newSel);
    
    
    //     Method newMethod = class_getClassMethod(class, newSel);
    //    Method oldMethod = class_getClassMethod(class, oldSel);
    
    
    IMP oldIMP = class_getMethodImplementation(class, oldSel);
    IMP newIMP = class_getMethodImplementation(class, newSel);
    
    //oldsel 未实现(当判断类方法时,需要传入的类为object_getClass((id)self),否则即使原来的方法已经实现,也会执行一下的第一段代码,并且造成交换方法失败
    if (class_addMethod(class, oldSel, newIMP, method_getTypeEncoding(newMethod))) {
        class_replaceMethod(class, newSel, oldIMP, method_getTypeEncoding(oldMethod));
    } else {
        //oldsel 已经实现
        method_exchangeImplementations(oldMethod, newMethod);
    }
    
}
static void swizzleClassMethod(Class class, SEL oldSel, SEL newSel){
    //要特别注意你替换的方法到底是哪个性质的方法
    // When swizzling a Instance method, use the following:
    //        Class class = [self class];
    
    // When swizzling a class method, use the following:
    
    //        Class class = object_getClass((id)self);//获取到的是类的地址(0x00000001ace88090)
    //        Class cs = [[[UIImage alloc] init] class];//获取到的是类名(UIImage)
    //
    //        Class cl = [self class];//获取到的是类名(UIImage)
    
    Class relclass = object_getClass(class);
    swizzleMethod(relclass, oldSel, newSel);
}

static void swizzleIntanceMethod(Class class, SEL oldSel, SEL newSel){
    swizzleMethod(class, oldSel, newSel);
}


static NSMutableDictionary * imageForKeyDict(){
    static dispatch_once_t onceToken;
    static NSMutableDictionary *imageForKeyDict = nil;
    dispatch_once(&onceToken, ^{
        imageForKeyDict = [NSMutableDictionary dictionary];
    });
    return imageForKeyDict;
}

static NSMutableDictionary * scaleForKeyDict(){
    static dispatch_once_t onceToken;
    static NSMutableDictionary *scaleForKeyDict = nil;
    dispatch_once(&onceToken, ^{
        scaleForKeyDict = [NSMutableDictionary dictionary];
    });
    return scaleForKeyDict;
}

//static pthread_mutex_t pthread_lock(){
//    
//    static pthread_mutexattr_t attr;
//    static pthread_mutex_t pthread_lock;
//    static dispatch_once_t onceToken;
//    dispatch_once(&onceToken, ^{
//        pthread_mutexattr_init(&attr);
//        pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
//        pthread_mutex_init(&pthread_lock, &attr);
//    });
//    return pthread_lock;
//}

static void dictionarySetWeakRefrenceObjectForKey(NSMutableDictionary *dictM, id object, NSString *key){
    
    NSValue *value = [NSValue valueWithNonretainedObject:object];
    [dictM setValue:value forKey:key];
//    [dictM setObject:object forKey:key];
}

static id dictionaryGetWeakRefrenceObjectForKey(NSMutableDictionary *dictM, NSString *key){
    NSValue *value = [dictM valueForKey:key];
    return value.nonretainedObjectValue;
//    return [dictM objectForKey:key];
}

static void dictionarySetObjectForKey(NSMutableDictionary *dictM, id object, NSString *key){
    if(!object || !key) return;
    [dictM setObject:object forKey:key];
    //    [dictM setObject:object forKey:key];
}

static id dictionaryGetObjectForKey(NSMutableDictionary *dictM, NSString *key){
  if(!key)return nil;
    return [dictM objectForKey:key];
    //    return [dictM objectForKey:key];
}

static void dictionaryRemoveObjectForKey(NSMutableDictionary *dictM, NSString *key){
if(!key) return;
    [dictM removeObjectForKey:key];
}

#pragma clang diagnostic pop

static NSArray *dictionaryAllKeysForObject(NSMutableDictionary *dictM,id object){
  if(!obect) return nil;
    return [dictM allKeysForObject:object];
}

#pragma clang diagnostic push
#pragma clang diagnostic ignored"-Wundeclared-selector"

@implementation SDImageCache (zb_decode)
+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        swizzleIntanceMethod(self, @selector(scaledImageForKey:image:), @selector(zb_scaledImageForKey:image:));
    });
}

- (nullable UIImage *)zb_scaledImageForKey:(nullable NSString *)key image:(nullable UIImage *)image
{
    UIImage *destImage = [self zb_scaledImageForKey:key image:image];
    dictionarySetObjectForKey(imageForKeyDict(), destImage, key);
    return destImage;
}

@end


@implementation SDWebImageDownloaderOperation (zb_decode)

+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        swizzleIntanceMethod(self, @selector(scaledImageForKey:image:), @selector(zb_scaledImageForKey:image:));
    });
}

- (nullable UIImage *)zb_scaledImageForKey:(nullable NSString *)key image:(nullable UIImage *)image
{
    UIImage *destImage = [self zb_scaledImageForKey:key image:image];
    dictionarySetObjectForKey(imageForKeyDict(), destImage, key);
    return destImage;
}

@end
#pragma clang diagnostic pop

#ifndef BYTE_SIZE
#define BYTE_SIZE 8 // byte size in bits
#endif

#define MEGABYTE (1024 * 1024)



//static BOOL ScaleDecode = NO;
//static float ImageScale = 1.0f;

@implementation UIImage (ZB_DecodeImage)

//+ (void)setScaleDecode:(BOOL)scaleDecode
//{
//    ScaleDecode = scaleDecode;
//}
//
//+ (BOOL)scaleDecode
//{
//    return ScaleDecode;
//}

+ (void)setImageScale:(float)imageScale ForUrl:(NSString *)url
{
    
    dictionarySetObjectForKey(scaleForKeyDict(), @(imageScale), url);
}

+ (float)imageScleForUrl:(NSString *)url
{
    return [dictionaryGetObjectForKey(scaleForKeyDict(), url) floatValue];
}
//+ (void)setImageScale:(float)imageScale
//{
//    ImageScale = imageScale;
//}
//+ (float)imageScale
//{
//    return ImageScale;
//}
+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        swizzleClassMethod(self, @selector(decodedImageWithImage:), @selector(zb_decodeImageWithImage:));
    });
}


+ (UIImage *)zb_decodeImageWithImage:(UIImage *)image
{
    @autoreleasepool {
        CGImageRef imageRef = image.CGImage;
        if (!imageRef) {
            return image;
        }
        CGColorSpaceRef colorSpace = CGImageGetColorSpace(imageRef);
        CGColorSpaceModel colorSpaceModel = CGColorSpaceGetModel(colorSpace);
        BOOL unsupportedColorSpace = (colorSpaceModel == kCGColorSpaceModelUnknown ||
                                      colorSpaceModel == kCGColorSpaceModelMonochrome ||
                                      colorSpaceModel == kCGColorSpaceModelCMYK ||
                                      colorSpaceModel == kCGColorSpaceModelIndexed);
        unsupportedColorSpace = YES;
        if (unsupportedColorSpace) {
            colorSpace = CGColorSpaceCreateDeviceRGB();
            CFAutorelease(colorSpace);
        }
        
        float imageScale = 1.0f;
        NSString *key = dictionaryAllKeysForObject(imageForKeyDict(), image).lastObject;
        if (key) {
            imageScale = [self imageScleForUrl:key];
            if (!imageScale) {
                imageScale = 1.0f;
            }
            NSLog(@"%f",imageScale);
        }
        //不使用时移除image对象,否则会造成内存的很大浪费
        dictionaryRemoveObjectForKey(imageForKeyDict(), key);
        //重新设置imageScale,避免由于imageScale过小导致的图片不清晰问题
size_t oriWidth = CGImageGetWidth(imageRef);
        size_t oriHeight = CGImageGetHeight(imageRef);
        
        size_t width = oriWidth * imageScale;
        size_t height = oriHeight * imageScale;
        if (imageScale == CourseImageScale) {
            if (width / height > 1.0f) {
                if (height < 200) {
                    imageScale = 200 / (oriHeight * 1.00f);
                } else {
                    if (width > 750) {
                        imageScale = 750 / (oriWidth * 1.0);
                    }
                }
            } else {
                if (width < 300) {
                    imageScale = 300 / (oriWidth * 1.0);
                } else {
                    if (height > 500) {
                        imageScale = 500 / (oriHeight * 1.0);
                    }
                }
            }
        }
        NSLog(@"%f",imageScale);
        width = oriWidth * imageScale;
        height = oriHeight * imageScale;
        size_t numberOfcomponents = CGColorSpaceGetNumberOfComponents(colorSpace) + 1;
        size_t bitsPerComponent = CHAR_BIT;
        size_t bitsPerPixel = (bitsPerComponent * numberOfcomponents);
        size_t bytesPerPixel = (bitsPerPixel / BYTE_SIZE);
        size_t bytesPerRow = (bytesPerPixel * width);
        
        if (width == 0 || height == 0) {
            return image;
        }
        BOOL hasAlpha = NO;
        CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(imageRef);
        hasAlpha = (alphaInfo == kCGImageAlphaPremultipliedLast ||
                    alphaInfo == kCGImageAlphaPremultipliedFirst ||
                    alphaInfo == kCGImageAlphaLast ||
                    alphaInfo == kCGImageAlphaFirst);
        CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault;
        bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;
        
        //    void *data = malloc(height * bytesPerRow);
        //    if (data == NULL) {
        //        free(data);
        //        return image;
        //    }
        CGContextRef context = CGBitmapContextCreate(NULL, width, height, bitsPerComponent, bytesPerRow, colorSpace, bitmapInfo);
        //
        if (context == NULL) {
            return image;
        }
       
        
        CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
        CGImageRef deImageRef = CGBitmapContextCreateImage(context);
        //
        //CGImageRelease(imageRef);会将外部的对象释放,有可能造成野指针错误
        //release
        CGContextRelease(context);
        
        UIImage *decImage = [UIImage imageWithCGImage:deImageRef scale:image.scale orientation:image.imageOrientation];
        //release
        CGImageRelease(deImageRef);
        
        return decImage;
    }
}

SD大图解码内存泄漏问题

修改前的截图


屏幕快照 2017-07-26 下午4.27.03.png

修改后的截图

屏幕快照 2017-07-26 下午4.28.22.png

使用(decodedAndScaledDownImageWithImage)会有内存泄漏

void* destBitmapData = malloc( bytesPerRow * destResolution.height );
        if (destBitmapData == NULL) {
            free(destBitmapData);
            return image;
        }
修改后的代码
  CGImageRef destImageRef = CGBitmapContextCreateImage(destContext);
        //
        CGContextRelease(destContext);
        free(destBitmapData);//在释放destContext的时候,释放destbitmapdata
        
        if (destImageRef == NULL) {
            return image;
        }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容