Timer的模拟使用
计时器一个很常用的场景就是,打开一个页面,页面中有一个banner使用计时器来自动翻页。在退出页面时,我们希望退出页面时,这个计时器能够停止工作,并且页面对象能够被销毁
TimerObj
// TimerObj.h
@interface TimerObj : NSObject
@property (nonatomic, strong, nullable) NSTimer *timer1;
+ (TimerObj *)object;
- (void)printLog;
- (void)printFinish;
- (void)beginLog;
- (void)beginLogWeak;
@end
@implementation TimerObj
+ (TimerObj *)object{
return [[TimerObj alloc] init];
}
#pragma mark - log
- (void)printLog{
static int idx = 0;
NSLog(@"msg: %d", ++idx);
}
- (void)printFinish{
NSLog(@"msg finish!!");
}
- (void)dealloc {
[self.timer1 invalidate];
self.timer1 = nil;
NSLog(@"%s", __func__);
}
@end
Test
// TimerObj
- (void)beginLog{
if (!self.timer1) {
self.timer1 = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(printLog) userInfo:nil repeats:YES];
}
[self.timer1 fire];
}
void classTimerCommon () {
@autoreleasepool {
TimerObj *obj = [TimerObj object];
[obj beginLog];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 4*NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[obj printFinish];
});
}
}
我们创建了
TimerObj
,调用- beginLog
方法模拟view展示。同时模拟4秒后退出界面
- 日志输出
Hello, World!
msg: 1
msg: 2
msg: 3
msg: 4
msg: 5
msg finish!!
msg: 6
msg: 7
msg: 8
- 结论
- 计时器并没有停止,
obj
也没有被销毁
- 计时器并没有停止,
可能尝试使用weakSelf来结局问题,单理想很美满,显示却很惨
// TimerObj
- (void)beginLogWeak{
if (!self.timer1) {
__weak typeof(self) weakSelf = self;
self.timer1 = [NSTimer scheduledTimerWithTimeInterval:1 target:weakSelf selector:@selector(printLog) userInfo:nil repeats:YES];
}
[self.timer1 fire];
}
void classTimerWeakSelf () {
@autoreleasepool {
TimerObj *obj = [TimerObj object];
[obj beginLogWeak];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 4*NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[obj printFinish];
});
}
}
- 日志输出
Hello, World!
msg: 1
msg: 2
msg: 3
msg: 4
msg: 5
msg finish!!
msg: 6
msg: 7
使用一个对象来中转能够解决,但是却不那么优雅
// TimerObjWeak
@interface TimerObjWeak : NSObject
@property (nonatomic, weak) TimerObj *weakObj;
@end
@implementation TimerObjWeak
- (void)printOtherLog{
[self.weakObj printLog];
}
- (void)dealloc {
NSLog(@"%s", __func__);
}
@end
// TimerObj
- (void)beginLogOtherWeak{
if (!self.timer1) {
TimerObjWeak *otherObj = [[TimerObjWeak alloc] init];
otherObj.weakObj = self;
self.timer1 = [NSTimer scheduledTimerWithTimeInterval:1 target:otherObj selector:@selector(printOtherLog) userInfo:nil repeats:YES];
}
[self.timer1 fire];
}
void classTimerWeakOther () {
@autoreleasepool {
TimerObj *obj = [TimerObj object];
[obj beginLogOtherWeak];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 4*NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[obj printFinish];
});
}
}
- 日志输出
Hello, World!
msg: 1
msg: 2
msg: 3
msg: 4
msg: 5
msg finish!!
-[TimerObjWeak dealloc]
-[TimerObj dealloc]
- 结论
- 两个对象都进行了释放
使用另一个对象来解决
timer
循环引用是可行的,但是不那么优雅。它引入了一个新的方法- printOtherLog
。如果有多个类似TimerObj
这样的对象,简直是可怕。使用NSProxy能非常优雅的解决这个问题
NSProxy
NSProxy
很强大,这里只举例来解决Timer循环引用
- TimerProxy
@interface TimerProxy: NSProxy
@property(nonatomic,weak)id target;
@end
@implementation TimerProxy
-(instancetype)initWithTarget:(id)target{
self.target = target;
return self;
}
+(instancetype)proxyWithTarget:(id)target{
return [[self alloc] initWithTarget:target];
}
-(NSMethodSignature *)methodSignatureForSelector:(SEL)sel{
// 在具体的子类中重写此方法,为给定的选择器和代理对象所代表的类返回正确的对象
return [self.target methodSignatureForSelector:sel];
}
-(void)forwardInvocation:(NSInvocation *)invocation{
// 将给定的调用传递给代理表示的真实对象。
SEL sel = invocation.selector;
if ([self.target respondsToSelector:sel]) {
[invocation invokeWithTarget:self.target];
}
}
@end
//
- (void)beginLogProxy{
if (!self.timer1) {
self.timer1 = [NSTimer scheduledTimerWithTimeInterval:1 target:[TimerProxy proxyWithTarget:self] selector:@selector(printLog) userInfo:nil repeats:YES];
}
[self.timer1 fire];
}
void classTimerProxy () {
@autoreleasepool {
TimerObj *obj = [TimerObj object];
[obj beginLogProxy];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 4*NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[obj printFinish];
});
}
}
- 日志输出
Hello, World!
msg: 1
msg: 2
msg: 3
msg: 4
msg: 5
msg finish!!
-[TimerObj dealloc]
Timer的模式
Apple的解释
-
NSRunLoopCommonModes
Objects added to a run loop using this value as the mode are monitored by all run loop modes that have been declared as a member of the set of “common" modes; see the description of
CFRunLoopAddCommonMode
-
NSDefaultRunLoopMode
The mode to deal with input sources other than
NSConnection
-
NSEventTrackingRunLoopMode
A run loop should be set to this mode when tracking events modally, such as a mouse-dragging loop.
-
NSModalPanelRunLoopMode
A run loop should be set to this mode when waiting for input from a modal panel, such as
NSSavePanel
orNSOpenPanel
. -
UITrackingRunLoopMode
The mode set while tracking in controls takes place.