美文网首页学习iOS技术iOS开发专区iOS开发攻城狮的集散地
sdweimage解码大图内存泄漏以及内存暴增的解决办法

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

作者: Rxiaobing | 来源:发表于2017-07-31 16:29 被阅读1318次

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

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

    相关文章

      网友评论

        本文标题:sdweimage解码大图内存泄漏以及内存暴增的解决办法

        本文链接:https://www.haomeiwen.com/subject/exerlxtx.html