ios开发系列之内存泄漏分析(上)
iOS自从引入ARC机制后,一般的内存管理就可以不用我们码农来负责了,但是一些操作如果不注意,还是会引起内存泄漏。
本文主要介绍一下内存泄漏的原理、常规的检测方法以及出现的常用场景和修改方法。
1、 内存泄漏原理
内存泄漏的在百度上的解释就是“程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果”。
在我的理解里就是,公司给一个入职的员工分配了一个工位,但是这个员工离职后,这个工位却不能分配给下一位入职的员工使用,造成了大量的资源浪费。
2、 常规的检测方法
2.1、Analyze静态分析 (command + shift + b)。
2.2、动态分析方法(Instrument工具库里的Leaks),product->profile ->leaks 打开可以工具主窗口,具体使用方法可以参考这篇文章:https://www.jianshu.com/p/9fc2132d09c7。
3、 内存泄漏的场景和分析:
3.1、代理的属性关键字设置为strong造成的内存泄漏
请看下面这段代码:
@protocol MFMemoryLeakViewDelegate <NSObject> @end @interface MFMemoryLeakView : UIView @property (nonatomic, strong) id<MFMemoryLeakViewDelegate> delegate; @end
MFMemoryLeakView *view = [[MFMemoryLeakView alloc] initWithFrame:self.view.bounds];
view.delegate = self;
[self.view addSubview:view];
造成的后果就是控制器得不到释放,原因是控制器对视图进行了强引用,而控制器又是视图的代理,视图对代理进行了强引用,导致了控制器和视图的循环引用。
解决方法也很简单,strong改成weak就行:
@property (nonatomic, weak) id<MFMemoryLeakViewDelegate> delegate;
3.2、CoreGraphics框架里申请的内存忘记释放
请看下面这段代码:
- (UIImage *)coreGraphicsMemoryLeak{
CGRect myImageRect = self.view.bounds;
CGImageRef imageRef = [UIImage imageNamed:@"MemoryLeakTip.jpeg"].CGImage;
CGImageRef subImageRef = CGImageCreateWithImageInRect(imageRef, myImageRect);
UIGraphicsBeginImageContext(myImageRect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextDrawImage(context, myImageRect, subImageRef);
UIImage *newImage = [UIImage imageWithCGImage:subImageRef];
CGImageRelease(subImageRef);
// CGImageRelease(imageRef);
UIGraphicsEndImageContext();
return newImage;
}
如果"CGImageRelease(subImageRef)"这行代码缺失,就会引起内存泄漏,使用静态分析可以轻易发现。
需要注意的是:只有当CGImageRef使用create或retain后才要手动release,没有就不需要手动处理了,系统会进行自动的释放。上面的imageRef对象就是这样,如果进行了手动release,会引起不确定性的崩溃。
为什么是不确定性的崩溃呢,目前我支持的一种说法是:CFRelease的对象不能是NULL,若是NULL的话,会引起runtime的错误并且程序要崩溃,本来imageRef的管理者是会在某个时刻调用release的,但是因为这里已经release过了,已经成了NULL,所以当这个调用时期到来的时候就crash掉了。
关于这个问题,大家可以使用我的demo进行尝试,打开后图中注释的代码后运行,先进入内存泄漏的页面,然后返回上级,再进入这个页面,程序崩溃,demo地址见底部。
3.3、 CoreFoundation框架里申请的内存忘记释放
请看下面这段代码:
- (NSString *)coreFoundationMemoryLeak{
CFUUIDRef uuid_ref = CFUUIDCreate(NULL);
CFStringRef uuid_string_ref= CFUUIDCreateString(NULL, uuid_ref);
// NSString *uuid = (__bridge NSString *)uuid_string_ref;
NSString *uuid = (__bridge_transfer NSString *)uuid_string_ref;
CFRelease(uuid_ref);
// CFRelease(uuid_string_ref);
return uuid;
}
如果"CFRelease(uuid_ref)"这行代码缺失,就会引起内存泄漏,使用静态分析可以轻易发现。
需要注意的是:“ __bridge”是将CoreFoundation框架的对象所有权交给Foundation框架来使用,但是Foundation框架中的对象并不能管理该对象的内存。“ __bridge_transfer”是将CoreFoundation框架的对象所有权交给Foundation来管理,如果Foundation中对象销毁,那么我们之前的对象(CoreFoundation)会一起销毁。
所以__bridge_transfer这种桥接方式,以后就不用再自己手动管理内存了。如果上面代码里的“CFRelease(uuid_string_ref)”的注释,uuid就会被销毁,程序运行到reurn 就崩溃。
3.4、NSTimer 不正确使用造成的内存泄漏
3.4.1、NSTimer重复设置为NO的时候,不会引起内存泄漏
3.4.2、NSTimer重复设置为YES的时候,有执行invalidate就不会内存泄漏,没有执行invalidate就会内存泄漏,在 timer的执行方法里调用invalidate也可以。
3.4.3、中间target:控制器无法释放,是因为timer对控制器进行了强引用,使用类方法创建的timer默认加入了runloop,所以,timer只要不持有控制器,控制器就能释放了。
[NSTimer scheduledTimerWithTimeInterval:1 target:[MFTarget target:self] selector:@selector(timerActionOtherTarget:) userInfo:nil repeats:YES];
#import "MFTarget.h" @implementation MFTarget - (instancetype)initWithTarget:(id)target {
_target = target;
return self;
} + (instancetype)target:(id)target {
return [[MFTarget alloc] initWithTarget:target];
} //这里将selector 转发给_target 去响应
- (id)forwardingTargetForSelector:(SEL)selector {
if ([_target respondsToSelector:selector]) {
return _target;
}
return nil;
} - (void)forwardInvocation:(NSInvocation *)invocation {
void *null = NULL;
[invocation setReturnValue:&null];
} - (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
return [NSObject instanceMethodSignatureForSelector:@selector(init)];
}
这样控制器的确是释放了,但是timer的方法还是会在不断的调用,如果对性能要求不那么严谨的,可以使用这种方法,具体代码见demo。
3.4.4、重写NSTimer:结合上面中间target的思路,在timer内部进行invalidate操作,请看一下代码。
@interface MFTimer : NSObject + (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo; + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo; @end
#import "MFTimer.h" @interface MFTimer () @property (nonatomic, weak) id target;
@property (nonatomic, assign) SEL selector;
@property (nonatomic, weak) NSTimer *timer; @end @implementation MFTimer + (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo {
MFTimer *mfTimer = [[MFTimer alloc] init];
mfTimer.timer = [NSTimer timerWithTimeInterval:ti target:mfTimer selector:@selector(timerAction:) userInfo:userInfo repeats:yesOrNo];
mfTimer.target = aTarget;
mfTimer.selector = aSelector;
return mfTimer.timer;
} + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo {
MFTimer *mfTimer = [[MFTimer alloc] init];
mfTimer.timer = [NSTimer scheduledTimerWithTimeInterval:ti target:mfTimer selector:@selector(timerAction:) userInfo:userInfo repeats:yesOrNo];
mfTimer.target = aTarget;
mfTimer.selector = aSelector;
return mfTimer.timer;
} - (void)timerAction:(NSTimer *)timer {
if (self.target) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
//不判断是否响应,是为了不实现定时器的方法就报错
[self.target performSelector:self.selector withObject:timer];
#pragma clang diagnostic pop
}else {
[self.timer invalidate];
self.timer = nil;
}
} @end
3.4.5、使用block创建定时器,需要正确使用block,要执行invalidate,否则也会内存泄漏。这里涉及到block的内存泄漏问题,我会在下篇中一起讲解。
其他内存泄漏如通知和KVO、block循环引用 、NSThread造成的内存泄漏请见下篇。
demo地址请点击这里:https://github.com/zmfflying/ZMFBlogProject
ios开发系列之内存泄漏分析(上)的更多相关文章
- ios开发系列之内存泄漏分析(下)
接上篇,本篇主要讲解通知和 KVO 不移除观察者.block 循环引用 .NSThread 和 RunLoop一起使用造成的内存泄漏. 1.通知造成的内存泄漏 1.1.ios9 以后,一般的通知,都不 ...
- iOS开发 如何检查内存泄漏
本文转载至 http://mobile.51cto.com/iphone-423391.htm 在开发的时候内存泄漏是不可避免的,但是也是我们需要尽量减少的,因为内存泄漏可能会很大程度的影响程序的稳定 ...
- iOS开发系列之性能优化(上)
本篇主要记录一下我对界面优化上的一些探索.关于时间优化的探索将会在中篇里进行介绍.下篇将主要介绍一些耗电优化.安装包瘦身的探索. ### 1.卡顿原理 要了解卡顿原理,需要对帧缓冲区.垂直同步.CPU ...
- iOS开发系列-打印内存地址
打印内存地址 基本数据类型 定义一个基本数据类型,会根据变量类型分配对应的内存空间.比如定义一个int类型的变量a. int a = 10; 内存如下 输入变量a在内存中内存地址 NSLog(@&qu ...
- iOS开发系列之app的一天
本文主要讲述我对 iOS 开发的一些理解,希望能通过 app 从启动到退出,将一些的知识整合起来,形成一条知识链,目前涉及到的知识点有 runloop.runtime.文件存储.界面布局.离线推送.内 ...
- Java内存泄漏分析系列之五:常见的Thread Dump日志案例分析
原文地址:http://www.javatang.com 症状及解决方案 下面列出几种常见的症状即对应的解决方案: CPU占用率很高,响应很慢 按照<Java内存泄漏分析系列之一:使用jstac ...
- Java内存泄漏分析系列之二:jstack生成的Thread Dump日志结构解析
原文地址:http://www.javatang.com 一个典型的thread dump文件主要由一下几个部分组成: 上图将JVM上的线程堆栈信息和线程信息做了详细的拆解. 第一部分:Full th ...
- iOS开发系列--数据存取
概览 在iOS开发中数据存储的方式可以归纳为两类:一类是存储为文件,另一类是存储到数据库.例如前面IOS开发系列-Objective-C之Foundation框架的文章中提到归档.plist文件存储, ...
- iOS开发系列--网络开发
概览 大部分应用程序都或多或少会牵扯到网络开发,例如说新浪微博.微信等,这些应用本身可能采用iOS开发,但是所有的数据支撑都是基于后台网络服务器的.如今,网络编程越来越普遍,孤立的应用通常是没有生命力 ...
随机推荐
- HDU 4847-Wow! Such Doge!(定位)
Wow! Such Doge! Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) ...
- 【Android工具类】用户输入非法内容时的震动与动画提示——EditTextShakeHelper工具类介绍
转载请注明出处:http://blog.csdn.net/zhaokaiqiang1992 当用户在EditText中输入为空或者是数据异常的时候,我们能够使用Toast来提醒用户,除此之外,我们还能 ...
- MCB2300的CTM1050(CAN) - 系列示意图
这一系列示意图由Portel DXP 2004绘. 截图: 文件下载: CTM1050.7z 版权声明:本文博客原创文章,博客,未经同意,不得转载.
- Android手势识别的发展
在播放器.与手势识别.所以,看看今天的我们Android手势识别. 首先,我们需要站在巨人的肩膀上.有些人举了个例子和说明. 第一章: http://www.2cto.com/kf/201110/10 ...
- 系统引导文件之 boot.ini(有很多参数)
Windows NT类的操作系统,也就是Windows NT/2000/XP中,有一个特殊文件,也就是“BOOT.INI”文件,这个文件会很轻松地按照我们的需求设置好多重启动系统. “BOOT.INI ...
- Emgu-WPF学习使用-Rectangle识别
原文:Emgu-WPF学习使用-Rectangle识别 环境:Win8 64位 Vs2015 Emgu 版本:emgucv-windesktop 3.2.0.2682 示例图上部流程:原图->灰 ...
- WPF最大化避免覆盖任务栏
原文:WPF最大化避免覆盖任务栏 WPF当窗体WindowStyle=”None”时,最大化会覆盖掉任务栏.如何解决这个问题呢? 我在Google里面搜到一篇文章,要用到Win32 API,通过让WP ...
- 在 Excel 中如何使用宏示例删除列表中的重复项
概要:在 Microsoft Excel 中,可以创建宏来删除列表中的重复项.也可以创建宏来比较两个列表,并删除第二个列表中那些也出现在第一个(主)列表中的项目.如果您想将两个列表合并在一起,或者如果 ...
- canvas——粒子系统(1)
这个动画在很早之前就见过,当时就没迷住了.最近在学canavs动画,动手实现了一下.代码在这里.展示效果在这里. 这属于粒子系统的一种,粒子系统就是需要管理一堆粒子嘛,动画实现的关键在于,遍历这些粒子 ...
- HTML特殊编码转换
var encoded = ""'&<>¡¢£¤" + "¥¦§¨©ª«¬®" + "¯°±²³´µ¶·" ...