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
被調用, 定時器中止