iOS-NSThread编程详解
note:文明看帖轉載是對自己的尊重也是對學者的鼓勵,歡迎批評討論
iOS多線程-NSThread編程詳解
再iOS開發中存在三種比較常用的實現多線程編程的方法,NSThread,NSOperation,GCD,今天的先來說NSThread,要想實現多線程編程就要弄清楚進程,線程,同步和異步等概念
一.進程與線程
以下是一些運行的扯談,言語不夠學術,還請見諒:
? ? ? ? 1.進程就是當你點擊應用程序的圖標時,系統會為你創建一個用于運行該程序的進程,你的應用程序在神通廣大也無法脫離操作系統的手掌心,在計算機世界里任何都遵循標準,有自描述,應用程序之間,操作系統之間應用程序之間都通過標準來交流,沒有標準的計算機將寸步難行,進程是操作系統分配給你的運行環境,應用程序可以通過進程和操作系統交互(系統調用,反過來操作系統可以控制進程的生命周期和計算機硬件資源分配);線程則是進程中的一條執行路徑,它擁有自己的運行棧狀態機制,可以獲得CPU運行時間片段。一個進程中可以有多個的線程,從而就會產生同步和異步的工作機制。(從編程的角度只要知道其運行原理并根據自己的知識儲備抽象成自己的思維編程模式,最后都是為coding服務,至于實現細節原理應該是系統設計者所考慮的問題,所以各個人有不同的理解):
2.操作系統在沒有啟動的時候,它并沒有在內存中,它就是靜靜地躺在磁盤上,當你按下電源鍵時,先運行的是內嵌到硬件上的BIOS,是它把操作系統搬到內存中(至于它怎么搬的這是硬件廠商和操作系統廠商或者是協議好的,作為軟件編程層面,只要了解是那么回事就行)再把計算機的使用權力交給操作系統,操作系統就負責管理分配計算機資源,而用戶要想使用計算機硬件資源就要需要通過操作系統這個大管家,如果大家感興趣推薦這本書《自己動手寫操作系統》這本書主要講的就是操作系統的實現,通過它你能了解到計算機底層的運行原理,我們不需要熟讀只要了解大意提升自己的編程思維就行。
3.操作系統運行之后,操作系統就是指令集合躺在內存中了,現在程序中就躺著一些服務進程也就是一些服務指令的功能劃分,它們時刻待命處理用戶數據和操作,當有不同的操作和不同的數據是系統就會調用不同的的服務進程,說白了就是cpu從對應的服務指令所在的內存地址上取出指令執行,系統中有默認的幾個服務進程,它們是使用計算機的基礎。
4.當你點擊應用程序的圖標是,又會發生什么了,操作系統就用fork()一個進程,《自己動手寫操作系統》中也有提到,這個進程就作為點擊應用程序的環境了,你點擊的應用程序并不是一個勁的全部的往內存中塞,這就涉及到應用程序的運行原理了,在計算機中任何的數據都有自我描述的頭部或文件,應用程序也不例外,這些頭部有可能是公共的標準,也可能是自家平臺上的定義的,說白了就是一流的公司定制最后標準化大家都遵循,應用程序為可執行文件,可以對應不同平臺上的格式要求,windows的exe,unix,Mac等,它們都在自己的可執行文件加了說明,通過可執行文件的頭部,操作系統就知道把應用的那一段指令放到內存中,也就是找到入口函數,而可執行程序在編譯是是基于虛擬內存的編譯的,你只要想到在coding時,編譯器不會蠢到用空間來存儲可怕的字符串的,它都是通過虛擬內存地址來編譯的也就是函數調用變量存儲都是虛擬內存地址標識,在coding層面上我門看到的就是我們易讀的字符串,計算機太傻了只認識 0-1,計算機通過約定的說明頭找到了入口地址把它的一部分放到實際的內存地址中,實際內存地址的使用由操作系統管理有可能和虛擬地址不一樣,當要運行的指令不在內存中時再去取,如果內存緊張就會進行內存葉的交換,這樣就相當于一個進程就擁有整個內存空間了,內存的虛擬空間大小由硬件的尋址能力決定,這樣就解決了應用程序的指令搬到內存中的問題了。
5.既然應用程序的指令被搬到了內存中和如何交換的文件解決,你就不用考慮要運行的指令還在硬盤上只是一部分用到的指令的問題(那些都是操作系統該干的事),現在你就假裝應用程序的全部指令和數據資源都全部的搬到了內存中了,下面的任務就是CPU 表演時間了,進程建立就會默認的建立一個主要的線程棧來運行指令,應用程序的指令運行完之后進程就會被操作系統殺掉。
6.所為的多線程無非就是多個執行的線路,多幾個線程棧,它們共用進程資源,堆內存空間,線程棧保留了自己的運行狀態信息,它要知道自己運行到什么地方了,CPU 寄存指令狀態等,因為線程有運行時間片,所以就要記住自己的切換狀態。可以用一個例子來說明,如果你是一個土財主,你有一個仆人A,對應單核CPU,今天的任務就是挑一百單水,一百捆柴,如果你說是順序執行干不完一樣不能干另外一樣,這樣的話就對應了應用程序的單線程設計,就順序完成一百單水,一百捆柴;如果你說不管怎樣只要你今天能干完就OK,你可以交替挑水捆柴,這樣的話就對應應用程序的多線程設計,仆人有可能挑十挑水砍十捆柴,最后完成任務,對于單核CPU而言看不出什么高效的地方;現在你發財了你有買來了一個仆人B,現在你有兩個仆人了,對應雙核CPU相當于同一時間可以去執行指令比單核多線程設計時的線程時間片的切換高效,還是一樣的任務,如果你現在叫A一個人去干,相當于應用程序是單線程設計,這樣仆人B就閑置沒事干,當A挑水挑了十挑的時候,你看這B閑暇著不爽你就叫他捆柴去,于是很快就完成任務了,對應程序在運行時創建出一個線程去完成別的任務的多線程設計,如果財主一開始就分配A去挑水,B去捆柴,就相當于應用程序在設計時把功能劃分清楚分配給多個線程執行的設計,這樣就充分的發揮了多核CPU的威力。
7.多線程程序設計的最主要的注意事項是多線程對同一堆上的變量或者數據文件修改的沖突,從而影響結果,所以在設計時要特別小心。
多線程在多核時代的今天已經非常成熟了,不管是移動設備還是臺式電腦,它們的大體運行原理都大同小異,只要能靈活理解抽象成自己的知識儲備,提升自己編程思維,為coding服務,所以一千個讀者就有一千個哈姆雷特,同一個知識點不同的人有不同的知識儲備就有不同的抽象理解思維,但其知識原理都一樣。
有興趣的可以去閱讀《自己動手寫操作系統》,《程序員的自我修養》
二.同步與異步
同步和異步可以說是一種依賴關系,就不如兩個線程A,B,當一個線程運行到一半時,就新建另一個線程B去完成某項任務,同步的話只有B線程運行完了之后A線程才往下運行,異步的話,A新建B線程之后繼續執行,在iOS的app設計中UI的更新在主線程中執行,關于網絡和數據處理的都放到非主線程執行,如果使用同步的話就會時主線程停止,影響用戶體驗
三.NSThread詳解
1.首先來看NSThread的頭文件
/
+ (NSThread *)currentThread; ? ? ? ? //獲得當前的線程如果是在主線程調用則的到主線程,否則就得到當前代碼運行的線程,通過方法就可以的到線程并控制它
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument;
+ (BOOL)isMultiThreaded;//判斷是否是多線程
@property (readonly,retain)NSMutableDictionary *threadDictionary; ? ?//用于儲存數據的
+ (void)sleepUntilDate:(NSDate *)date; ? ? ? ?//控制線程的運行時間用date指定
+ (void)sleepForTimeInterval:(NSTimeInterval)ti; ? //時間間隔來制定
+ (void)exit;//線程的退出
+ (double)threadPriority; //線程的優先級別,高的時間片就多
+ (BOOL)setThreadPriority:(double)p; //設置優先級別
+ (NSArray *)callStackReturnAddressesNS_AVAILABLE(10_5,2_0);//返回現場調用的地址數組,配合NSLog()使用能打印出線程棧的函數調用地址
+ (NSArray *)callStackSymbolsNS_AVAILABLE(10_6,4_0); ?//返回現場調用的名字數組,配合NSLog()使用能打印出線程棧的函數調用地址
@property (copy)NSString *nameNS_AVAILABLE(10_5,2_0); //名字
@property NSUInteger stackSizeNS_AVAILABLE(10_5,2_0);//線程棧大小
@property (readonly)BOOL isMainThreadNS_AVAILABLE(10_5,2_0); //判斷是否是主線程
+ (BOOL)isMainThreadNS_AVAILABLE(10_5,2_0);// reports whether current thread is main
+ (NSThread *)mainThreadNS_AVAILABLE(10_5,2_0); ?//得到主線程
- (instancetype)initNS_AVAILABLE(10_5,2_0)NS_DESIGNATED_INITIALIZER;//初始化方法
- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(id)argumentNS_AVAILABLE(10_5,2_0);//初始化方法
@property (readonly,getter=isExecuting)BOOL executingNS_AVAILABLE(10_5,2_0);//判斷是否執行
@property (readonly,getter=isFinished)BOOL finishedNS_AVAILABLE(10_5,2_0);//是否執行完成
@property (readonly,getter=isCancelled)BOOL cancelledNS_AVAILABLE(10_5,2_0);//狀態判斷
- (void)cancelNS_AVAILABLE(10_5,2_0);//撤銷
- (void)startNS_AVAILABLE(10_5,2_0);//開始
- (void)mainNS_AVAILABLE(10_5,2_0);//線程入口函數
@end
/下面的幾個函數是NSObject的擴展而已,說明下面的函數的實現是基于NSThread的多線程實現的
@interface NSObject (NSThreadPerformAdditions)
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)arrayNS_AVAILABLE(10_5,2_0);
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5,2_0);
// equivalent to the first method with kCFRunLoopCommonModes
- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)argNS_AVAILABLE(10_5,2_0);
/
2.NSThread的使用及詳細說明
類方法:
+ (NSThread *)currentThread; ?//該類方法可以獲得當前代碼運行的線程,通過該類方法你就能控制線程的運行退出狀態改變狀態屬性,從而達到控制線程的作用
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument; ?//該類方法的作用是從當前線程中分支出一條線程,而這條線程的入口函數為target實例變量的selector方法,因為線程都必須有一個入口函數,
+ (void)sleepUntilDate:(NSDate *)date;//運行的時間控制
+ (void)sleepForTimeInterval:(NSTimeInterval)ti; ? ?
+ (void)exit;//退出的類方法保證, ? ? 相當于[[NSThread currentThread]exit] ? ,
+ (double)threadPriority; ? //線程的優先級別設置,達到線程的時間片的分配 ?同exit方法一樣
+ (BOOL)setThreadPriority:(double)p;//設置線程的優先級別
+ (NSArray *)callStackReturnAddresses //線程的調用都會有函數的調用函數的調用就會有棧返回地址的記錄,在這里返回的是函數調用返回的虛擬地址,說白了就是在該線程中函數調用的虛擬地址的數組
+ (NSArray *)callStackSymbols?//同上面的方法一樣,只不過返回的事該線程調用函數的名字數字
note:callStackReturnAddress和callStackSymbols這兩個函數可以同NSLog聯合使用來跟蹤線程的函數調用情況,是編程調試的重要手段
實例方法:
- (void)cancel?//取消函數
- (void)start//線程開始運行函數
- (void)main//線程的入口函數
如果你子類化NSThread的話,你就可以把線程運行任務放到main函數中,這樣你就可以通過start函數來手動的運行線程了,
如果讀者想通過NSThread來編寫多線程應用時,要記住線程必須要有一個入口函數,這入口函數可以是實例變量的方法,也可以是main,把你要用多線程執行的任務寫在入口函數中,你可以通過類方法[NSThread currentThread]來得到當前運行線程從而可以控制該線程了
下面的函數是NSObject的擴展方法,它們都是基于NSThread來實現的
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array;?
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
// equivalent to the first method with kCFRunLoopCommonModes
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)arrayNS_AVAILABLE(10_5,2_0);
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5,2_0);
// equivalent to the first method with kCFRunLoopCommonModes
- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg
3.NSThread的使用
a.方式一
#import "ViewController.h"@interface ViewController ()@end@implementation ViewController {NSThread *thread; }- (void)viewDidLoad {[super viewDidLoad];// Do any additional setup after loading the view, typically from a nib.//新建一個線程,你要指定一個入口函數,和一個targert,下面新建的線程久是指定self中的netThread方法為該線程的入口函數,你只要把多線程實現的任務放到newThread方法中即可,質疑其他的屬性讀者可以自行調試thread= [[NSThread alloc]initWithTarget:self selector:@selector(newThread:) object:@"我是傳過來的對象"];[thread start];//需要手動的啟動,否則線程不會自動執行NSLog(@"我是主線程%@",[NSThread currentThread]);}-(void)newThread:(id)sender{NSLog(@"%@",sender);NSLog(@"我是新線程%@",[NSThread currentThread]); } 運行結果:2015-01-01 18:09:57.845 threadTest[1251:58938] 我是主線程<NSThread: 0x7fc0da513770>{number = 1, name = main}
2015-01-01 18:09:57.845 threadTest[1251:58993] 我是傳過來的對象
2015-01-01 18:09:57.846 threadTest[1251:58993] 我是新線程<NSThread: 0x7fc0da70dd60>{number = 2, name = (null)}
b.方式二,通過類方法
該方法只是把方法一中的新建啟動包含到這個類方法中,輸出結果不變
[NSThread detachNewThreadSelector:@selector(newThread:) toTarget:self withObject:@"我是傳過來的對象"];
c.方法三,該方法是通過NSObject基于NSThread的擴展實現的輸出結果不變
[self performSelector:@selector(newThread:) withObject:@"我是傳過來的對象"];
還有其他的方法可以使用這里就不一一說明了,通過這三個方式可以總結出,多線程任務中必須有一個函數作為線程的入口函數,Target-selector-sender用來指定那一個target對象的方法selector作為入口函數傳入什么參數sender作為傳人線程的payload數據
4.NSThread的子類化
下面之類化NSThread,既是新建一個類繼承NSThread并覆蓋其main方法
#import "MyThread.h"@implementation MyThread -(void)main{//覆蓋main把多線程任務寫在此處你可以通過delegate的語法方法把MyThread任務的執行狀態通過代理方法傳出去,入圖片下載完之后通過代理通知delegate,并讓它更新UI或者存儲到磁盤等NSLog(@"我是myThread,你可以把任務寫在這里哦"); } @end<p class="p1"><span class="s1"></span>使用</p><p class="p2"><span class="s1">? ? </span><span class="s2">MyThread</span><span class="s1"> *th = [[</span><span class="s2">MyThread</span><span class="s1"> </span><span class="s3">alloc</span><span class="s1">]</span><span class="s3">init</span><span class="s1">];</span></p><p class="p2"><span class="s1">? ? [th </span><span class="s3">start</span><span class="s1">];</span></p> 運行結果:2015-01-01 18:30:10.927 threadTest[1300:63866] 我是myThread,你可以把任務寫在這里哦
總結:NSThread編程入口函數,線程任務,[NSThread currentThread]的運用,UI的更新在主線程中更新,如果在其他線程更新UI不能及時看到效果,非UI的任務可以放到非主線程中執行。
轉載于:https://www.cnblogs.com/fanyiyao-980404514/p/4207426.html
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的iOS-NSThread编程详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux 操作 一批文件或者文件夹
- 下一篇: 2014-06-25nbsp;20:39