[iOS]定时器NSTimer、CADisplayLink的内存管理

NSTimer、CADisplayLink会对target产生强引用,如果target同时对他们产生强引用,则会发生循环引用。

以NSTimer为例,解决循环引用的问题。

方法1:使用block

复制代码
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    __weak typeof(self) weakself = self;
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
        [weakself func];
    }];
}

- (void)func
{
    NSLog(@"%s",__func__);
}

- (void)dealloc
{
    NSLog(@"%s",__func__);
    [self.timer invalidate];
}
复制代码

方法2:使用NSObject作为中间对象

Proxy1.h

@interface Proxy1 : NSObject
+ (instancetype)initWithTarget:(id)target;
@end
复制代码
Proxy1.m

@interface Proxy1 ()
@property (nonatomic,weak) id target;
@end

@implementation Proxy1

+ (instancetype)initWithTarget:(id)target
{
    Proxy1 *proxy = [[Proxy1 alloc] init];
    proxy.target = target;
    return proxy;
}

- (id)forwardingTargetForSelector:(SEL)aSelector
{
    return self.target;
}

@end
复制代码
复制代码
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[Proxy1 initWithTarget:self] selector:@selector(func) userInfo:nil repeats:YES];
}

- (void)func
{
    NSLog(@"%s",__func__);
}

- (void)dealloc
{
    NSLog(@"%s",__func__);
    [self.timer invalidate];
}
复制代码

方法3:使用NSProxy作为中间对象

Proxy2.h

@interface Proxy2 : NSProxy
+ (instancetype)initWithTarget:(id)target;
@end
复制代码
Proxy2.m

@interface Proxy2 ()
@property (nonatomic,weak) id target;
@end

@implementation Proxy2

+ (instancetype)initWithTarget:(id)target
{
    Proxy2 *proxy = [Proxy2 alloc];
    proxy.target = target;
    return proxy;
}

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

- (void)forwardInvocation:(NSInvocation *)invocation
{
    [invocation invokeWithTarget:self.target];
}

@end
复制代码
复制代码
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[Proxy2 initWithTarget:self] selector:@selector(func) userInfo:nil repeats:YES];
}

- (void)func
{
    NSLog(@"%s",__func__);
}

- (void)dealloc
{
    NSLog(@"%s",__func__);
    [self.timer invalidate];
}
复制代码

方法3的优点:

执行效率高,无需执行父类的方法搜索过程,直接进行消息转发。

关于NSProxy补充:

通过调用isKindOfClass

Proxy1 *proxy1 = [Proxy1 initWithTarget:self];
Proxy2 *proxy2 = [Proxy2 initWithTarget:self];

NSLog(@"%d",[proxy1 isKindOfClass:[ViewController class]]);   // 0
NSLog(@"%d",[proxy2 isKindOfClass:[ViewController class]]);   // 1

proxy1为Proxy1类型,Proxy1继承自NSObject,可以正常处理isKindOfClass方法,所以判断结果为0.

proxy2为Proxy2类型,Proxy2继承自NSProxy,大部分方法会直接进入消息转发阶段,会改为使用target进行调用,所以判断结果为1.

通过观察NSProxy的源码发现,该方法直接进行了消息转发。

复制代码
/**
 * Calls the -forwardInvocation: method to determine if the 'real' object
 * referred to by the proxy is an instance of the specified class.
 * Returns the result.<br />
 * NB. The default operation of -forwardInvocation: is to raise an exception.
 */
- (BOOL) isKindOfClass: (Class)aClass
{
    NSMethodSignature    *sig;
    NSInvocation        *inv;
    BOOL            ret;
    
    sig = [self methodSignatureForSelector: _cmd];
    inv = [NSInvocation invocationWithMethodSignature: sig];
    [inv setSelector: _cmd];
    [inv setArgument: &aClass atIndex: 2];
    [self forwardInvocation: inv];
    [inv getReturnValue: &ret];
    return ret;
}
复制代码
posted @   EverNight  阅读(148)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 因为Apifox不支持离线,我果断选择了Apipost!
· 通过 API 将Deepseek响应流式内容输出到前端
点击右上角即可分享
微信分享提示