学习iOS技术iOS开发专区iOS开发攻城狮的集散地

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

2017-07-31  本文已影响1318人  Rxiaobing

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

最近在优化项目,遇到图片优化问题,由于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;
        }
上一篇下一篇

猜你喜欢

热点阅读