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;
}