select 出错! errno:22(EINVAL)。
随时可以修改定时间隔并立刻生效的可变时长定时器。
众所周知NSTimer,在一个定时间隔之间,不能修改定时间隔。想修改定时间隔只有等定时器超时,重新关闭定时器,并修改时间间隔,并重新启动定时器才行。
那么若等到一个定时间隔结束,那么就需要等待很久,若你的定时间隔很短,你就需要定时器运行周期变短,那么你的应用耗电量就要提高,若运行间隔超短,那么你的耗电量就超快,并且时间精度也会下降,毕竟cpu不会总时实时(100毫秒以内)给定时器。下面我们介绍一个零延迟(指的是修改的定时间隔立刻生效)可变时长定时器。
通过BSD线程,管道,select来实现无延迟可变时长定时器。当然你也可以用NSThread线程代替BSD线程。
修改的定时器时长可以通过管道发送过来,关闭定时器也可以通过管道发送过来的字符串来决定。
定时器绝大部分时间停留在select函数处等待管道消息或超时,handleConnectAbort相当于定时器函数。
生成管道的代码:
-(void)initData { FLDDLogDebug(@"函数"); self.guardModifyWaitTimePipe = [NSPipe pipe]; self.guardModifyWaitTimePipeWriteHandle = [self.guardModifyWaitTimePipe fileHandleForWriting]; self.fdGuardModifyWaitTimeReadPipe = [[self.guardModifyWaitTimePipe fileHandleForReading] fileDescriptor]; }
调用处的代码:
_connectTime = (long long)[[NSDate date] timeIntervalSince1970]; FLDDLogDebug(@"_connectTime = %lld\n", _connectTime); [self.guardModifyWaitTimePipeWriteHandle writeData: [@"GuardThreadStartMonitor:10;" dataUsingEncoding: NSASCIIStringEncoding]]; int i = connect(server_sock_fd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr_in)); FLDDLogDebug(@"connect after _connectTime = %lld, self.testGuardThread : %ld\n", (long long)[[NSDate date] timeIntervalSince1970], self.testGuardThread); _connectTime = 0; [self.guardModifyWaitTimePipeWriteHandle writeData: [@"GuardThreadIdle:1680;" dataUsingEncoding: NSASCIIStringEncoding]];
可变时长定时器BSD线程代码:
-(void)guardThread { FLDDLogDebug(@"函数"); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [[NSThread currentThread] setName:@"socket thread"]; FLDDLogDebug(@"guard thread"); //守护线程大循环 while(1) { fd_set read_fd_set; FD_ZERO(&read_fd_set); FD_SET(self.fdGuardModifyWaitTimeReadPipe, &read_fd_set); struct timeval tv; tv.tv_sec = self.guardThreadWaitTimeInterval; tv.tv_usec = 0; //socket一般都处于可写状态,除了网卡满了写不进去的罕见情况,并且若时线程外发送的消息都写了管道消息,管道有数据可度也会结束侦听,所以该处侦听一般都是立刻响应 long ret = select(self.fdGuardModifyWaitTimeReadPipe + 1, &read_fd_set, NULL, NULL, &tv); if(ret > 0) { //先把消息接收过来,后面再处理,防止再处理消息时,连接异常。 if (FD_ISSET(self.fdGuardModifyWaitTimeReadPipe, &read_fd_set)) { FLDDLogDebug(@"ret = %ld\n", ret); long nbytes; unsigned char readBuffer[PIPE_MESSAGE_MAX_BUFFER_LEN] = {0}; nbytes = read(self.fdGuardModifyWaitTimeReadPipe, readBuffer, PIPE_MESSAGE_MAX_BUFFER_LEN); if (nbytes <= 0) { FLDDLogDebug(@"no data."); } else { //防止收到的管道消息粘包,超过最大缓冲区长度,最后一个缓冲区字节强制置为结束符号 readBuffer[PIPE_MESSAGE_MAX_BUFFER_LEN - 1] = '\n'; NSString *str = [[NSString alloc] initWithString:[NSString stringWithFormat:@"%s", readBuffer]]; FLDDLogDebug(@"pipe data:%@", str); NSRange rangeGuardThreadIdle=[str rangeOfString:@"GuardThreadIdle:" options:NSBackwardsSearch]; NSRange rangeGuardThreadStartMonitor=[str rangeOfString:@"GuardThreadStartMonitor:" options:NSBackwardsSearch]; if(rangeGuardThreadIdle.length > 0) { FLDDLogDebug(@"收到守护线程空闲的消息,停止实时监控长连接线程:%@", str); // self.guardThreadWaitTimeInterval = GUARD_THREAD_MAX_WAIT_TIME_INTERVAL; NSRange range1 =[str rangeOfString:@";" options:NSBackwardsSearch]; if((range1.length > 0) && (range1.location > rangeGuardThreadIdle.length + rangeGuardThreadIdle.location)) { NSRange range2 = NSMakeRange (rangeGuardThreadIdle.location + rangeGuardThreadIdle.length, range1.location - rangeGuardThreadIdle.location - rangeGuardThreadIdle.length); NSString *number = [str substringWithRange : range2]; if(number.length > 0) { self.guardThreadWaitTimeInterval = [number longLongValue]; } else { self.guardThreadWaitTimeInterval = GUARD_THREAD_MAX_WAIT_TIME_INTERVAL; } } } else if(rangeGuardThreadStartMonitor.length > 0) { FLDDLogDebug(@"收到守护线程开始监控的消息,开始实时监控长连接线程:%@", str); // //如果已经连接Xcode调试不再处理connect异常的问题 // if(isatty(STDOUT_FILENO)) { // return; // } // self.guardThreadWaitTimeInterval = GUARD_THREAD_MIN_WAIT_TIME_INTERVAL; NSRange range1 =[str rangeOfString:@";" options:NSBackwardsSearch]; if((range1.length > 0) && (range1.location > rangeGuardThreadStartMonitor.length + rangeGuardThreadStartMonitor.location)) { NSRange range2 = NSMakeRange (rangeGuardThreadStartMonitor.location + rangeGuardThreadStartMonitor.length, range1.location - rangeGuardThreadStartMonitor.location - rangeGuardThreadStartMonitor.length); NSString *number = [str substringWithRange : range2]; if(number.length > 0) { self.guardThreadWaitTimeInterval = [number longLongValue]; } else { self.guardThreadWaitTimeInterval = GUARD_THREAD_MIN_WAIT_TIME_INTERVAL; } } } } } } else if(ret ==0){ FLDDLogDebug(@"select 超时!\n"); [self handleConnectAbort]; } else { FLDDLogDebug(@"select 出错!\n"); } } }); }
至于启动定时器BSD线程的代码我就不用写了吧?自己在自己需要的情况下,直接调用[self guardThread];也可以在应用起来时直接调用,或发通知调用。你的地盘你做主。这个在后台运行的线程我搞了很久才找到。有的技术就是和你隔一张纸,实际上很简单,但是问题就在你不知道。不过它只能在后台存活三分钟。
下面是一个测试GCD线程使用MainDispatchQueue证明它在后台能正常运行的代码,做一个单例或页面,在代码中调用test函数,可以看到,它在后台还照常打日志三分钟,这种起线程的方式独占cpu只能刷新一次UI:
-(void)test { dispatch_async(dispatch_get_main_queue(), ^{ for(NSInteger i = 0; ; i++) { sleep(1); NSLog(@"test:%ld", (long)i); } }); }