NSObject 中執行Selector 的相關方法,nsobjectselector
1. 對當前Run Loop中Selector Sources的取消
NSObject
中的performSelector:withObject:afterDelay:
方法將會在當前線程的Run Loop中根據afterDelay
參數建立一個Timer,如果沒有調用有inModes
參數的方法,該Timer會運行在當前Run Loop的預設模式中,也就是NSDefaultRunLoopMode
定義的模式中。
performSelector:withObject:afterDelay:
方法的使用看起來還是很簡單的。這裡講另外一個輔助函數,NSObject中靜態cancelPreviousPerformRequestsWithTarget
方法。該方法就是專門用來取消取消performSelector:withObject:afterDelay:
方法所建立的Selector source(內部上就是一個Run Loop的Timer source)。因此該方法和performSelector:withObject:afterDelay:
方法一樣,只限於當前Run Loop中。
我們可以利用cancelPreviousPerformRequestsWithTarget
直接取消一個對象在當前Run Loop中的所有未執行的performSelector:withObject:afterDelay:
方法所產生的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(@"調用成功: %@", obj);
}
不會有任何輸出,因為兩個調用都被取消了。
如果想取消單獨一個的話,需使用cancelPreviousPerformRequestsWithTarget:selector:object:
方法,注意selector
和object
參數需要一一對應。如下代碼:
- (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(@"調用成功: %@", obj);
}
只會輸出:
調用成功: 26
其他兩個Selector都被取消了。
返回目錄
2. 在NSThread中執行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:
靜態方法,那麼上面NSObject
的performSelectorInBackground:withObject:
方法調用完全可以替換成:
[NSThread detachNewThreadSelector:@selector(test:) toTarget:self withObject:nil];
當然,使用者也可以自行手動建立一個NSThread
來完成上述功能,代碼如下:
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(test:) object:nil];
[thread start];
這兩種方法運行後的輸出是和第一種類似的。
返回目錄
3. 在NSThread中的Run Loop中執行Selector
這裡需要的方法是NSObject
的performSelector:onThread:withObject:waitUntilDone:
方法。由於是在另一個NSThread
中執行Selector,所以我們需要手動開始Run Loop。首先需要在ViewController
中定義兩個欄位,分別是NSThread
和控制線程內Run Loop執行的flag。
@interface ViewController ()
{
NSThread *_thread;
BOOL _isNewThreadAborted;
}
接下來做的是執行這個線程,並且線上程中手動調用NSRunLoop
的runMode:beforeDate:
方法。這裡注意,如果Run Loop沒有任何Source的話,該方法會立即返回,所以需要建立一個迴圈來持續調用Run Loop的runMode:beforeDate:
方法。並在Selector執行結束後同時嘗試結束這個迴圈。最終代碼如下:
- (void)viewDidLoad
{
[super viewDidLoad];
[self threadInfo:@"UI"];
_isNewThreadAborted = NO;
_thread = [[NSThread alloc] initWithTarget:self selector:@selector(newThread:) object:nil];
//開始線程
[_thread start];
//在另一個線程中的Run Loop中執行Selector
[self performSelector:@selector(test:) onThread:_thread withObject:nil waitUntilDone:NO];
}
//在新線程中建立並開始一個NSRunLoop
- (void)newThread:(id)obj
{
@autoreleasepool
{
NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
while (!_isNewThreadAborted)
{
[currentRunLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
NSLog(@"線程停止");
}
}
//Selector執行
- (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
參數,如果傳YES
的話,當前線程會等待Selector在另一個線程中執行完畢後繼續執行。
4. UIPinchGestureRecognizer 與 NSObject Selector 的關聯
if (recognizer.state == UIGestureRecognizerStateChanged) {
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(XXXSetNeedsDisplay) object:recognizer];
// 相關的方法...
} else if (recognizer.state == UIGestureRecognizerStateEnded) {
[self performSelector:@selector(XXXSetNeedsDisplay) withObject:recognizer afterDelay:1.0f];
}