对于开发者来说,内存管理几乎是一个永恒的话题。
内存管理对于编写出高效率的 iOS APP
是非常重要的,这是因为iOS
是多任务系统,在同一时刻可能有多个应用程序共享内存,有时为了使某个任务更好地执行,iOS
系统可能会对其它任务分配的内存进行移动,甚至删除。
现在被广泛使用的内存管理机制主要有GC
和RC
两种。GC
:垃圾回收机制,定期查找不再使用的对象,释放对象占用的内存。RC
:引用计数机制,采用引用计数来管理对象的内存,当需要持有一个对象时,使它的引用计数+1,当不需要再持有一个对象的时候,使它的引用计数-1;当一个对象的引用计数为0时,该对象就会被销毁。
Objective-c
支持三种内存管理机制:GC,MRC,ARC
。以前MacOS
开发中是支持GC
的,后面引入ARC
后就弃用了GC
机制。iOS
开发中使用的是RC
机制,从MRC
到现在的ARC
。
引用计数
可以看我之前的一篇关于引用计数。
MRC时代
基本内存管理规则:MRC
下是要严格遵守引用计数内存管理规则。
内存管理模型是基于对象的所有权。任何对象都可以拥有一个或者多个所有者,但是一个对象最少要拥有一个所有者,它才会继续存在。如果对象没有所有者的,它将会被销毁。
四条内存管理策略:
1.自己生成的对象,自己持有alloc/new/copy/mutableCopy
等方法创建的对象,我们自己直接持有,它的RC
初始值为1,我们可以直接使用,不需要使用的时候调用release
释放。
2.非自己生成的对象,自己也能持有。
不通过上面方法创建的对象,它的RC
初始值也为1,但是我们不能直接使用,要先调用retain
,否则可能会crash
。原因是因为这些方法内部是给对象调用了autorelease
方法,所以这些对象是被添加到了自动释放池中了。
两种情况,程序没有手动指定@autoreleasepool
,当runloop
迭代结束时,会自动给释放池中的对象调用release
方法。如果我们使用前不进行retain
,刚好runloop
迭代结束,对象的RC从1变成0,然后就被销毁了.我们去访问一个已经被销毁的对象,程序就会crash
。
程序手动指定了@autoreleasepool
,当时出了其作用域,对象会调用release
方法,然后销毁,这个时候使用对象也会crash
。
3.不再需要自己持有的对象时,释放。
不再需要持有或者使用对象的时候,就调用release
或者autorelease
方法进行释放。rc
变成0时,就会调用dealloc
方法销毁。release
立马rc-1
,autorelease
加入自动释放池,在合适的时候rc-1
,实际上就是延迟释放。
4.非自己持有的对象,无需释放。
自己不是持有者,调用release
的话,会crash
。
实用的内存管理原则
使用访问器方法可以省略大量的retain
和release
推荐使用访问器方法设置属性,一个是可以避免忘记写retain
和release
,第二个是触发kvo
不要在初始化和dealloc中使用访问器方法,原因是在初始化中使用访问器方法,self=[super init]
,首先会调用父类的访问器方法,然后如果子类重写了访问器方法,会去调用子类的访问器方法,而这个时候,子类实际上是没有初始化的,所以会出错。dealloc
实际上是个反向的。就是子类先释放,再去调用父类。就是调用父类的dealloc
时,会调用父类的访问器方法,如果子类重写了该访问器方法,就会去调用子类的访问器方法,但是子类已经释放掉了,也会出错。还有一个原因就是会触发KVO
,在准备释放前,触发了kvo
,即将释放的对象被持有了。就是出现莫名其妙的错误。
推荐使用@autoreleasepool
,虽然mrc
下,可以使用NSautoreleasepool
,但是速度慢很多。
1.写的程序是非UI
框架,比如命令行工具
2.循环创建大量的临时变量。
3.多线程中。比如创建辅助线程。
ARC时代
ARC
时代,内存管理基本都交给编译器来处理了,对于开发者来说是十分友好了。ARC
的编译器有两部分,前端编译器和优化器。前端编译器会对每一个对象插入相应的release
语句。类拥有的对象会在dealloc
中释放。方法内创建的对象,前端编译器会在方法末尾自动插入release
语句销毁它。优化器负责移除多余的retain
和release
操作语句。
但是还是有些地方需要注意的。
新增的几个关键字:__strong
强引用,用来保证对象不会被释放。__weak
弱引用 释放时会置为nil__unsafe_unretained
弱引用 可能不安全,因为释放时不置为nil。悬垂对象。__autoreleasing
对象被注册到autorelease pool
中方法在返回时自动释放。
block使用中出现循环引用
_person1 = [[Person alloc] init];
_person2 = [[Person alloc] init];
_person2.name = @"张三";
__weak __typeof(self) weakSelf = self;
_person1.block = ^{
__typeof(&*weakSelf) strongSelf = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",strongSelf.person2.name);
});
};
使用weakSelf结合strongSelf的情况下,能够避免循环引用,也不会造成提前释放导致block内部代码无效。
在MRC下,应该使用__block。
NSTimer循环引用
使用category处理。
@implementation NSTimer (BlcokTimer)
+ (NSTimer *)bl_scheduledTimerWithTimeInterval:(NSTimeInterval)interval block:(void (^)(void))block repeats:(BOOL)repeats {
return [self scheduledTimerWithTimeInterval:interval target:self selector:@selector(bl_blockSelector:) userInfo:[block copy] repeats:repeats];
}
+ (void)bl_blockSelector:(NSTimer *)timer {
void(^block)(void) = timer.userInfo;
if (block) {
block();
}
}
@end
使用中间件,利用runtime,self依赖timer,time依赖中间件,中间件弱引用self
@interface WeakObject()
@property (weak, nonatomic) id weakObject;
@end
@implementation WeakObject
- (instancetype)initWithWeakObject:(id)obj {
_weakObject = obj;
return self;
}
+ (instancetype)proxyWithWeakObject:(id)obj {
return [[WeakObject alloc] initWithWeakObject:obj];
}
/**
* 消息转发,让_weakObject响应事件
*/
- (id)forwardingTargetForSelector:(SEL)aSelector {
return _weakObject;
}
- (void)forwardInvocation:(NSInvocation *)invocation {
void *null = NULL;
[invocation setReturnValue:&null];
}
- (BOOL)respondsToSelector:(SEL)aSelector {
return [_weakObject respondsToSelector:aSelector];
}
使用method swizzing,利用Method Swizzling,通过替换原有的接口,将循环引用的对象改为弱引用。
//替换类方法——宏
#define ClassMethodSwizzling(func0, func1) \
{\
Method imp = class_getInstanceMethod(object_getClass([self class]), @selector(func0));\
Method myImp = class_getInstanceMethod(object_getClass([self class]), @selector(func1));\
method_exchangeImplementations(imp, myImp);\
}
+ (void)load
{
//替换方法
ClassMethodSwizzling(scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:, fc_scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:);
}
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo
{
typeof(aTarget) __weak weakTarget = aTarget;
//创建类方法的引用,并指向中间函数
NSTimer *timer = [self scheduledTimerWithTimeInterval:interval target:self selector:@selector(fc_blockInvoke:) userInfo:userInfo repeats:yesOrNo];
typeof(NSTimer *) __weak weakTime = timer;
//生成block
timer.fcTimerBlock = ^(void){
if (!weakTarget)
{
return;
}
IMP imp = [weakTarget methodForSelector:aSelector];
void (*func)(id, SEL, id) = (void *)imp;
func(weakTarget, aSelector,weakTime);
};
weakTime = timer;
return timer;
}