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