美文网首页
iOS底层原理 - 内存管理 之 定时器(一)

iOS底层原理 - 内存管理 之 定时器(一)

作者: hazydream | 来源:发表于2019-03-21 16:42 被阅读0次

面试题引发的思考:

Q: 使用CADisplayLinkNSTimer有什么注意点?

  • 循环引用:
    CADisplayLinkNSTimer会对target产生强引用,如果target又对自身产生强引用,那么就会引发 循环引用。
  • 不准时:
    CADisplayLinkNSTimer依赖于RunLoop,如果RunLoop的任务过于繁重,可能会导致CADisplayLinkNSTimer不准时。

Q: 使用CADisplayLinkNSTimer如何避免循环引用?

  • 使用scheduledTimerWithTimeInterval: repeats: block:方法;
  • 使用代理对象。

Q: 简述NSProxy

  • NSProxy是专门用来做消息转发的类,相比NSObject类来说NSProxy更轻量级。
  • 通过NSProxy可以帮助Objective-C间接的实现多重继承的功能。

1. 实例:

(1) NSTimer使用时产生循环引用

// TODO: -----------------  ViewController类  -----------------
@interface ViewController ()
// 循环引用问题:self对NSTimer对象产生强引用
@property (nonatomic, strong) NSTimer *timer;
@end

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    //  循环引用问题:NSTimer对象对self产生强引用
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerTest) userInfo:nil repeats:YES];
}
- (void)timerTest {
    NSLog(@"%s", __func__);
}
- (void)dealloc {
    NSLog(@"%s", __func__);
    [self.timer invalidate];
}
@end
循环引用

由以上分析可知:

selfNSTimer对象产生强引用,而NSTimer对象又会对self产生强引用,此时会造成循环引用问题,导致无法NSTimer对象无法随着ViewController的释放而释放。


(2) CADisplayLink使用时产生循环引用

// TODO: -----------------  ViewController类  -----------------
@interface ViewController ()
// 循环引用问题:self对CADisplayLink对象产生强引用
@property (nonatomic, strong) CADisplayLink *link;
@end

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    // 保证调用频率和屏幕的刷帧频率一致,60FPS
    //  循环引用问题:CADisplayLink对象对self产生强引用
    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];
}
@end
循环引用

由以上分析可知:

selfCADisplayLink对象产生强引用,而CADisplayLink对象又会对self产生强引用,此时会造成循环引用问题,导致无法CADisplayLink对象无法随着ViewController的释放而释放。


2. 解决方法探究:

(1) 解决方案一:使用block

// TODO: -----------------  ViewController类  -----------------
- (void)viewDidLoad {
    [super viewDidLoad];

    // weakSelf - block内部用的是弱指针,对外面的对象产生弱引用
    // self - block内部用的是强指针,对外面的对象产生强引用
    __weak typeof(self) weakSelf = self;

    // weakSelf只是把地址赋值给target,而target在NSTimer内部是强引用
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target: weakSelf selector:@selector(timerTest) userInfo:nil repeats:YES];
}
解决循环引用方案

由以上分析可知:

直接使用弱指针是无法解决循环引用问题的,因为weakSelf只是把地址赋值给target,而targetNSTimer内部是强引用。而NSTimer是不开源的,无法修改成弱指针。

弱指针是针对block的方案,block内部用的是弱指针,对外面的对象产生弱引用。

所以可以使用以下方法避免循环引用:

// TODO: -----------------  ViewController类  -----------------
- (void)viewDidLoad {
    [super viewDidLoad];
    // 弱指针是针对block的方案
    __weak typeof(self) weakSelf = self;
 
    // NSTimer对象对block产生强引用
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
        // block对self产生弱引用
        [weakSelf timerTest];
    }];
}

(2) 解决方案二:使用代理对象

1> 方案分析
解决循环引用方案

由以上分析可知:

直接使用弱指针是无法解决循环引用问题的,因为weakSelf只是把地址赋值给target,而targetNSTimer内部是强引用。而NSTimer是不开源的,无法修改成弱指针。

如上图使用代理对象:

selfNSTimer对象产生强引用,NSTimer对象对OtherObject对象产生强引用,而OtherObject对象对self产生弱引用,此时会避免循环引用,NSTimer对象会随着ViewController的释放而释放。

a> 对NSTimer使用代码如下:
// TODO: -----------------  ViewController类  -----------------
@interface ViewController ()
@property (nonatomic, strong) NSTimer *timer;
@end

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad]; 
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[MYObjectProxy proxyWithTarget:self] selector:@selector(timerTest) userInfo:nil repeats:YES];
}
- (void)timerTest {
    NSLog(@"%s", __func__);
}
- (void)dealloc {
    NSLog(@"%s", __func__);
    [self.timer invalidate];
}
@end

// TODO: -----------------  MYObjectProxy类  -----------------
@interface MYObjectProxy : NSObject
@property (nonatomic, weak) id target;
+ (instancetype)proxyWithTarget:(id)target;
@end

@implementation MYObjectProxy
+ (instancetype)proxyWithTarget:(id)target {
    MYObjectProxy *proxy = [[MYObjectProxy alloc] init];
    proxy.target = target;
    return proxy;
}
// 消息转发
- (id)forwardingTargetForSelector:(SEL)aSelector {
    // objc_msgSend(self.target, aSelector);
    return self.target;
}
@end
b> 对CADisplayLink使用代码如下:
// TODO: -----------------  ViewController类  -----------------
@interface ViewController ()
// 没有block方案
@property (nonatomic, strong) CADisplayLink *link;
@end

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad]; 
    self.link = [CADisplayLink displayLinkWithTarget:[MYObjectProxy proxyWithTarget:self] selector:@selector(linkTest)];
    [self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
}
- (void)timerTest {
    NSLog(@"%s", __func__);
}
- (void)dealloc {
    NSLog(@"%s", __func__);
    [self.timer invalidate];
}
@end

// TODO: -----------------  MYObjectProxy类  -----------------
@interface MYObjectProxy : NSObject
@property (nonatomic, weak) id target;
+ (instancetype)proxyWithTarget:(id)target;
@end

@implementation MYObjectProxy
+ (instancetype)proxyWithTarget:(id)target {
    MYObjectProxy *proxy = [[MYObjectProxy alloc] init];
    proxy.target = target;
    return proxy;
}
// 消息转发
- (id)forwardingTargetForSelector:(SEL)aSelector {
    // objc_msgSend(self.target, aSelector);
    return self.target;
}
@end
2> 方案优化:使用NSProxy

源码如下:

// NSProxy声明
@interface NSProxy <NSObject> { 
    Class isa;
}
// NSObject声明
@interface NSObject <NSObject> {  
    Class isa;
}

由源码可知:

  • NSProxy、NSObject两者都是基类;
  • 两者区别在与方法调用执行流程不同。

MYObjectProxy继承自NSObject其方法调用执行流程:

objc_msgSend()的执行流程可以分为三个阶段:

  • 消息发送阶段:负责从类及父类的缓存列表及方法列表查找方法;
  • 动态解析阶段:如果消息发送阶段没有找到方法,则会进入动态解析阶段,负责动态的添加方法实现;
  • 消息转发阶段:如果也没有实现动态解析方法,则会进行消息转发阶段,将消息转发给可以处理消息的接收者来处理;
  • 报错:如果也没有实现消息转发方法,会报错unrecognzied selector sent to instance

MYProxy继承自NSProxy其方法调用执行流程:

  • 直接进入消息转发阶段:将消息转发给可以处理消息的接收者来处理;
  • 报错:如果也没有实现消息转发方法,会报错unrecognzied selector sent to instance

由以上结论可知:

  • NSProxy是专门用来做消息转发的类,相比NSObject类来说NSProxy更轻量级。
  • 通过NSProxy可以帮助Objective-C间接的实现多重继承的功能。
NSProxy使用代码如下:
// TODO: -----------------  ViewController类  -----------------
@interface ViewController ()
@property (nonatomic, strong) NSTimer *timer;
@end

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad]; 
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[MYProxy proxyWithTarget:self] selector:@selector(timerTest) userInfo:nil repeats:YES];
}
- (void)timerTest {
    NSLog(@"%s", __func__);
}
- (void)dealloc {
    NSLog(@"%s", __func__);
    [self.timer invalidate];
}
@end

// TODO: -----------------  MYProxy类  -----------------
@interface MYProxy : NSProxy
@property (nonatomic, weak) id target;
+ (instancetype)proxyWithTarget:(id)target;
@end

@implementation MYProxy
+ (instancetype)proxyWithTarget:(id)target {
    // NSProxy对象不需要调用init,没有init方法
    MYProxy *proxy = [MYProxy alloc];
    proxy.target = target;
    return proxy;
}
// 消息转发 - 效率高
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    return [self.target methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
    [invocation invokeWithTarget:self.target];
}
@end

3. 延伸:

Q: 以下代码输出为何为 “0 - 1” ?

// TODO: -----------------  ViewController类  -----------------
@interface ViewController ()
@property (nonatomic, strong) NSTimer *timer;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    ViewController *vc = [[ViewController alloc] init];

    MYObjectProxy *objectProxy = [MYObjectProxy proxyWithTarget:vc];
    MYProxy *proxy = [MYProxy proxyWithTarget:vc];

    NSLog(@"%d - %d",
          [objectProxy isKindOfClass:[ViewController class]],
          [proxy isKindOfClass:[ViewController class]]);
}

objectProxy对象是MYObjectProxy类型,继承自NSObject
MYObjectProxy不是UIViewController类型及其子类,输出结果为“0”。

proxy对象是MYProxy类型,继承自NSProxy
proxy对象调用isKindOfClass方法时进行消息转发,即调用target进行转发;
那么[proxy isKindOfClass:[ViewController class]]相当于[vc isKindOfClass:[ViewController class]]
proxyUIViewController类型及其子类,输出结果为“1”。

相关文章

网友评论

      本文标题:iOS底层原理 - 内存管理 之 定时器(一)

      本文链接:https://www.haomeiwen.com/subject/nvjcvqtx.html