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 的问题。
主要问题
我正在为 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 的问题。