CADisplayLink
: 使用频率和屏幕的刷新频率保持一致, 60FPSoop
设置程序的界面结构以下图所示, 其中橙色的界面就是ViewController
ui
ViewController
中有以下代码, ViewController
有一个属性CADisplayLink *displayLink
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, strong) CADisplayLink *displayLink;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayLinkTest)];
[self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
}
- (void)displayLinkTest
{
NSLog(@"%s", __func__);
}
- (void)dealloc
{
NSLog(@"%s", __func__);
[self.displayLink invalidate];
}
@end
复制代码
ViewController
后能够看到控制台不停的打印-dealloc
根本没有执行, 定时器依然在不停的调用ViewController-CADisplayLink
的循环引用, 相似下图Proxy
中代码以下, 使用便利构造器
建立Proxy
对象, 同时存储target
Proxy
不实现任何target
调用的方法, 而是使用消息转发的方式, 将消息转发给target
, 这样不论定时器
调用任何方法, 都能交给target
去执行#import "Proxy.h"
@interface Proxy ()
@property (nonatomic, weak) id target;
@end
@implementation Proxy
+ (instancetype)proxyWithTarget:(id)target
{
Proxy *proxy = [[Proxy alloc] init];
proxy.target = target;
return proxy;
}
- (id)forwardingTargetForSelector:(SEL)aSelector
{
return self.target;
}
@end
复制代码
ViewController
中代码以下, CADisplayLink
绑定[Proxy proxyWithTarget:self]
, 调用-displayLinkTest
方法Proxy
没有实现-displayLinkTest
方法, 此时Proxy
就会经过消息转发, 将displayLinkTest
转交给target
去执行#import "ViewController.h"
#import "Proxy.h"
@interface ViewController ()
@property (nonatomic, strong) CADisplayLink *displayLink;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.displayLink = [CADisplayLink displayLinkWithTarget:[Proxy proxyWithTarget:self] selector:@selector(displayLinkTest)];
[self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
}
- (void)displayLinkTest
{
NSLog(@"%s", __func__);
}
- (void)dealloc
{
NSLog(@"%s", __func__);
[self.displayLink invalidate];
}
@end
复制代码
ViewController
界面后, 能够看到控制台不停的打印, 当点击返回按钮, 退出ViewController
后, 就会调用ViewController
的-dealloc
方法, 中止定时器ViewController-CADisplayLink
的循环引用问题NSTimer
与CADisplayLink
相似, 也会形成循环引用问题#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, strong) NSTimer *timer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
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
复制代码
ViewController
能够看到每一秒打印一次, 退出ViewController
后打印也不会中止CADisplayLink
同样, 使用中间对象Proxy
便可#import "ViewController.h"
#import "Proxy.h"
@interface ViewController ()
@property (nonatomic, strong) NSTimer *timer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[Proxy proxyWithTarget:self] selector:@selector(timerTest) userInfo:nil repeats:YES];
}
- (void)timerTest
{
NSLog(@"%s", __func__);
}
- (void)dealloc
{
NSLog(@"%s", __func__);
[self.timer invalidate];
}
@end
复制代码
ViewController
后控制台持续打印, 退出ViewController
后, 定时器中止NSProxy
是与NSObject
同级别的类, NSProxy
的定义是下面这段代码@interface NSProxy <NSObject> {
Class isa;
}
+ (id)alloc;
+ (id)allocWithZone:(nullable NSZone *)zone NS_AUTOMATED_REFCOUNT_UNAVAILABLE;
+ (Class)class;
- (void)forwardInvocation:(NSInvocation *)invocation;
- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel NS_SWIFT_UNAVAILABLE("NSInvocation and related APIs not available");
- (void)dealloc;
- (void)finalize;
@property (readonly, copy) NSString *description;
@property (readonly, copy) NSString *debugDescription;
+ (BOOL)respondsToSelector:(SEL)aSelector;
- (BOOL)allowsWeakReference NS_UNAVAILABLE;
- (BOOL)retainWeakReference NS_UNAVAILABLE;
// - (id)forwardingTargetForSelector:(SEL)aSelector;
@end
复制代码
NSProxy
没有任何的父类, 与NSObject
同样遵照<NSObject>
协议NSProxy
是用来作消息转发的类, 若是本身没有实现目标方法, 那么就会马上进入消息转发NSProxy
解决定时器内存管理问题BWProxy
继承自NSProxy
, 并实现下列方法@interface BWProxy : NSProxy
@property (nonatomic, weak) id target;
+ (instancetype)proxyWithTarget:(id)target;
@end
@implementation BWProxy
+ (instancetype)proxyWithTarget:(id)target
{
// NSProxy没有init方法, 只须要调用alloc建立对象便可
BWProxy *proxy = [BWProxy alloc];
proxy.target = target;
return proxy;
}
@end
复制代码
ViewController
使用BWProxy
替代上面的Proxy
#import "ViewController.h"
#import "BWProxy.h"
@interface ViewController ()
@property (nonatomic, strong) NSTimer *timer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[BWProxy proxyWithTarget:self] selector:@selector(timerTest) userInfo:nil repeats:YES];
}
- (void)timerTest
{
NSLog(@"%s", __func__);
}
- (void)dealloc
{
NSLog(@"%s", __func__);
[self.timer invalidate];
}
@end
复制代码
ViewController
能够看到, 有下面的报错-[NSProxy methodSignatureForSelector:] called!
, 并非找不到timerTest
方法BWProxy
中加入-methodSignatureForSelector:
和-forwardInvocation:
两个方法, 实现消息转发
来解决崩溃的问题#import <Foundation/Foundation.h>
@interface BWProxy : NSProxy
@property (nonatomic, weak) id target;
+ (instancetype)proxyWithTarget:(id)target;
@end
@implementation BWProxy
+ (instancetype)proxyWithTarget:(id)target
{
// NSProxy没有init方法, 只须要调用alloc建立对象便可
BWProxy *proxy = [BWProxy alloc];
proxy.target = target;
return proxy;
}
- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
return [self.target methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
[invocation invokeWithTarget:self.target];
}
@end
复制代码
ViewController
后, 再次退出, 能够看到NSTimer
中止, ViewController
被释放CADisplayLink
与NSTimer
同样, 这里就再也不赘述Proxy
和BWProxy
, 实现下面的代码Proxy *proxy1 = [Proxy proxyWithTarget:self];
BWProxy *proxy2 = [BWProxy proxyWithTarget:self];
NSLog(@"%d", [proxy1 isKindOfClass:[ViewController class]]);
NSLog(@"%d", [proxy2 isKindOfClass:[ViewController class]]);
复制代码
proxy1
的基类是NSObject
, 因此打印为0
proxy2
其实是进行了消息转发, 将isKindOfClass:
转发给了target
, 也就是ViewController
, 因此打印是1
GUNStep
中也能够看到实现过程NSTimer
依赖于RunLoop
,若是RunLoop
的任务过于繁重,可能会致使NSTimer
不许时GCD
定时器不依赖于RunLoop
, 会更加的准时#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, strong) dispatch_source_t timer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 获取主队列
dispatch_queue_t queue = dispatch_get_main_queue();
// 建立定时器, 在主线程中调用
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// 2秒后执行
NSTimeInterval start = 2.0;
// 执行间隔1秒
NSTimeInterval interval = 1.0;
// 设置定时器
dispatch_source_set_timer(timer,
dispatch_time(DISPATCH_TIME_NOW, start * NSEC_PER_SEC),
interval * NSEC_PER_SEC,
0);
// 设置回调
__weak typeof(self) weakSelf = self;
dispatch_source_set_event_handler(timer, ^{
[weakSelf timerTest];
});
// 启动定时器
dispatch_resume(timer);
self.timer = timer;
}
- (void)timerTest
{
NSLog(@"%s", __func__);
}
- (void)dealloc
{
NSLog(@"%s", __func__);
}
@end
复制代码
ViewController
, 能够看到定时器的打印, 退出ViewController
能够看到-dealloc
被调用, 定时器中止