一、什么是内存泄漏,在 Obj-C 中如何检测内存泄漏?
“内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。”
泄露的内存主要有以下两种:
Leak Memory 这种是忘记 Release 操作所泄露的内存。
Abandon Memory 这种是循环引用,无法释放掉的内存。
检测内存泄漏的方式有:
Memory Leaks、
MLeaksFinder腾讯阅读团队出品。
内存泄漏的几种原因:
1、Block循环引用
2、代理(delegate)循环引用,关键字return和其他引用计数器加1的操作。
3、NSTimer 循环引用(类实例释放之前手动调用一下NSTimer的invalidate方法,并将NSTimer = nil)
4、AFNetWorking请求队列管理者AFHTTPSessionManager单例创建形式,防止内存泄漏
5、非OC对象内存处理
6、地图类处理
7、大次数循环内存暴涨问题
二、循环引用
1、Block循环引用
原因:一个对象中强引用了block,并且在block中又使用了该对象,就会发生循环引用。
解决方式:避免产生循环引用,通常是将 strong 引用改为 weak 引用。 比如在修饰属性时用 weak。
在block内调用对象方法使,使用其弱引用可以使用两个宏。
#define WS(weakSelf) __weak __typeof(&*self)weakSelf = self; //弱引用
#define ST(strongSelf) __strong __typeof(&*self)strongSelf = weakSelf; //使用这个需要先声明弱引用,强引用目的:为了防止对象提前释放。
备注:
还可以使用__block 来修饰变量,在 MRC 下,__block 不会增加其引用计数,避免了循环引用。在 ARC 下,__block 修饰对象会被强引用,无法避免循环引用,需要手动解除。
什么时候在 block 里面用 self,不需要使用 weak self?
当 block 本身不被 self 持有,而被别的对象持有,同时不产生循环引用的时候,就不需要使用 weak self 了。最常见的代码就是 UIView 的动画代码,我们在使用 UIView 的 animateWithDuration:animations 方法 做动画的时候,并不需要使用 weak self,因为引用持有关系是:
UIView 的某个负责动画的对象持有了 block
block 持有了 self
因为 self 并不持有 block,所以就没有循环引用产生,因为就不需要使用 weak self 了。
[UIView animateWithDuration:0.2 animations:^{
self.alpha = 1;
}];
2、代理(delegate)循环引用
原因:代理(delegate)循环引用是 iOS 中开发中比较常遇到的循环引用。
解决方式:一般在声明 delegate 的时候都要使用弱引用 weak,或者assign,MRC 的话只能用 assign,在 ARC 的情况下最好使用 weak, 因为 weak 修饰的变量在释放后自动指向 nil,防止野指针存在。
3、NSTimer 循环引用
原因:在控制器内,创建 NSTimer 作为其属性,由于定时器创建后也会强引用该控制器对象,那么该对象和定时器就相互循环引用了。
解决方式:这里我们可以使用手动断开循环引用, 如果是不重复定时器,在回调方法里将定时器 invalidate 并置为 nil 即可。 如果是重复定时器,在合适的位置将其 invalidate 并置为 nil 即可
4、AFNetWorking的使用我们通常会对通用参数、网址环境切换、网络状态监测、请求错误信息等进行封装。在封装网络请求类时需注意的是需要将请求队列管理者AFHTTPSessionManager声明为单例创建形式。
5、非OC对象内存处理
一些非OC对象,使用完毕后其内存仍需要我们手动释放。
例子,比如常用的滤镜操作调节图片亮度。
代码中的CGImageRef类型变量非OC对象,其需要手动执行释放操作CGImageRelease(ref),否则会造成大量的内存泄漏导致程序崩溃。其他的对于CoreFoundation框架下的某些对象或变量需要手动释放、C语言代码中的malloc等需要对应free等都需要注意。
6、地图类处理
地图是比较耗费App内存的,因此在根据文档实现某地图相关功能的同时,我们需要注意内存的正确释放,大体需要注意的有需在使用完毕时将地图、代理等滞空为nil,注意地图中标注(大头针)的复用,并且在使用完毕时清空标注数组等。
(void)clearMapView{
self.mapView = nil;
self.mapView.delegate =nil;
self.mapView.showsUserLocation = NO;
[self.mapView removeAnnotations:self.annotations];
[self.mapView removeOverlays:self.overlays];
[self.mapView setCompassImage:nil];
}
7、大次数循环内存暴涨问题
//原因:该循环内产生大量的临时对象,直至循环结束才释放,可能导致内存泄漏
for(int i = 0; i < 100000; i++) {
NSString *string = @"Abc";
string = [string lowercaseString];
string = [string stringByAppendingString:@"xyz"];
NSLog(@"%@", string);
}
//解决方法:循环中创建自己的autoReleasePool,及时释放占用内存大的临时变量,减少内存占用峰值。
for(int i = 0; i < 100000; i++) {
@autoreleasepool {
NSString *string = @"Abc";
string = [string lowercaseString];
string = [string stringByAppendingString:@"xyz"];
NSLog(@"%@", string);
}
}
三、说一下什么是 悬垂指针?什么是 野指针?
悬垂指针:指针指向的内存已经被释放了,但是指针还存在,这就是一个 悬垂指针 或者说 迷途指针。
野指针:没有进行初始化的指针,其实都是 野指针。
四、关键字
Strong 修饰符表示指向并持有该对象,其修饰对象的引用计数会加 1。该对象只要引用计数不为 0 就不会 被销毁。当然可以通过将变量强制赋值 nil 来进行销毁。
weak 修饰符指向但是并不持有该对象,引用计数也不会加 1。在 Runtime 中对该属性进行了相关操作,无需处理,可以自动销毁。weak 用来修饰对象,多用于避免循环引用的地方。weak 不可以修饰基本数据类型。
assign 主要用于修饰基本数据类型,存储在栈中,内存不用程序员管理。assign 是可以修饰对象的,但是会出现问题。
copy 关键字和 strong 类似,copy 多用于修饰有可变类型的不可变对象 NSString,NSArray,NSDictionary 上。
__unsafe_unretain 类似于 weak ,但是当对象被释放后,指针已然保存着之前的地址,被释放后的地址变 为 僵尸对象,访问被释放的地址就会出问题,所以说他是不安全的。
copy分深copy和浅copy。浅copy:对象指针的复制,目标对象指针和原对象指针指向同一块内存空间,引用计数增加。深copy:对象内容的复制,开辟一块新的内存空间,可变的对象的copy和mutableCopy都是深拷贝,不可变对象的copy是浅拷贝,mutableCopy是深拷贝,copy方法返回的都是不可变对象。
但是,对于集合对象的内容复制仅仅是对对象本身,但是对象的里面的元素还是指针复制。要想复制整个 集合对象,就要用集合深复制的方法,有两种: (1)使用 initWithArray:copyItems:方法,将第二个参数设置为 YES 即可。
NSDictionary shallowCopyDict= [[NSDictionary alloc] initWithDictionary:dic copyItems:YES];
(2)将集合对象进行归档(archive)然后解归档(unarchive)。
NSArray *trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:oldArray]];