当前事件的时间戳/ NSDate在UIKit中启动

克里斯托弗·奥斯特兰德

问题:如何确保由于运行循环事件(计时器,用户交互,performSelector等)而执行的代码具有“现在”的相同概念?

背景:假设事件处理程序需要100毫秒才能执行,这意味着[NSDate date]将返回稍有不同的“现在”,具体取决于执行调用的时间。如果您对计时不太满意,您甚至可能在通话之间有不同的日期。

这会给依赖当前时间进行各种计算的事情带来问题,因为这些计算在执行期间可能会有所不同。

当然,对于特定的事件处理程序,您可以只将日期存储在AppDelegate或类似的文件中,或者在从入口点开始的每次调用中传递日期。

但是,我想要更安全,更自动的工具。理想情况下,我想知道当前运行循环何时开始处理事件。我可以简单地将[NSDate date]替换为,并始终获得相同的结果,直到触发下一个事件为止。

我运气不好就看了NSRunLoop的文档。我还研究了CADisplayLink的潜在解决方法。两者均未提供明确答案。

感觉这应该是很常见的事情,而不是需要“解决方法”的事情。我的猜测是我在错误的位置寻找或使用错误的搜索词。

代码示例:

UIView *_foo, _fie;
NSDate *_hideDate;

- (void)handleTimer
  {
    [self checkVisible:_foo];
    [self checkVisible:_fie];
  }

- (void)checkVisible:(UIView *)view
  {
    view.hidden = [_hideDate timeIntervalSinceNow] < 0];
  }

在这种情况下,当_foo仍可见时,由于_fie被隐藏了,因为调用之间“ now”的变化很小。

这是一个非常简化的示例,其中通过简单地调用[NSDate date]并将该实例发送给所有调用者就可以轻松解决此问题。这是我感兴趣的一般情况,尽管调用链可能非常深,周期性,可重入等。

抢马约夫

NSRunLoop是的包装器CFRunLoopCFRunLoop具有NSRunLoop未公开的功能,因此有时您必须降至CF级别。

这样的功能之一就是观察者,它是您可以注册的回调,当运行循环进入不同的阶段时可以调用该回调。在这种情况下,您想要的阶段是一个等待后的观察者,它在运行循环接收到事件(从源,由于计时器触发或由于将块添加到主队列)而收到事件后调用。

让我们添加一个wakeDate属性到NSRunLoop

// NSRunLoop+wakeDate.h

#import <Foundation/Foundation.h>

@interface NSRunLoop (wakeDate)

@property (nonatomic, strong, readonly) NSDate *wakeDate;

@end

使用此类别,我们可以在需要时随时要求NSRunLoopwakeDate属性,例如:

#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类,可以将其作为关联对象附加到运行循环:

// 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;
}

@end

运行循环可以在不同的模式下运行,尽管有少量的通用模式,但理论上可以动态创建新的模式。如果希望在特定模式下调用观察者,则必须为该模式注册观察者。因此,为确保报告的日期始终正确,我们不仅要记住日期,还要记住记录日期的方式:

@implementation WakeDateRecord {
    NSRunLoop *_runLoop;
    NSRunLoopMode _dateMode;
    NSDate *_date;
    CFRunLoopObserverRef _observer;
}

要初始化,我们只需存储运行循环并创建观察者:

- (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;
}

当要求输入日期时,我们首先检查当前模式是否与记录模式的日期不同。如果是这样,那么当运行循环在当前模式下唤醒时,日期不会更新。这意味着没有为当前模式注册观察者,因此我们应该立即注册并更新日期:

- (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

有关此解决方案的重要警告:观察者每次通过运行循环时都会触发一次。运行循环可以为单个计时器中添加到主队列中的多个计时器和多个块提供服务。所有服务的计时器或块将显示相同的内容wakeDate

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

从Javascript中的当前事件获取最接近的祖先

获取时间戳格式的当前NSDate

如何将当前事件与 Drools 中的上一个事件进行比较?

RuntimeError:异步+ Apscheduler中的线程中没有当前事件循环

线程“Thread-1”中没有当前事件循环

RuntimeError:线程“ Dummy-1”中没有当前事件循环

Azure 函数错误线程中没有当前事件循环

SQL Server中的当前时间戳

在 Firestore 规则中获取当前时间戳

获取接下来 24 小时的所有事件,包括 Office365 API 中的当前事件

仅在Laravel事件中返回时间戳

在并发线程中运行加密提要:线程“ThreadPoolExecutor-0_0”中没有当前事件循环

如何获得Kotlin中时间戳的当前时间?

在Java中为当前时间添加较大的时间戳

RuntimeError:线程“ Thread-1”,多线程和异步错误中没有当前事件循环

尝试设置ipdb.set_trace():RuntimeError:线程'Thread -....'中没有当前事件循环。

Python3 asyncio生成新线程时线程中没有当前事件循环

在新循环上运行时,异常“线程'MainThread'中没有当前事件循环”

在不触发表单的当前事件的情况下删除表单的记录源中的记录

多线程处理python时出现RuntimeError“ RuntimeError:线程'Thread-1'中没有当前事件循环。”

Flask asyncio aiohttp-RuntimeError:线程“ Thread-2”中没有当前事件循环

在 Valetina Studio 中的 mysql 中获取当前时间戳

熊猫日期时间指数:随着时间的推移当前事件数

如何在Laravel 5中从Carbon获取当前时间戳

在AppEngine / Go中获取当前已部署的时间戳

需要获取Java中的当前时间戳

当前时间戳作为Java中的文件名

在jdbc中插入当前的postgres服务器时间戳

jq命令中可以包含当前时间戳吗?