我已经在这个问题上工作了几天,但我的解决方案都不够。我认为,我缺乏理论知识来实现这一目标,并且会喜欢一些建议(不一定是iOS特定的 - 我可以将C,伪代码等等转换成我需要的东西。)
基本上,我有两部iPhone。当用户按下按钮时,任何一个都可以触发重复动作。然后它需要通知其他iPhone(通过MultiPeer框架)触发相同的操作......但它们都需要在同一时刻启动并保持步调。我真的需要获得1/100秒的准确度,我认为可以在这个平台上实现。
作为对我的同步程度的半粗略衡量标准,我使用AudioServices来播放" tick"每个设备上的声音......你可以很容易地通过耳朵告诉它们它们的同步性(理想情况下你无法识别多个声源)。
当然,我必须以某种方式考虑MultiPeer延迟......并且它的变化很大,在我的测试中从.1秒到.8秒。
根据我的目的发现系统时钟完全不可靠,我找到了NTP的iOS实现并且正在使用它。所以我有理由相信这两款手机有一个准确的时间共同参考(尽管我还没有找到一种方法来测试这种假设,而不是在两台设备上连续显示NTP时间,我这样做,以及它似乎与我的眼睛很好地同步。)
我之前尝试的是发送"开始时间"使用P2P消息,然后(在接收方端)从1.5秒的常数中减去该等待时间,并在该延迟之后执行动作。在发送方端,我只是等待该常量继续,然后执行操作。这根本不起作用。我离开了。
我的下一次尝试是在两端等待整整一秒被3整除,因为延迟似乎总是<1秒,我认为这会起作用。我使用&#34;延迟&#34;简单地阻塞线程的方法。我知道,这只是一个棍棒,但我只是想在获得更优雅的解决方案之前获得时间工作期。所以,我的&#34; 发件人&#34; (按下按钮的设备)执行此操作:
-(void)startActionAsSender
{
[self notifyPeerToStartAction];
[self delay];
[self startAction];
}
收件人这样做,以响应代表电话:
-(void)peerDidStartAction
{
[self delay];
[self startAction];
}
我的&#34; 延迟&#34;方法如下所示:
-(void)delay
{
NSDate *NTPTimeNow = [[NetworkClock sharedInstance] networkTime];
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *components = [calendar components:NSSecondCalendarUnit
fromDate:NTPTimeNow];
NSInteger seconds = [components second];
// If this method gets called on a second divisible by three, wait a second...
if (seconds % 3 == 0) {
sleep(1);
}
// Spinlock
while (![self secondsDivideByThree]) {}
}
-(BOOL)secondsDivideByThree
{
NSDate *NTPTime = [[NetworkClock sharedInstance] networkTime];
NSCalendar *calendar = [NSCalendar currentCalendar];
NSInteger seconds = [[calendar components:NSSecondCalendarUnit fromDate:NTPTime]
second];
return (seconds % 3 == 0);
}
答案 0 :(得分:5)
这是旧的,所以我希望你能够得到一些有用的东西。我遇到了一个非常类似的问题。在我的情况下,我发现不一致几乎完全是由于计时器合并,导致计时器在iOS设备上出错by up to 10%以节省电池使用量。
作为参考,这是我在自己的应用中使用的解决方案。首先,我使用一种简单的自定义协议,该协议基本上是一个基本的NTP,相当于通过本地网络同步两个设备之间的单调递增时钟。我把这个称为同步时间&#34; DTime&#34;在下面的代码中。使用此代码,我能够告诉所有同伴&#34;在时间Y&#34;执行操作X,并且它同步发生。
+ (DTimeVal)getCurrentDTime
{
DTimeVal baseTime = mach_absolute_time();
// Convert from ticks to nanoseconds:
static mach_timebase_info_data_t s_timebase_info;
if (s_timebase_info.denom == 0) {
mach_timebase_info(&s_timebase_info);
}
DTimeVal timeNanoSeconds = (baseTime * s_timebase_info.numer) / s_timebase_info.denom;
return timeNanoSeconds + localDTimeOffset;
}
+ (void)atExactDTime:(DTimeVal)val runBlock:(dispatch_block_t)block
{
// Use the most accurate timing possible to trigger an event at the specified DTime.
// This is much more accurate than dispatch_after(...), which has a 10% "leeway" by default.
// However, this method will use battery faster as it avoids most timer coalescing.
// Use as little as necessary.
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, DISPATCH_TIMER_STRICT, dispatch_get_main_queue());
dispatch_source_set_event_handler(timer, ^{
dispatch_source_cancel(timer); // one shot timer
while (val - [self getCurrentDTime] > 1000) {
// It is at least 1 microsecond too early...
[NSThread sleepForTimeInterval:0.000001]; // Change this to zero for even better accuracy
}
block();
});
// Now, we employ a dirty trick:
// Since even with DISPATCH_TIMER_STRICT there can be about 1ms of inaccuracy, we set the timer to
// fire 1.3ms too early, then we use an until(time) { sleep(); } loop to delay until the exact time
// that we wanted. This takes us from an accuracy of ~1ms to an accuracy of ~0.01ms, i.e. two orders
// of magnitude improvement. However, of course the downside is that this will block the main thread
// for 1.3ms.
dispatch_time_t at_time = dispatch_time(DISPATCH_TIME_NOW, val - [self getCurrentDTime] - 1300000);
dispatch_source_set_timer(timer, at_time, DISPATCH_TIME_FOREVER /*one shot*/, 0 /* minimal leeway */);
dispatch_resume(timer);
}