问题:如何确保运行环事件(计时器、用户交互、performSelector等)执行的代码具有相同的“现在”概念?
背景:说事件处理程序需要100 to才能执行,这意味着NSDate日期将返回一个稍微不同的“现在”,这取决于在执行过程中进行调用的时间。如果你在时间上非常不走运,你甚至可能会在两次通话之间出现不同的日期。
这会给依赖当前时间进行各种计算的事情带来问题,因为在执行过程中,这些计算可能会有所不同。
当然,对于特定的事件处理程序,您只需将日期存储在AppDelegate或类似的文件中,或者从入口点开始在每个调用中传递它。
然而,我想要更安全和自动的东西。理想情况下,我想知道当前运行循环什么时候开始处理事件。我可以简单地将NSDate日期替换为并始终得到相同的结果,直到下一个事件被触发为止。
我查看了NSRunLoop的文档,没有太多的运气。我还研究了CADisplayLink的潜在解决方案。两人都没有给出明确的答案。
我觉得这应该是一件普通的事情,而不是需要“解决办法”的事情。我的猜测是,我找错地方或使用错误的搜索词。
代码示例:
UIView *_foo, _fie;
NSDate *_hideDate;
- (void)handleTimer
{
[self checkVisible:_foo];
[self checkVisible:_fie];
}
- (void)checkVisible:(UIView *)view
{
view.hidden = [_hideDate timeIntervalSinceNow] < 0];
}在这种情况下,当_fie仍然可见时,我们可能会隐藏_foo,因为"now“在调用之间发生了很小的变化。
这是一个非常简化的示例,在这个示例中,通过简单地调用NSDate日期并将该实例发送给所有调用者,修复非常简单。这是一般情况下,我感兴趣的,虽然调用链可能是非常深,循环,重新进入,等等。
发布于 2018-03-12 16:59:52
NSRunLoop是CFRunLoop的包装器。CFRunLoop具有NSRunLoop不公开的特性,因此有时必须降到CF级别。
其中一个特性是观察者,当run循环进入不同的阶段时,您可以注册要调用的回调。在这种情况下,您想要的阶段是一个等待后观察者,它是在run循环接收到一个事件(来自源,或由于计时器触发,或者由于一个块被添加到主队列)之后调用的。
让我们将wakeDate属性添加到NSRunLoop中
// NSRunLoop+wakeDate.h
#import <Foundation/Foundation.h>
@interface NSRunLoop (wakeDate)
@property (nonatomic, strong, readonly) NSDate *wakeDate;
@end对于这个类别,我们可以在任何时候请求NSRunLoop提供它的wakeDate属性,例如:
#import "AppDelegate.h"
#import "NSRunLoop+wakeDate.h"
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSTimer *timer = [NSTimer timerWithTimeInterval:0.5 repeats:YES block:^(NSTimer *timer){
NSLog(@"timer: %.6f", NSRunLoop.currentRunLoop.wakeDate.timeIntervalSinceReferenceDate);
}];
[NSRunLoop.currentRunLoop addTimer:timer forMode:NSRunLoopCommonModes];
return YES;
}
@end要实现此属性,我们将创建一个WakeDateRecord类,可以将其作为关联对象附加到run循环:
// NSRunLoop+wakeDate.m
#import "NSRunLoop+wakeDate.h"
#import <objc/runtime.h>
@interface WakeDateRecord: NSObject
@property (nonatomic, strong) NSDate *date;
- (instancetype)initWithRunLoop:(NSRunLoop *)runLoop;
@end
static const void *wakeDateRecordKey = &wakeDateRecordKey;
@implementation NSRunLoop (wakeDate)
- (NSDate *)wakeDate {
WakeDateRecord *record = objc_getAssociatedObject(self, wakeDateRecordKey);
if (record == nil) {
record = [[WakeDateRecord alloc] initWithRunLoop:self];
objc_setAssociatedObject(self, wakeDateRecordKey, record, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return record.date;
}
@endrun循环可以在不同的模式下运行,虽然有少量的公共模式,但理论上可以动态地创建新的模式。如果希望在特定模式下调用观察者,则必须将其注册为该模式。因此,为了确保报告的日期总是正确的,我们不仅要记住日期,还要记住我们记录日期的模式:
@implementation WakeDateRecord {
NSRunLoop *_runLoop;
NSRunLoopMode _dateMode;
NSDate *_date;
CFRunLoopObserverRef _observer;
}要初始化,只需存储run循环并创建观察者:
- (instancetype)initWithRunLoop:(NSRunLoop *)runLoop {
if (self = [super init]) {
_runLoop = runLoop;
_observer = CFRunLoopObserverCreateWithHandler(nil, kCFRunLoopEntry | kCFRunLoopAfterWaiting, true, -2000000, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
[self setDate];
});
}
return self;
}当询问日期时,我们首先检查当前模式是否与记录模式的日期不同。如果是这样的话,那么当run循环在当前模式中唤醒时,日期不会被更新。这意味着观察者没有为当前模式注册,因此我们应该立即注册它并更新日期:
- (NSDate *)date {
NSRunLoopMode mode = _runLoop.currentMode;
if (![_dateMode isEqualToString:mode]) {
// My observer didn't run when the run loop awoke in this mode, so it must not be registered in this mode yet.
NSLog(@"debug: WakeDateRecord registering in mode %@", mode);
CFRunLoopAddObserver(_runLoop.getCFRunLoop, _observer, (__bridge CFRunLoopMode)mode);
[self setDate];
}
return _date;
}更新日期时,还需要更新存储模式:
- (void)setDate {
_date = [NSDate date];
_dateMode = _runLoop.currentMode;
}
@end有关此解决方案的一个重要警告:观察者每次通过run循环触发一次。run循环可以在一次传递过程中为添加到主队列的多个定时器和多个块提供服务。所有的服务定时器或块都会看到相同的wakeDate。
https://stackoverflow.com/questions/49239026
复制相似问题