目录
- NSTimer传参
- NStimer在UITableViewCell中
NSTimer传参
需求:搜索要自动联想搜索,即等待1s后自动搜索
实现:用NSTimer
实现:
var searchDelayer:NSTimer?
func searchBar(searchBar: UISearchBar!, textDidChange searchText: String!) {
searchDelayer?.invalidate()
searchDelayer = nil
searchDelayer = NSTimer.scheduledTimerWithTimeInterval(1.5, target: self, selector: Selector("doDelayedSearch:"), userInfo: searchText, repeats: false)
}
func doDelayedSearch(text:String){
...
}
直接调用doDelayedSearch()
方法会程序会挂掉,控制台打印以下数据:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFTimer copyWithZone:]: unrecognized selector sent to instance 0x7f9c622ae7e0'
*** First throw call stack:
(
0 CoreFoundation 0x000000010c05b3e5 __exceptionPreprocess + 165
1 libobjc.A.dylib 0x000000010ba42967 objc_exception_throw + 45
2 CoreFoundation 0x000000010c0624fd -[NSObject(NSObject) doesNotRecognizeSelector:] + 205
3 CoreFoundation 0x000000010bfba7ec forwarding + 988
4 CoreFoundation 0x000000010bfba388 _CF_forwarding_prep_0 + 120
5 CoreFoundation 0x000000010bf32935 CFStringCreateCopy + 229
6 libswiftFoundation.dylib 0x000000010dc41314 _TF10Foundation24_convertNSStringToStringFCSo8NSStringSS + 116
7 MapCode 0x000000010a1a567e TToFC7MapCode17MapViewController15doDelayedSearchfS0_FSST + 62
8 Foundation 0x000000010b5fce94 __NSFireTimer + 83
9 CoreFoundation 0x000000010bfc34d4 CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION + 20
10 CoreFoundation 0x000000010bfc3095 __CFRunLoopDoTimer + 1045
11 CoreFoundation 0x000000010bf863cd __CFRunLoopRun + 1901
12 CoreFoundation 0x000000010bf859f6 CFRunLoopRunSpecific + 470
13 GraphicsServices 0x000000010ecfd9f0 GSEventRunModal + 161
14 UIKit 0x000000010c96b990 UIApplicationMain + 1282
15 MapCode 0x000000010a1b3fee top_level_code + 78
16 MapCode 0x000000010a1b402a main + 42
17 libdyld.dylib 0x000000010f9d7145 start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
这是因为NStimer的回调方法只能是不带参数的方法
或者是带参数但是参数本身只能是NStimer
的方法;
改成这样就不会闪退了:
func doDelayedSearch(timer: NSTimer) {
let searchText = timer.userInfo as String
// ...
}
stackoverFlow地址
还有个方法更加方便,用延迟调用实现:
[[self class] cancelPreviousPerformRequestsWithTarget:self selector:@selector(doDelayedSearch:) object:text];
[self performSelector:@selector(doDelayedSearch:) withObject:text afterDelay:1.5];
但是这个方法一定要保证两个参数的传值一致!
NStimer在UITableViewCell中
需求:在tableViewCell中添加计时器
实现:
如果使用NSTimer的简便构造方法时:
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)time target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
在tableview
滑动时timer就显示暂停,原因是timer的这个简便构造方法把timer加入了NSRunLoopDefaultMode
上,而tableview
在滑动时只会处理UITrackingRunLoopMode
,RunLoop
并没有处理timer事件。
解决的办法是将timer绑定到NSRunLoopCommonModes
上,通过以下方法构造timer:
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
通过以下方法将构造的timer绑定到NSRunLoopCommonModes
上:
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
这样timer在tableview滑动中就不会暂停了。
当然这样做也不是100%能解决问题,有人反映timer计时不准确。 可以进行如下优化:
计时器只有在 runLoop 的一次循环中被检查,所以如果在上次循环中做了什么耗时的操作,那么计时器就被延后执行了。
__block typeof(self) weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
weakSelf.timer = [NSTimer timerWithTimeInterval:1.0 target:weakSelf selector:@selector(applyCheckCodeTimer:) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:weakSelf.timer forMode:NSRunLoopCommonModes];
[[NSRunLoop currentRunLoop] run];
});
解释:
苹果不允许直接创建 RunLoop
,它只提供了两个自动获取的函数:CFRunLoopGetMain()
和 CFRunLoopGetCurrent()
。 这两个函数内部的逻辑大概是下面这样:
/// 全局的Dictionary,key 是 pthread_t, value 是 CFRunLoopRef
static CFMutableDictionaryRef loopsDic;
/// 访问 loopsDic 时的锁
static CFSpinLock_t loopsLock;
/// 获取一个 pthread 对应的 RunLoop。
CFRunLoopRef _CFRunLoopGet(pthread_t thread) {
OSSpinLockLock(&loopsLock);
if (!loopsDic) {
// 第一次进入时,初始化全局Dic,并先为主线程创建一个 RunLoop。
loopsDic = CFDictionaryCreateMutable();
CFRunLoopRef mainLoop = _CFRunLoopCreate();
CFDictionarySetValue(loopsDic, pthread_main_thread_np(), mainLoop);
}
/// 直接从 Dictionary 里获取。
CFRunLoopRef loop = CFDictionaryGetValue(loopsDic, thread));
if (!loop) {
/// 取不到时,创建一个
loop = _CFRunLoopCreate();
CFDictionarySetValue(loopsDic, thread, loop);
/// 注册一个回调,当线程销毁时,顺便也销毁其对应的 RunLoop。
_CFSetTSD(..., thread, loop, __CFFinalizeRunLoop);
}
OSSpinLockUnLock(&loopsLock);
return loop;
}
CFRunLoopRef CFRunLoopGetMain() {
return _CFRunLoopGet(pthread_main_thread_np());
}
CFRunLoopRef CFRunLoopGetCurrent() {
return _CFRunLoopGet(pthread_self());
}
从上面的代码可以看出,线程和RunLoop
之间是一一对应的,其关系是保存在一个全局的 Dictionary 里。线程刚创建时并没有RunLoop
,如果你不主动获取,那它一直都不会有。RunLoop
的创建是发生在第一次获取时,RunLoop
的销毁是发生在线程结束时。你只能在一个线程的内部获取其 RunLoop
(主线程除外)。