内存优化实践-2019
简介
项目开发完,提交测试之后,解bug之余,看看内存相关的内容。
静态检查
这个推荐做一下,非常简单。Product =》Analyse。
这里可以检查出一些低级错误,比如viewWillAppear缺少super等。一般情况下,这里查出的问题最好都能解决一下。
Swift
工程里用到了一个Swift写的第三方库,在用Instrument的时候发现跑不起来。输出的信息有This copy of libswiftCore.dylib requires an OS version prior to 12.2.0这样的一句话。解决方法是,在Build Setting =》Runpath Search Path标签下添加/usr/lib/swift
https://stackoverflow.com/questions/55361057/this-copy-of-libswiftcore-dylib-requires-an-os-version-prior-to-12-2-0
image.png
NSURLSession
工程中直接使用了NSURLSession进行网络传输。由于NSURLSession对于其delegate是强引用,所以容易造成引用循环。需要调用其invalidateAndCancel或者finishTasksAndInvalidate方法,进行释放。
解决方案,一种是全局使用一个NSURLSession进行网络传输。
NSURLSession时需要注意一个内存泄漏问题
https://www.cnblogs.com/lys-iOS-study/p/5684454.html
另外一种方法就是在网络完成之后,主动释放。
关于NSURLSession内存泄露解决方案
当然,这个只有在自己写网络传输的时候才需要注意,如果用AFNetworking之类的第三方库,这些问题已经考虑到了,不需要再操心。
Block
block在Object-C中比较普遍。如果block中包含self,很容易造成引用循环。当然,只是“很容易”,而不是"一定"
这个就是会造成引用循环的例子,需要处理
kWeakSelf(self)
// 左边的关闭按钮
self.bankSelectView.leftButtonBlock = ^{
kStrongSelf(self)
self.viewIndex = KJTPayViewIndexPay;
};
这个其实是不会造成引用循环的。不过处理一下,也没什么关系。
// 从网络取数据
- (void)fetchData {
kWeakSelf(self)
[KJTRequestApi getMainPageInfo:^(KJTMainPageInfoModel * _Nonnull pageInfo) {
kStrongSelf(self)
self.errorView.hidden = YES;
} failureBlock:^(NSString * _Nullable message) {
kStrongSelf(self)
self.errorView.hidden = NO;
}];
}
所以,简单起见,只要block里面包含了self,就用weak self ; strong self对处理一下,反正也没有什么副作用。
RACObserve
这个可以用来监听属性值的改变,然后在随后的subscribeNext中进行相应。比如下面的例子
[RACObserve(self.viewModel, title) subscribeNext:^(NSString * title) {
self.title = title;
}];
由于RACObserve宏隐含了对self的引用,所以,上面的代码其实已经造成了循环引用。
#define RACObserve(TARGET, KEYPATH) \\
({ \\
_Pragma("clang diagnostic push") \\
_Pragma("clang diagnostic ignored \\"-Wreceiver-is-weak\\"") \\
__weak id target_ = (TARGET); \\
[target_ rac_valuesForKeyPath:@keypath(TARGET, KEYPATH) observer:self]; \\
_Pragma("clang diagnostic pop") \\
})
所以,这里要打破引用循环,可以这样:
@weakify(self);
[RACObserve(self.viewModel, title) subscribeNext:^(NSString * title) {
@strongify(self);
self.title = title;
}];
更进一步: 在有subscribeNext的地方都用上weak self,比如像下面这样的代码:
@weakify(self);
[self.viewModel.titleSignal subscribeNext:^(NSString * title) {
@strongify(self);
self.title = title;
}];
RACSubject
RACSubject被称为热信号,这个难以理解。我更愿意把它类比为一种消息发送和监听处理的一种比较方便的使用方式
image.png
这个用起来还是比较方便的,特别是网络访问,只要创建成功,失败两个不同的RACSubject就可以了。
不过要注意的是,如果忘了[subject sendCompleted];这句,很可能会带来内存泄漏。所以,为了保险起见,[subject sendNext:@1];都跟上这么一句话,就好多了。详细信息可以参考下面这篇文章:
嵌套的block
在项目中,还遇到一种情况:首先是接口1的网络访问,这里有一个异步block;然后,弹出一个对话框,等待用户输入,这里有一个异步block;最后,调用接口2的网络接口,这里有一个异步block。这种异步block嵌套多次的情况,断点调试,最后能出来,不过要等好久。所以,这种时候,也加上weak self,出来的速度就快多了。
@weakify(self);
[Newwork sucess:^(void) {
@strongify(self);
[InputDialog sucess:^(NSString * title) {
self.title = title;
[newwork sucess:^(void) {
NSLog(@"done");
}]
}];
}];
dealloc
在基类控制器中添加如下代码:
- (void)dealloc {
NSLog(@"%@退出了。",NSStringFromClass([self class]));
}
其实,产品运行起来后,都是一个Controller在跳转,其他的类,比如view,button之类的,并不能独立存在。所以,一般来说,只要Controller能正常退出,就不会有内存泄漏。这是检测内存是否泄漏的一个比较简单的方法。
单例
网上虽然有关于单例的消除相关文章,但是,实际执行下来,发现dealloc并不能真正执行。比如:
+ (void)tearDown{
sInstance=nil;
onceToken=0l;
}
所以,单例并不能真正执行。
不过,既然是单例,常驻内存也是可以的。并且,只有一个实例,所以,并不会造成内存泄漏。
不过,有一种情况需要注意,如果把Controller赋值给某个单例强引用属性,那么这个Controller就退不出来了。不过,这种情况内存占用是有限的,并不会造成内存泄漏。下次再进的话,Controller会被替换,不会叠加,占用的内存是有限的。
解决这个问题的方法是单例在把Controller作为属性的时候,修饰符用weak,不要用strong就可以了。
// 输入Controller
@property (weak, nonatomic) UIViewController *inputVc;
Leaks
Xcode自带的Leaks一般被认为是查内存泄漏的好工具。以前也用过,虽然不熟练,不过按照网上的介绍文章,还是能够找出几个内存泄漏的地方的。当时,现在的10.2.1版本的XCode,Leaks几乎没什么用。工程跑起来,就停止首页不动,也能报出很多的红叉,并且一会儿就自动退出了,说是遇到了error,但是却找不到有效的错误信息。这让人感觉到,苹果是越来越差,越来越没前途。XCode改得越来越难用。
image.png
以前虽然感觉也很差,不过至少还有点作用,反正用得也不多,凑合着也行。现在的,真是太差了。库克还真是个蠢货。
网上的文章也很多,不过几乎没有实用的,这实在是Leaks这工具本身做得实在太差,太难用了。
IOS-Instrument-Leaks
iOS Instruments之Leaks
iOS性能优化之内存管理:Analyze、Leaks、Allocations的使用和案例代码
聚焦ViewController
ViewController能够退出,那么内存泄漏的问题就不大。
如果ViewController作为其他成员的属性,那么就应该用weak修饰。特别是用子ViewController的时候,在父ViewController应该作为weak类型的属性成员存在。因为父子ViewController难免要相互引用。
// child controller
@property (weak, nonatomic) KJTPasswordLoginChildViewController *passwordController;
@property (weak, nonatomic) KJTCodeLoginChildViewController *codeController;
腾讯基于Facebook的库,二次开发了一个内存泄漏检测的工具,非常好用,强烈推荐
MLeaksFinder
iOS开发中内存泄漏检测工具--MLeaksFinder
备注:内存检测工具,就不要加入正式版本中了。可以拉一个分支,加入。用的时候,这个分支只运行,不修改。修改都在开发分支中进行,开发完后,再和到这个检测分支,跑一下。如果发现内存泄漏,就在开发分支上改,改完后再把改动合过来。
image.png
如果有Controller释放不了,会弹框提示,然后再去查找代码,就相对容易点了。这个和在Controller的dealloc函数中打log的功能是一样的,不过这个更直观。推荐使用。