这一阶段我们主要来讲讲iOS内存管理方面的知识,面试的时候可能大家多多少少都会被问及这方面的问题,那我们就从常见的面试题开讲
- 使用
、CADisplayLink
有什么注意点?NSTimer
- 介绍下内存的几大区域
- 讲一下你对iOS内存管理的理解
-
都帮我们做了什么?ARC
-
的实现原理weak指针
-
对象在什么时机会被调用autorelease
release
- 方法里有局部对象,出了方法后会
吗?立即释放
我们一个一个来,今天我们就先来讲讲第一条使用
CADisplayLink
、
NSTimer
有什么注意点?
主要就是、
CADisplayLink
会对
NSTimer
产生
target
,如果
强引用
又对它们产生
target
,那么就会引发
强引用
循环引用
循环引用大家都知道吧,这样就会导致内存泄漏,我们写代码来看看
首先我们看下
CADisplayLink
,我们新建一个
NavigationController
,然后点击一个按钮
push
到我们
ViewController
,
NavigationController
就一个按钮比较简单我就不写了,下面看下我们的
ViewController
里的代码
@interface ViewController ()
@property (strong, nonatomic) CADisplayLink *link;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 保证调用频率和屏幕的刷帧频率一致,60FPS
self.link = [CADisplayLink displayLinkWithTarget:self selector:@selector(linkTest)];
[self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
}
- (void)linkTest
{
NSLog(@"%s", __func__);
}
- (void)dealloc
{
NSLog(@"%s", __func__);
[self.link invalidate];
}
可能有些同学没用过
CADisplayLink
,
我们先简单了解下,其实也是一个定时器,只不过这个定时器
CADisplayLink
,它是要保证
不用你来设置时间
和
调用频率
,通常来说大概是
屏幕的刷帧频率一致
,当然如果你主线程要是做了很多耗时操作的话也可能就不到60了,也就是说我们的
60FPS
大概一秒钟会调用
linkTest方法
的样子, 那我们运行程序看下控制台的输出也证实了这一点
60次
那接下来我们来看问题
我们看到我们
displayLinkWithTarget
这个类方法会传入一个
self
,这样,
CADisplayLink
对象就会强引用
self
,而
self
强引用了
@property (strong, nonatomic) CADisplayLink *link
,所以就产生了
循环引用
,导致两者
都不会被释放
,可能我们很多同学也都会在
dealloc
方法里调用
[self.link invalidate]
其实是没有用的,我们点下返回键就可以看到,
dealloc
根本不用被调用,那如何解决呢,我们先来看看
Timer
是不是也有这个问题,给Controller添加一个属性
@property (strong, nonatomic) NSTimer *timer;
然后初始化并每隔一秒调用
timerTest
方法
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerTest) userInfo:nil repeats:YES];
- (void)timerTest
{
NSLog(@"%s", __func__);
}
我们运行程序看到,返回的时候
timer
并没有停止,
dealloc
也没有调用说明
timer
也存在这个问题,那接下来我们先来解决
timer
的问题,然后再解决
CADisplayLink
的问题
可能很多同学会说我来个弱指针
__weak typeof(self) weakSelf = self
,把
weakSelf
传入
target
, 不就行了吗,那大家想想管用吗?我们运行下程序发下然并卵返回
timer
依旧跑着,
dealloc
也没调用,那为什么之前的都好使这次不好使了呢,之前是因为我们在
block
里的
循环引用
可以用
weakSelf
来解决,我们现在是没有
block
的,而且,
weakSelf
和
self
其实都是同一个内存地址,我们只是把它当做
参数
来传给
target
这个形参的,所以没用,依旧还是强引用,我们可以这样认为
@interface NSTimer()
@property(strong, nonatomic) id target
@end
NSTimer
内部强引用了
target
跟你外部传入强引用还是弱引用没有半毛钱关系,所以这是不能解决问题的,那怎么办?其实要是
NSTimer
的话有好几种解决方案,我们先来看看第一种方案
1、更换 timer
的初始化方法,用带 block
的方法,这个时候就可以用 weakSelf
了
timer
block
weakSelf
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
[weakSelf timerTest];
}];
我们再运行下程序,我们看到
dealloc
调用了
timer
也停止了,所以问题也解决了
2、那我们再换回第一种方法,是否可以解决呢,答案是肯定的,我们来引入一个OtherObject当target,我们来画个图来理解下
这样,
timer
强引用
OtherObject
,
OtherObject
弱引用
Controller
就这样就行了,那具体如何来做呢
我们新建一个
XXProxy
类
@interface XXProxy : NSObject
+ (instancetype)proxyWithTarget:(id)target;
@property (weak, nonatomic) id target;
@end
@implementation XXProxy
+ (instancetype)proxyWithTarget:(id)target
{
XXProxy *proxy = [[XXProxy alloc] init];
proxy.target = target;
return proxy;
}
然后修改我们的
Controller
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[XXProxy proxyWithTarget:self] selector:@selector(timerTest) userInfo:nil repeats:YES];
这样就解决循环引用了,但是我们发现另一个问题,现在
target
里并没有
timerTest
这个方法,运行肯定会
crash
,当然我们也可以直接在
XXProxy
类里写一个
timerTest
- (void)timerTest
{
[self.target timerTest];
}
但是大家想没想过,
XXProxy
这个可能不止被一个
timer
用,要是有很多
timer
总不至于把所有的方法都写上吧,大家想想有没有什么更好的方法呢?其实,这个时候我们就可以用
消息转发机制
,之后可以给大家详细说说,其实消息转发是有
三个阶段
的
forwardInvocation
、
methodSignatureForSelector
、
forwardingTargetForSelector
,我们现在就直接用第三阶段了直接
- (id)forwardingTargetForSelector:(SEL)aSelector
{
return self.target;
}
这样不管外界调用我什么方法我都直接转发给控制器对应的方法,这样是不是就一劳永逸了
现在我们运行下程序,我们可以看到dealloc是有调用的,所以问题解决
那我们现在再看下CADisplayLink是不是可以用相同的办法解决,我们修改下代码
// 保证调用频率和屏幕的刷帧频率一致,60FPS
self.link = [CADisplayLink displayLinkWithTarget:[XXProxy proxyWithTarget:self] selector:@selector(linkTest)];
[self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
我们运行下程序,看下日志,同样解决问题了
One More Thing
喜欢的朋友可以扫描关注我的公众号(您的支持是我写作的最大动力)