- Grand Central Dispatch In-Depth,介紹GCD和thread的基本概念,這篇文章出自於Raywenderlich,除了GCD API的使用以外也介紹了何謂thread,對我這個半路出家的開發者來說很有幫助。
- How To Use NSOperations and NSOperationQueues,這篇文章同樣出自Raywenderlich,這篇文章帶著讀者一步步寫出客製化main函式。
- Run Loops in Thread Programming Guide of Apple,這篇是Apple官方的教學文件,裡面講到如何讓thread接收外來的資訊進行相應的動作。
這篇文章主要在談NSThread,如上面所列除了NSThread以外,Apple提供了其他的API,操作起來比NSThread簡單,但是我遇到的問題是:
我把所學的概念寫成Sample Code(也有Swift版本),接下來,我將會講解Sample Code的內容,還有Swift和Objc-C兩種語言在一些小地方的差異。
- 我希望可以控制Thread在使用者做某些動作的當下馬上開始或結束。
因此,我不能使用GCD(無法在任務執行到一半時結束動作)和NSOperation(無法保證動作會馬上開始執行,因為iOS會視系統狀況調整目前的concurrent thread數量,在一般情況下是比較安全的選擇)我的做法直接開啟NSThread,同時我會接收系統memory warning的訊息,當系統提示記憶體不足,我會在GUI提示使用者,並且關閉最近開啟的NSThread。
我把所學的概念寫成Sample Code(也有Swift版本),接下來,我將會講解Sample Code的內容,還有Swift和Objc-C兩種語言在一些小地方的差異。
首先,進入App之後會看到以下的畫面,有兩個Label分別是$counter1, $counter2,按下Thread1之後$counter1就會開始出現遞增的數字,按下Thread2之後$counter2也會出現遞增的數字。這兩個Label遞增的動作分別是在兩個Thread中完成,等等會有進一步的說明。
按下中間的Thread_Safety會出現下面的Log,我會用兩個Thread對同一個Array塞資料,一個Thread在Array尾端加入10,另外一個在Array的開端加入0。(Obj-C版本的sample code,只有在Thread1與Thread2都開啟的狀態下按下Thread_Safety才能正常動作)
接下來我將會用說明在sample code中,我做了哪些事情:
- 建立一個NSThread與如何結束一個NSThread
- 讓一個NSThread持續等待外界呼叫在其內部執行方法(Obj-C only)
- Thread Safety,如何處理兩個Thread爭搶資源的方法
建立一個NSThread
請打開ViewController.m或是ViewController.swift檔,viewDidLoad的方法內我初始化了兩個NSThread。
- (void)viewDidLoad {
[super viewDidLoad];
/*
Setup _thread1 and _thread2 with their enter method
*/
_thread1 = [[NSThread alloc]initWithTarget:self selector:@selector(thread1Routine) object:nil];
_thread2 = [[NSThread alloc]initWithTarget:self selector:@selector(thread2Routine) object:nil];
_arr = [NSMutableArray new];
}
Objective C
override func viewDidLoad() {
super.viewDidLoad()
self.thread1 = NSThread(target: self, selector: "thread1Routine", object: nil);
self.thread2 = NSThread(target: self, selector: "thread2Routine", object: nil);
}
Swift
上面兩段程式碼分別是Objective-C與Swift的sample code的片段,我在viewDidLoad方法裡面初始化兩個NSThread,並且指定好target和thread進入點所要執行的方法與參數。在這邊target的就是告訴系統,等等在thread中如果出現"self",這個"self"是誰。
接著要是定義thread進入點所要執行的方法,這個概念有點類似C的程式進入點,這個thread在創造之後會執行這個方法,這個進入點方法內所呼叫到的方法會在這個thread執行,以下就用thread1的進入點方法為例。
上面兩段程式碼分別是Objective-C與Swift的sample code的片段,我在viewDidLoad方法裡面初始化兩個NSThread,並且指定好target和thread進入點所要執行的方法與參數。在這邊target的就是告訴系統,等等在thread中如果出現"self",這個"self"是誰。
接著要是定義thread進入點所要執行的方法,這個概念有點類似C的程式進入點,這個thread在創造之後會執行這個方法,這個進入點方法內所呼叫到的方法會在這個thread執行,以下就用thread1的進入點方法為例。
-(void)thread1Routine{
//Create a autoReleasePool for _thread1
@autoreleasepool {
//Hold the pointer of current thread, which means _thread1
NSThread * curThread = [NSThread currentThread];
//Hold the pointer of current runloop
NSRunLoop * curRunloop = [NSRunLoop currentRunLoop];
int counter = 0;
//Run the loop while current thread is not cancelled
while (curThread.isCancelled == NO) {
counter += 1;
//Back to main thread to update GUI
dispatch_async(dispatch_get_main_queue(), ^{
_counter1Lb.text = [NSString stringWithFormat:@"%d",(counter * 2) - 1];
});
//Force current thread to sleep 0.5 sec
usleep(500000);
//Wait input source
if ([curRunloop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]) {
}
}
[NSThread exit];
}
}
Objective C
func thread1Routine(){
autoreleasepool{
let curThread = NSThread.currentThread()
let curRunLoop = NSRunLoop.currentRunLoop()
var counter = 0
while curThread.cancelled == false {
counter += 1
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.counter1Lb.text = "\(counter * 2)"
});
usleep(500000);
}
NSThread.exit()
}
}
Swift
由於我現在自行創造了自己的thread,所以要幫它做個autoreleasepool(請參考這裡),這樣幫助thread裡面的記憶體更有效率回收。
我利用NSThread的類別方法currentThread來抓住currentThread的指標,在這個例子裡我抓住的就是thread1。
利用NSRunLoop的類別方法,我抓住了這個thread中的runloop指標。
接著我讓thread跑一個while迴圈,在thread結束之前,它會改變counter1Lb的內容,然後休眠0.5秒。(注意!所有UI的更新都要切回main thread,否則你會無法預期UI什麼時候會被更新!)
當while迴圈結束時,結束這個thread。
NSThread等待外界Input
由於我現在自行創造了自己的thread,所以要幫它做個autoreleasepool(請參考這裡),這樣幫助thread裡面的記憶體更有效率回收。
我利用NSThread的類別方法currentThread來抓住currentThread的指標,在這個例子裡我抓住的就是thread1。
利用NSRunLoop的類別方法,我抓住了這個thread中的runloop指標。
接著我讓thread跑一個while迴圈,在thread結束之前,它會改變counter1Lb的內容,然後休眠0.5秒。(注意!所有UI的更新都要切回main thread,否則你會無法預期UI什麼時候會被更新!)
當while迴圈結束時,結束這個thread。
NSThread等待外界Input
也許你已經察覺到Obj-C的程式碼和Swift的程式碼有些不同,Obj-C的程式碼中有這麼一段:
//Wait input source
if ([curRunloop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]) {
}
這段程式之所以被放在while迴圈中是因為我希望在runloop在這個迴圈中等待外界的input,當thread收到外界的input時runMode:beforeDate:將會回傳YES,此時runloop會run一次然後回到這一行並且block住直到我剛剛給的時間點或是它再次接收到外界的input,但我給的時間點是極遙遠的未來,所以基本上它會等到外界再次input。
(未完待續...)
(未完待續...)



沒有留言:
張貼留言