简体   繁体   English

NSTimer 不起作用

[英]NSTimer doesn't work

Main problem主要问题

I'm implementing bandwidth management for Socket Rocket.我正在为 Socket Rocket 实施带宽管理。 To reduce amount of alteration in Socket Rocket I've decided to create subclass of NSOutputStream which will wrap NSOutputStream created for a socket.为了减少 Socket Rocket 中的改动量,我决定创建NSOutputStream子类,它将包装为套接字创建的NSOutputStream Concept is quite nice and should work like charm.概念非常好,应该像魅力一样工作。

Encounter problem遇到问题

In - (NSInteger)write:(const uint8_t *)buffer maxLength:(NSUInteger)len method I'm queering from bandwidth manager if data can be send or not.- (NSInteger)write:(const uint8_t *)buffer maxLength:(NSUInteger)len方法中,我正在询问带宽管理器是否可以发送数据。 If not bandwidth manager gives me required delay for next write operation.如果不是,带宽管理器会给我下一次写操作所需的延迟。

So my code looks more or less like this:所以我的代码看起来或多或少是这样的:

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

In logs I can see, (there is a run loop and it contains created timer):在日志中我可以看到,(有一个运行循环,它包含创建的计时器):

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)
},

}
}

Now I have a run loop my custom stream and wrapped stream are scheduled in this run loop, so socket rocket and my wrapping class are reciveing all notifications, so I've rule out all obvious mistakes.现在我有一个运行循环,我的自定义流和包装流被安排在这个运行循环中,所以套接字火箭和我的包装类正在接收所有通知,所以我排除了所有明显的错误。

Still for some mysterious reason notifyReadyToWrite: is never called.仍然出于某种神秘的原因notifyReadyToWrite:从未被调用。 Does anyone have a clue why?有谁知道为什么?

Ok I've found source of problems.好的,我找到了问题的根源。

When Socket Rocket receives notification that space is available it doesn't process this notification immediately, but dispatches notification to working queue and does processing there.当 Socket Rocket 收到空间可用的通知时,它不会立即处理此通知,而是将通知分派到工作队列并在那里进行处理。
So when - (NSInteger)write:(const uint8_t *)buffer maxLength:(NSUInteger)len method is invoked current event loop is associated with dispatch queue and this queue is not fully functional.因此,当- (NSInteger)write:(const uint8_t *)buffer maxLength:(NSUInteger)len方法被调用时,当前事件循环与调度队列相关联,并且该队列没有完全发挥作用。 You can't add timer to such run loop.您不能向此类运行循环添加计时器。 So instead adding timer to current event loop I've added timer to event loop associated with the stream and timer start working:因此,我将计时器添加到与流相关的事件循环中,而不是将计时器添加到当前事件循环中,并且计时器开始工作:

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

This solves issue with a NSTimer.这解决了 NSTimer 的问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM