首页 > 其他分享 >iOS 永久解决NSTimer/CADisplayLink循环引用的问题

iOS 永久解决NSTimer/CADisplayLink循环引用的问题

时间:2023-03-22 14:47:24浏览次数:61  
标签:sel return target CADisplayLink self iOS aSelector NSTimer SEL

在iOS中定时器的循环引用会导致定时器无法正常关闭,页面无法正常释放导致内存泄漏。
正常来讲 我们的vc强引用定时器 定时器强引用vc从而导致引用环无法结束,通过中间人的方式可以解决相互之间引用的问题
让中间人弱引用vc 定时器强引用中间人对象 这样就断开了定时器和vc间的循环

大致代码如下:

@interface WeakTarget : NSObject

+ (instancetype)proxyWithTarget:(id)target;

@end

#import "WeakTarget.h"

@interface WeakTarget ()

@property (weak,nonatomic,readonly) id target;

@end

@implementation WeakTarget

- (instancetype)initWithTarget:(id)target{
    _target = target;
    return self;
}

+ (instancetype)proxyWithTarget:(id)target{
    return [[self alloc] initWithTarget:target];
}

+(BOOL)resolveInstanceMethod:(SEL)sel{
    return YES;
}

+(BOOL)resolveClassMethod:(SEL)sel{
    return YES;
}

-(id)forwardingTargetForSelector:(SEL)aSelector{
    if([self respondsToSelector:aSelector]){
        return self.target;
    }
    return [super forwardingTargetForSelector:aSelector];
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    return [self.target methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)invocation{
    SEL sel = [invocation selector];
    if ([self.target respondsToSelector:sel]) {
        [invocation invokeWithTarget:self.target];
    }
}

- (BOOL)respondsToSelector:(SEL)aSelector{
    return [self.target respondsToSelector:aSelector];
}
@end

使用:这里以NSTimer为例,CADisplayLink同理

  _timer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:[WeakTarget proxyWithTarget:self] selector:@selector(timeInterval) userInfo:nil repeats:YES];
  [[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];
 /// 开启定时器
  [_timer fire];

  /// 注意别忘记在dealloc方法中关闭并销毁定时器
    if(self.timer.isValid){
        [self.timer invalidate];
    }
   _timer = nil;

大致是用到消息转发的机制,利用这个机会再次重温一下消息转发的流程

runtime会为每个类(不是类的实例)添加一个方法列表,该列表通过hash表实现,方便使用时快速查找,缓存中的方法通过hash查找,
消息传递的过程:
在我们调用一个方法的时候,先从缓存中去查找,看缓存中是否有对应方法选择器的实现,如果命中通过函数指针调用方法,完成一次消息的传递
如果没有命中,会通过当前类的isa指针去查找类的方法列表,看是否有同样名称的方法,若果命中,则通过函数指针调用该方法,结束消息的传递
如果当前类的方法类表中仍然没有找到同名方法,则逐级父类的方法列表中查找,通过当前类对象的[super class]指针查找父类的方法列表,如果命中,则返回,如果未命中,则启动消息转发流程
消息转发流程分为3个环节
1、运行时决议是否有动态添加的方法 通过class_addMethod动态添加方法

/// 类方法
-(bool)resolveClassMethod:(SEL)sel
/// 实例方法
-(bool)resolveInstanceMethod:(SEL)sel

消息转发的第一步,通过判断运行时是否需要动态添加方法,如果动态添加方法,通过class_addMethod添加并返回yes,否则走后续流程

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self performSelector:@selector(foo:)];
}

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(foo:)) {
        class_addMethod([self class], sel, (IMP)fooMethod, "v@");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

void fooMethod(id obj, SEL _cmd){
    NSLog(@"Doing foo");
}

2.如果运行时未添加该方法,则走后续流程,判断其他对象是否实现了需要的方法,并返回其他对象

-(id)forwardingTargetForSelector:(SEL)aSelector{
    if([self respondsToSelector:aSelector]){
        return self.target;
    }
    return [super forwardingTargetForSelector:aSelector];
}
- (BOOL)respondsToSelector:(SEL)aSelector{
    return [self.target respondsToSelector:aSelector];
}

如本例中WeakTarget未实现Interval方法,判断target是否实现了interval方法,如果发现target有实现则返回target对象

3、如果这一步未返回需要实现的其他对象,则走最后一步消息转发流程 通过methodSignatureForSelector和forwardInvocation来实现

-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
-(void)forwardInvocation:(NSInvocation *)anInvocation

先通过methodSignatureForSelector获取调用方法的签名,返回NSInvocation对象,可以在该方法中指定某个类来实现某个方法

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    return [self.target methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)invocation{
    SEL sel = [invocation selector];
    if ([self.target respondsToSelector:sel]) {
        [invocation invokeWithTarget:self.target];
    }
}

如果forwardInvocation中未成功返回执行方法,则消息转发失败,报错指定方法未找到

标签:sel,return,target,CADisplayLink,self,iOS,aSelector,NSTimer,SEL
From: https://www.cnblogs.com/qqcc1388/p/17243685.html

相关文章

  • 在Vue中发起axios请求成功,却被catch捕捉返回Network Error
    前端发起请求成功,后台接收处理返回,却被axios的catch捕获,没有走then函数。  最后添加了headers配置成功解决,如上,附上axios接口配置中文文档:axios中文文档|axios中文网......
  • -bash: iostat: 未找到命令
    运维排查哪个进程比较消耗io,发现终端没有iostat,下面跟着我一起进行安装把。一、在线安装[root@ops1~]#iostat#发现centos未安装iostat-bash:iostat:未......
  • iostat命令安装及详解
    iostat是I/Ostatistics(输入/输出统计)的缩写,iostat工具将对系统的磁盘操作活动进行监视。它的特点是汇报磁盘活动统计情况,同时也会汇报出CPU使用情况。同vmstat一样,iostat......
  • Windows系统下生成IOS证书
    我使用ApiCloud开发APP,开发后需要生成IOS的证书才能在项目开发控制台中进行编译,于是我在网上大海捞针似的寻找办法。官方文档提供了使用苹果系统下生成IOS证书的步骤,对于......
  • axios、Fetch
    axios 1、axios是什么axios是一个基于Promise的HTTP库,可以用在浏览器和node.js中第三方Ajax库http://www.axios-js.com/zh-cn/docs/2、axios的基本用法引入axioscon......
  • iOS之Runtime - 常用的API
    Runtime常用的API1-类②成员变量③属性④方法⑤其他2-如何使用①新建Person和Animal两个类,如下②代码示例1#import<Foundation/Found......
  • 【Unity3D】AudioSource组件
    1简介​1)AudioSource与AudioListener简介​AudioSource(音频源)组件用于控制播放AudioClip(音频片段),能够控制2D和3D(距离越远,声音越小)声音播放,它一般挂在产......
  • iOS借用WKWebView将svg转码为png
    #import"WKSVGConvert.h"#import<WebKit/WebKit.h>@interfaceWKSVGConvert()<WKNavigationDelegate>@property(nonatomic,strong)WKWebView*webView;@prop......
  • Vue全局挂载axios
    前言在vue开发过程中我们有时会把需要的一些模块挂载的全局,以便在各个组件或页面中使用。vue2与vue3中全局挂载是有一些不同的。一、全局挂载示例:pandas是基于NumPy的一......
  • Vue axios简易封装
    1.安装axiosnpminstallaxios-g 2.创建utils文件夹,新建文件request.js对axios进行封装3.设置请求超时通过axios.defaults.timeout设置默认的请求超时时间。例......