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开发系列之内存泄漏分析(上)的更多相关文章

  1. ios开发系列之内存泄漏分析(下)

    接上篇,本篇主要讲解通知和 KVO 不移除观察者.block 循环引用 .NSThread 和 RunLoop一起使用造成的内存泄漏. 1.通知造成的内存泄漏 1.1.ios9 以后,一般的通知,都不 ...

  2. iOS开发 如何检查内存泄漏

    本文转载至 http://mobile.51cto.com/iphone-423391.htm 在开发的时候内存泄漏是不可避免的,但是也是我们需要尽量减少的,因为内存泄漏可能会很大程度的影响程序的稳定 ...

  3. iOS开发系列之性能优化(上)

    本篇主要记录一下我对界面优化上的一些探索.关于时间优化的探索将会在中篇里进行介绍.下篇将主要介绍一些耗电优化.安装包瘦身的探索. ### 1.卡顿原理 要了解卡顿原理,需要对帧缓冲区.垂直同步.CPU ...

  4. iOS开发系列-打印内存地址

    打印内存地址 基本数据类型 定义一个基本数据类型,会根据变量类型分配对应的内存空间.比如定义一个int类型的变量a. int a = 10; 内存如下 输入变量a在内存中内存地址 NSLog(@&qu ...

  5. iOS开发系列之app的一天

    本文主要讲述我对 iOS 开发的一些理解,希望能通过 app 从启动到退出,将一些的知识整合起来,形成一条知识链,目前涉及到的知识点有 runloop.runtime.文件存储.界面布局.离线推送.内 ...

  6. Java内存泄漏分析系列之五:常见的Thread Dump日志案例分析

    原文地址:http://www.javatang.com 症状及解决方案 下面列出几种常见的症状即对应的解决方案: CPU占用率很高,响应很慢 按照<Java内存泄漏分析系列之一:使用jstac ...

  7. Java内存泄漏分析系列之二:jstack生成的Thread Dump日志结构解析

    原文地址:http://www.javatang.com 一个典型的thread dump文件主要由一下几个部分组成: 上图将JVM上的线程堆栈信息和线程信息做了详细的拆解. 第一部分:Full th ...

  8. iOS开发系列--数据存取

    概览 在iOS开发中数据存储的方式可以归纳为两类:一类是存储为文件,另一类是存储到数据库.例如前面IOS开发系列-Objective-C之Foundation框架的文章中提到归档.plist文件存储, ...

  9. iOS开发系列--网络开发

    概览 大部分应用程序都或多或少会牵扯到网络开发,例如说新浪微博.微信等,这些应用本身可能采用iOS开发,但是所有的数据支撑都是基于后台网络服务器的.如今,网络编程越来越普遍,孤立的应用通常是没有生命力 ...

随机推荐

  1. 在嵌入式程序中QT去掉鼠标指针

    在像arm的QT编程当中,一般都是使用触摸来操作,当是我们运行程序的时候会发现总是有个鼠标箭头在那里,下面介绍种方法将其给去掉.这样就漂亮多了.在main()函数加入 #include <QWS ...

  2. 探索jquery方法中empty,remove与detach的区别

    最近一直疑惑此三种方法的具体区别在于何处,随即想弄明白其具体的区别,看了一些说明,也依照官方文档,终于把这三个方法弄明白了,果然功夫不负有心人,继续努力. 上正文,先简单介绍下这三种方法 .empty ...

  3. 深入python3 (Dive Into Python 3) 在线阅读与下载

    在线阅读:http://book.doucube.com/diveintopython3/  中文版 下载地址:https://github.com/downloads/diveintomark/di ...

  4. 安德鲁斯Toast它们的定义和防止重复显示器

    Toast安卓系统,当用户错误或功能运行完成,提示,要求用户,它不集中,并且将在一定时间内消失.然而,在用户继续误(如登录,password错)当次,将有多个Toast创建.系统会把这些toast放进 ...

  5. ASP .NET My97DatePicker

    My97DatePicker http://jingyan.baidu.com/article/e6c8503c7244bae54f1a18c7.html <input type="t ...

  6. XF 主从页面

    using System; using Xamarin.Forms; using Xamarin.Forms.Xaml; [assembly: XamlCompilation (XamlCompila ...

  7. XF Grid使用-两行两列-跟WPF不同

    <?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http:/ ...

  8. 文字滚屏控件(SliderPanel)

    http://www.delphifans.com/infoview/Article_629.html 日期:2005年9月6日 作者:arhaha {==================== 满天星 ...

  9. 展讯通信:文章"紫光收购后展讯困难重重”失实(展讯的成就确实很高)

    6月22日上午消息,展讯通信官方微信对自媒体文章<五大危机缠身,紫光收购后展讯困难重重>作出声明,称,其中内容严重失实,对公司造成了不良影响,并表示,将坚决采取法律手段维护自身的合法权益. ...

  10. html send mail

    <html> <body> <script> var formattedBody = "FirstLine \n Second Line \n Third ...