在Cocoa Touch中实现Debounced/Coalesced模式,如`layoutSubviews`

Ant*_*tox 6 cocoa cocoa-touch objective-c ios

许多Cocoa Touch类利用了合并事件的设计模式.UIViews中,例如,有一种方法setNeedsLayout,其使得layoutSubviews在不久的将来将被调用.这在许多属性影响布局的情况下尤其有用.在您可以调用的每个属性的setter中[self setNeedsLayout],将确保更新布局,但如果一次更改多个属性,或者即使在一次迭代中多次修改单个属性,也会阻止布局的许多(可能是昂贵的)更新运行循环.其他昂贵的操作,如setNeedsDisplaydrawRect:一对方法遵循相同的模式.

实现这样的模式的最佳方法是什么?具体来说,我想将一些依赖属性绑定到一个昂贵的方法,如果属性发生了变化,每次迭代运行循环需要调用一次.


可能的解决方案:

使用CADisplayLink或者NSTimer你可以得到这样的工作,但两者似乎都比必要的更多涉及我不确定将这个添加到许多对象(特别是计时器)的性能影响.毕竟,性能是做这样的事情的唯一原因.

在某些情况下我使用过这样的东西:

- (void)debounceSelector:(SEL)sel withDelay:(CGFloat)delay {
    [NSObject cancelPreviousPerformRequestsWithTarget:self selector:sel object:nil];
    [self performSelector:sel withObject:nil afterDelay:delay];
}
Run Code Online (Sandbox Code Playgroud)

这在用户输入应该仅在连续动作或类似事件时触发某些事件的情况下工作得很好.当我们想要确保触发事件没有延迟时,我们只想在同一个运行循环中合并调用,这似乎很笨拙.

Jos*_*Lin 3

NSNotificationQueue正是您正在寻找的东西。请参阅有关合并通知的文档

这是 UIViewController 中的一个简单示例:

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

- (void)viewDidLoad
{
    [super viewDidLoad];

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(configureView:)
                                                 name:@"CoalescingNotificationName"
                                               object:self];

    [self setNeedsReload:@"viewDidLoad1"];
    [self setNeedsReload:@"viewDidLoad2"];
}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    [self setNeedsReload:@"viewWillAppear1"];
    [self setNeedsReload:@"viewWillAppear2"];
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    [self setNeedsReload:@"viewDidAppear1"];
    [self setNeedsReload:@"viewDidAppear2"];
}

- (void)setNeedsReload:(NSString *)context
{
    NSNotification *notification = [NSNotification notificationWithName:@"CoalescingNotificationName"
                                                                 object:self
                                                               userInfo:@{@"context":context}];

    [[NSNotificationQueue defaultQueue] enqueueNotification:notification
                                               postingStyle:NSPostASAP
                                               coalesceMask:NSNotificationCoalescingOnName|NSNotificationCoalescingOnSender
                                                   forModes:nil];
}

- (void)configureView:(NSNotification *)notification
{
    NSString *text = [NSString stringWithFormat:@"configureView called: %@", notification.userInfo];
    NSLog(@"%@", text);
    self.detailDescriptionLabel.text = text;
}
Run Code Online (Sandbox Code Playgroud)

您可以查看文档并使用 postsStyle 来获得您想要的行为。在本例中,使用NSPostASAP将为我们提供输出:

configureView called: {
    context = viewDidLoad1;
}
configureView called: {
    context = viewDidAppear1;
}
Run Code Online (Sandbox Code Playgroud)

这意味着连续的调用已经setNeedsReload合并。