NSTimer 不起作用

NSTimer doesn't work

主要问题

我正在为 Socket Rocket 实施带宽管理。 为了减少 Socket Rocket 中的更改量,我决定创建 NSOutputStream 的 subclass,它将包装为套接字创建的 NSOutputStream。概念非常好,应该很有魅力。

遇到问题

- (NSInteger)write:(const uint8_t *)buffer maxLength:(NSUInteger)len 方法中,我从带宽管理器查询是否可以发送数据。如果不是带宽管理器给我下一次写入操作所需的延迟。

所以我的代码大致是这样的:

- (NSInteger)write:(const uint8_t *)buffer maxLength:(NSUInteger)len
{
    auto bandwithInfo = [_bandwidthManager bandwidthInfoForSize: len];
    if (bandwithInfo.m_bOKtoSendNow)
    {
        auto actualWritten = [self.throttledStream write: buffer maxLength: len];
        if (actualWritten<0)
        {
            // report failure of sending data
            [_bandwidthManager reportNetworkBackPressure: len];
        }
        else
        {
            [_bandwidthManager ReportConsumedSendBandwidth: actualWritten];
            int bytesNotSentCount = len - actualWritten;
            if (bytesNotSentCount>0)
            {
                [_bandwidthManager ReportNetworkBackPressure: bytesNotSentCount];
            }
            [self enqueueEvent: NSStreamEventHasSpaceAvailable];
        }
        return actualWritten;
    }
    else
    {
        auto delay = bandwithInfo.m_nWaitTimeMilliseconds;
        NSASSERT(delay>0);
        [self scheduleNotifyReadyToWriteAfterMs: delay];
        return 0;
    }
}

- (void)scheduleNotifyReadyToWriteAfterMs: (int)miliseconds
{
    if (!isReadyToWriteScheduled)
    {
        isReadyToWriteScheduled = YES;
        static const NSTimeInterval kTimeIntervalMilisecond = 0.001;
        NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval: miliseconds * kTimeIntervalMilisecond
                                                          target: self
                                                        selector: @selector(notifyReadyToWrite:)
                                                        userInfo: nil
                                                         repeats: false];
        NSLog(@"timer=%@\nRunLoop=%@", timer, [NSRunLoop currentRunLoop]);
// this alternative also doesn't work:
//      [self performSelector: @selector(notifyReadyToWrite:)
//                 withObject: nil
//                 afterDelay: miliseconds * kTimeIntervalMilisecond];
    }
}

- (void)notifyReadyToWrite: (NSTimer *)timer
{
    [timer invalidate];

    if (self.hasSpaceAvailable) {
        isReadyToWriteScheduled = NO;
        [self enqueueEvent: NSStreamEventHasSpaceAvailable];
    }
}

在日志中我可以看到,(有一个 运行 循环并且它包含创建的计时器):

timer=<__NSCFTimer: 0x109a96180>
RunLoop=<CFRunLoop 0x109a962d0 [0x7fff7208f440]>{wakeup port = 0x4507, stopped = false, ignoreWakeUps = true, 
current mode = (none),
common modes = <CFBasicHash 0x109a96390 [0x7fff7208f440]>{type = mutable set, count = 1,
entries =>
    2 : <CFString 0x7fff71fa1940 [0x7fff7208f440]>{contents = "kCFRunLoopDefaultMode"}
}
,
common mode items = (null),
modes = <CFBasicHash 0x109a963d0 [0x7fff7208f440]>{type = mutable set, count = 1,
entries =>
    2 : <CFRunLoopMode 0x109a96410 [0x7fff7208f440]>{name = kCFRunLoopDefaultMode, port set = 0x440b, queue = 0x109a964e0, source = 0x109a96570 (not fired), timer port = 0x4b03, 
    sources0 = (null),
    sources1 = (null),
    observers = (null),
    timers = <CFArray 0x109a95b10 [0x7fff7208f440]>{type = mutable-small, count = 1, values = (
    0 : <CFRunLoopTimer 0x109a96180 [0x7fff7208f440]>{valid = Yes, firing = No, interval = 0, tolerance = 0, next fire date = 485078914 (-0.00818103552 @ 3808422033916), callout = (NSTimer) [SRSendStreamWithThrottling notifyReadyToWrite:] (0x7fff87025d0d / 0x10264b100) (/Users/maru/Library/Developer/Xcode/DerivedData/AvayaCommonWorkspace-cnweundajqjciphewynfexnutumh/Build/Products/Debug/testrunner), context = <CFRunLoopTimer context 0x109a93370>}
)},
    currently 485078914 (3808417241824) / soft deadline in: 0.00479207 sec (@ 3808422033916) / hard deadline in: 0.004791892 sec (@ 3808422033916)
},

}
}

现在我有一个 运行 循环 我的自定义流和包装流被安排在这个 运行 循环中,所以 socket rocket 和我的包装 class 正在接收所有通知,所以我已经排除了所有明显的错误。

仍然出于某种神秘原因 notifyReadyToWrite: 从未被调用。有人知道为什么吗?

好的,我找到了问题的根源。

当 Socket Rocket 收到 space 可用的通知时,它不会立即处理此通知,而是将通知分派到工作队列并在那里进行处理。
因此,当调用 - (NSInteger)write:(const uint8_t *)buffer maxLength:(NSUInteger)len 方法时,当前事件循环与调度队列相关联,并且该队列未完全发挥作用。您不能将计时器添加到这样的 运行 循环中。因此,我将定时器添加到与流关联的事件循环中,而不是将定时器添加到当前事件循环中,定时器开始工作:

- (void)scheduleNotifyReadyToWriteAfterMs: (clientsdk::MillisecondTime)miliseconds
{
    if (!isReadyToWriteScheduled)
    {
        isReadyToWriteScheduled = YES;
        static const NSTimeInterval kTimeIntervalMilisecond = 0.001;

        NSTimer *timer = [NSTimer timerWithTimeInterval: miliseconds * kTimeIntervalMilisecond
                                                 target: self
                                               selector: @selector(notifyReadyToWrite:)
                                               userInfo: nil
                                                repeats: NO];

        [self enumerateRunLoopsUsingBlock:^(CFRunLoopRef runLoop) {
            CFRunLoopAddTimer(runLoop, (CFRunLoopTimerRef)timer, kCFRunLoopCommonModes);
        }];
    }
}

这解决了 NSTimer 的问题。