目錄
[背景/案例]
[1. 對當前Run Loop中Selector Sources的取消]
[2. 在NSThread中執(zhí)行Selector]
[3. 在NSThread中的Run Loop中執(zhí)行Selector]
背景
在做類似網(wǎng)易客戶端中的圖集功能的時候,用到單擊隱藏導(dǎo)航條秘狞,雙擊放大圖片的功能去枷;這里用了UICollectionView
拧篮,圖片參照了MWPhoto
庫里面MWZoomingScaleView
的實現(xiàn)邏輯纤勒,然后發(fā)現(xiàn)一個很有趣的現(xiàn)象寡喝,MWZoomingScaleView
是用UITouch
來實現(xiàn)的壳繁,代碼如下:
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
NSUInteger tapCount = touch.tapCount;
switch (tapCount) {
case 1:
[self handleSingleTap:touch];
break;
case 2:
[self handleDoubleTap:touch];
break;
case 3:
[self handleTripleTap:touch];
break;
default:
break;
}
[[self nextResponder] touchesEnded:touches withEvent:event];
}
這里在進入雙擊事件前會先調(diào)用一次單擊事件缆毁,然后MWZoomingScaleView
是這么處理這個問題的,思路很有意思邑狸,代碼如下:
- (void)handleSingleTap:(CGPoint)touchPoint {
[_photoBrowser performSelector:@selector(toggleControls) withObject:nil afterDelay:0.2];
}
- (void)handleDoubleTap:(CGPoint)touchPoint {
// if Error image,stop handing
if (_loadingError) {
return;
}
// Cancel any single tap handling
[NSObject cancelPreviousPerformRequestsWithTarget:_photoBrowser];
...
}
這里先是延遲調(diào)用單擊事件懈糯,然后在雙擊事件里又取消掉了單擊的方法。于是就順便總結(jié)下Selector的相關(guān)用法吧
對當前Run Loop中Selector Sources的取消
NSObject
中的performSelector:withObject:afterDelay:
方法將會在當前線程的Run Loop
中根據(jù)afterDelay
參數(shù)創(chuàng)建一個Timer
单雾,如果沒有調(diào)用有inModes
參數(shù)的方法赚哗,該Timer
會運行在當前Run Loop
的默認模式中,也就是NSDefaultRunLoopMode
定義的模式中硅堆。
performSelector:withObject:afterDelay:
方法的使用看起來還是很簡單的屿储。這里講另外一個輔助函數(shù),NSObject
中靜態(tài)的cancelPreviousPerformRequestsWithTarget
方法渐逃。該方法就是專門用來取消取消performSelector:withObject:afterDelay:
方法所創(chuàng)建的Selector source
(內(nèi)部上就是一個Run Loop
的Timer source
)够掠。因此該方法和performSelector:withObject:afterDelay:
方法一樣,只限于當前Run Loop
中茄菊。
我們可以利用cancelPreviousPerformRequestsWithTarget
直接取消一個對象在當前Run Loop
中的所有未執(zhí)行的performSelector:withObject:afterDelay:
方法所產(chǎn)生的Selector Sources
疯潭,如下代碼:
- (void)viewDidLoad{
[super viewDidLoad];
[self performSelector:@selector(test:) withObject:nil afterDelay:1];
[self performSelector:@selector(test:) withObject:@"mgen" afterDelay:2];
[NSObject cancelPreviousPerformRequestsWithTarget:self];
}
- (void)test:(id)obj{
NSLog(@"調(diào)用成功: %@", obj);
}
不會有任何輸出,因為兩個調(diào)用都被取消了面殖。
如果想取消單獨一個的話袁勺,需使用cancelPreviousPerformRequestsWithTarget:selector:object:
方法,注意selector
和object
參數(shù)需要一一對應(yīng)畜普。如下代碼:
- (void)viewDidLoad{
[super viewDidLoad];
[self performSelector:@selector(test:) withObject:[NSNumber numberWithInt:26] afterDelay:1];
[self performSelector:@selector(test:) withObject:[NSNumber numberWithInt:17] afterDelay:2];
[self performSelector:@selector(test:) withObject:[NSNumber numberWithInt:17] afterDelay:3];
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(test:) object:[NSNumber numberWithInt:17]];
}
- (void)test:(id)obj{
NSLog(@"調(diào)用成功: %@", obj);
}
只會輸出:
調(diào)用成功: 26
其他兩個Selector都被取消了期丰。
在NSThread中執(zhí)行Selector
這個話題很簡單,直接通過NSObject
的performSelectorInBackground:withObject:
方法就可以,如下代碼:
- (void)viewDidLoad {
[super viewDidLoad];
[self threadInfo:@"UI"];
[self performSelectorInBackground:@selector(test:) withObject:nil];
}
- (void)test:(id)obj {
@autoreleasepool
{
[self threadInfo:@"test"];
}
}
- (void)threadInfo:(NSString*)category {
NSLog(@"%@ - %@", category, [NSThread currentThread]);
}
輸出:
UI - <NSThread: 0x71639e0>{name = (null), num = 1}
test - <NSThread: 0x7176ad0>{name = (null), num = 3}
這個方法完全等效于NSThread
的detachNewThreadSelector:toTarget:withObject:
靜態(tài)方法钝荡,那么上面NSObject
的performSelectorInBackground:withObject:
方法調(diào)用完全可以替換成:
[NSThread detachNewThreadSelector:@selector(test:) toTarget:self withObject:nil];
當然街立,用戶也可以自行手動創(chuàng)建一個NSThread
來完成上述功能,代碼如下:
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(test:) object:nil];
[thread start];
這兩種方法運行后的輸出是和第一種類似的埠通。
在NSThread中的Run Loop中執(zhí)行Selector
這里需要的方法是NSObject
的performSelector:onThread:withObject:waitUntilDone:
方法赎离。由于是在另一個NSThread
中執(zhí)行Selector
,所以我們需要手動開始Run Loop
端辱。首先需要在ViewController
中定義兩個字段梁剔,分別是NSThread
和控制線程內(nèi)Run Loop
執(zhí)行的flag。
@interface ViewController (){
NSThread *_thread;
BOOL _isNewThreadAborted;
}
接下來做的是執(zhí)行這個線程舞蔽,并且在線程中手動調(diào)用NSRunLoop
的runMode:beforeDate:
方法荣病。這里注意,如果Run Loop沒有任何Source的話渗柿,該方法會立即返回个盆,所以需要創(chuàng)建一個循環(huán)來持續(xù)調(diào)用Run Loop的runMode:beforeDate:
方法。并在Selector執(zhí)行結(jié)束后同時嘗試結(jié)束這個循環(huán)朵栖。最終代碼如下:
- (void)viewDidLoad {
[super viewDidLoad];
[self threadInfo:@"UI"];
_isNewThreadAborted = NO;
_thread = [[NSThread alloc] initWithTarget:self selector:@selector(newThread:) object:nil];
//開始線程
[_thread start];
//在另一個線程中的Run Loop中執(zhí)行Selector
[self performSelector:@selector(test:) onThread:_thread withObject:nil waitUntilDone:NO];
}
//在新線程中創(chuàng)建并開始一個NSRunLoop
- (void)newThread:(id)obj {
@autoreleasepool {
NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
while (!_isNewThreadAborted)
{
[currentRunLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
NSLog(@"線程停止");
}
}
//Selector執(zhí)行
- (void)test:(id)obj {
[self threadInfo:@"test"];
_isNewThreadAborted = YES;
}
- (void)threadInfo:(NSString*)category {
NSLog(@"%@ - %@", category, [NSThread currentThread]);
}
輸出:
UI - <NSThread: 0x717e7e0>{name = (null), num = 1}
test - <NSThread: 0x8078a80>{name = (null), num = 3}線程停止
最后注意performSelector:onThread:withObject:waitUntilDone:
方法中最后的waitUntilDone
參數(shù)颊亮,如果傳YES
的話,當前線程會等待Selector在另一個線程中執(zhí)行完畢后繼續(xù)執(zhí)行陨溅。
Related Posts:
iOS: NSTimer使用小記
.NET vs iOS:反射 + DLR vs Selector
TinyMCE 4: 使用getDoc和DOMUtils.select執(zhí)行CSS Selector
iOS: 把對象直接轉(zhuǎn)化成NSDictionary或JSON
iOS: NSObject中執(zhí)行Selector的相關(guān)方法