美文网首页
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