NSTimer不起作用

时间:2016-05-13 16:14:51

标签: objective-c nstimer nsrunloop nsoutputstream socketrocket

主要问题

我正在为Socket Rocket实施带宽管理。 为了减少Socket Rocket中的更改量,我决定创建NSOutputStream的子类,它将为socket创建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和我的包装类正在回收所有通知,所以我排除了所有明显的错误。

仍有一些神秘的原因notifyReadyToWrite:从未被调用过。有没有人知道为什么?

1 个答案:

答案 0 :(得分:1)

好的,我发现了问题的根源。

当Socket Rocket收到空间可用的通知时,它不会立即处理此通知,而是将通知发送到工作队列并在那里进行处理。
因此,当调用- (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的问题。