單例模式例子:
https://github.com/XiaoRuiZuo/Singleton
多線程:
多線程是為了同步完成多項任務(wù),不是為了提高運行效率,而是為了提高資源使用效率來提高系統(tǒng)的效率。線程是在同一時間需要完成多項任務(wù)的時候?qū)崿F(xiàn)的。
3.多線程
多線程(multithreading),是指從軟件或者硬件上實現(xiàn)多個線程并發(fā)執(zhí)行的技術(shù)。具有多線程能力的計算機因有硬件支持而能夠在同一時間執(zhí)行多于一個線程,進而提升整體處理性能。
原理:
同一時間,CPU只能處理1條線程,只有1條線程在工作(執(zhí)行)
多線程并發(fā)(同時)執(zhí)行,其實是CPU快速地在多條線程之間調(diào)度(切換)
如果CPU調(diào)度線程的時間足夠快,就造成了多線程并發(fā)執(zhí)行的假象
注意:多線程并發(fā),并不是cpu在同一時刻同時執(zhí)行多個任務(wù),只是CPU調(diào)度足夠快,造成的假象。
優(yōu)點:
能適當(dāng)提高程序的執(zhí)行效率
能適當(dāng)提高資源利用率(CPU、內(nèi)存利用率)
缺點:
1.開啟線程需要占用一定的內(nèi)存空間(默認(rèn)情況下,主線程占用1M,子線程占用512KB),如果開啟大量的線程,會占用大量的內(nèi)存空間,降低程序的性能
2.線程越多,CPU在調(diào)度線程上的開銷就越大
二、iOS開發(fā)中的應(yīng)用
1.主線程
一個iOS程序運行后,默認(rèn)會開啟1條線程,稱為“主線程”或“UI線程”。
作用:
顯示\刷新UI界面
處理UI事件(比如點擊事件、滾動事件、拖拽事件等)
注意:
刷新UI必須放在主線程
別將比較耗時的操作放到主線程中
耗時操作會卡住主線程,嚴(yán)重影響UI的流暢度
2.實現(xiàn)方案

NSThread
一個NSThread對象控制執(zhí)行的線程。當(dāng)你想在自己的執(zhí)行線程的Objective-C的方法運行使用這個類。當(dāng)你需要執(zhí)行一個漫長的任務(wù)線程是特別有用的,但不希望它阻止應(yīng)用程序的其余部分的執(zhí)行。特別是,您可以使用線程來避免阻塞應(yīng)用程序,它處理的用戶界面和事件相關(guān)的操作的主線。線程也可以用來將一個大的工作分成幾個較小的作業(yè),這可能會導(dǎo)致在多核計算機性能的提高。
一、創(chuàng)建和啟動線程
// 1.創(chuàng)建線程NSThread*thread = [[NSThreadalloc] initWithTarget:selfselector:@selector(run) object:nil];// 2.啟動線程[thread start];// 線程一啟動,就會在線程thread中執(zhí)行self的run方法// 創(chuàng)建線程后自動啟動線程[NSThreaddetachNewThreadSelector:@selector(run) toTarget:selfwithObject:nil];// 隱式創(chuàng)建并啟動線程[selfperformSelectorInBackground:@selector(run) withObject:nil];上述2種創(chuàng)建線程方式的優(yōu)缺點- 優(yōu)點:簡單快捷- 缺點:無法對線程進行更詳細(xì)的設(shè)置
二、主線程相關(guān)用法
// 返回主線程+ (NSThread*)mainThread;// 是否為主線程(類方法)+ (BOOL)isMainThread;// 是否為主線程(對象方法)- (BOOL)isMainThread;
三、其他用法
// 線程通知NSDidBecomeSingleThreadedNotificationNSThreadWillExitNotificationNSWillBecomeMultiThreadedNotification// 獲得當(dāng)前線程NSThread*current = [NSThreadcurrentThread];// 線程的名字- (void)setName:(NSString*)n; - (NSString*)name;
線程的狀態(tài)

線程的狀態(tài)
// 進入就緒狀態(tài) -> 運行狀態(tài)。當(dāng)線程任務(wù)執(zhí)行完畢,自動進入死亡狀態(tài)- (void)start;// 阻塞(暫停)線程->進入阻塞狀態(tài)+ (void)sleepUntilDate:(NSDate*)date;+ (void)sleepForTimeInterval:(NSTimeInterval)ti;// 強制停止線程-> 進入死亡狀態(tài)+ (void)exit;注意:一旦線程停止(死亡)了,就不能再次開啟任務(wù)
多線程的安全隱患
一塊資源可能會被多個線程共享,也就是多個線程可能會訪問同一塊資源。
當(dāng)多個線程訪問同一塊資源時,很容易引發(fā)數(shù)據(jù)錯亂和數(shù)據(jù)安全問題。

解決辦法:(互斥鎖)

// 注意:鎖定1份代碼只用1把鎖,用多把鎖是無效的@synchronized(鎖對象) {// 需要鎖定的代碼? }
優(yōu)點與缺點
能有效防止因多線程搶奪資源造成的數(shù)據(jù)安全問題
需要消耗大量的CPU資源
atomic與nonatomic
OC在定義屬性時有nonatomic和atomic兩種選擇
@property(nonatomic,copy)NSString*name;@property(atomic,copy)NSString*name;
atomic:原子屬性,為setter方法加鎖(默認(rèn)就是atomic)
線程安全,需要消耗大量的資源
nonatomic:非原子屬性,不會為setter方法加鎖
非線程安全,適合內(nèi)存小的移動設(shè)備
開發(fā)建議
所有屬性都聲明為nonatomic
盡量避免多線程搶奪同一塊資源
盡量將加鎖、資源搶奪的業(yè)務(wù)邏輯交給服務(wù)器端處理,減小移動客戶端的壓力
線程間通信
在1個進程中,線程往往不是孤立存在的,多個線程之間需要經(jīng)常進行通信
例如在子線程下載圖片,在主線程刷新UI顯示圖片。
線程間通信常用方法
// 1.在主線程上執(zhí)行操作- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;// 2.在指定線程上執(zhí)行操作- (void)performSelector:(SEL)aSelector onThread:(NSThread*)thr withObject:(id)arg waitUnti
GCD
全稱是Grand Central Dispatch,“偉大的中樞調(diào)度器”
GCD是蘋果公司為多核的并行運算提出的解決方案
純C語言,提供了非常多強大的函數(shù)
優(yōu)勢
GCD會自動利用更多的CPU內(nèi)核(比如雙核、四核)
GCD會自動管理線程的生命周期(創(chuàng)建線程、調(diào)度任務(wù)、銷毀線程)
只需要告訴GCD想要執(zhí)行什么任務(wù),不需要編寫任何線程管理代碼
基本概念
任務(wù)和隊列
GCD中有2個核心概念
1.任務(wù):執(zhí)行什么操作
2.隊列:用來存放任務(wù)
// 1.定制任務(wù):確定想做的事情// 2.將任務(wù)添加到隊列中:GCD會自動將隊列中的任務(wù)取出,放到對應(yīng)的線程中執(zhí)行。Tips:任務(wù)的取出遵循隊列的FIFO原則:先進先出,后進后出
任務(wù)
一、執(zhí)行任務(wù)
-queue:隊列 - block:任務(wù)// 1.用同步的方式執(zhí)行任務(wù)dispatch_sync(dispatch_queue_tqueue,dispatch_block_tblock);// 2.用異步的方式執(zhí)行任務(wù)dispatch_async(dispatch_queue_tqueue,dispatch_block_tblock);// 3.GCD中還有個用來執(zhí)行任務(wù)的函數(shù)// 在前面的任務(wù)執(zhí)行結(jié)束后它才執(zhí)行,而且它后面的任務(wù)等它執(zhí)行完成之后才會執(zhí)行dispatch_barrier_async(dispatch_queue_tqueue,dispatch_block_tblock);
注意:
同步:只能在當(dāng)前線程中執(zhí)行任務(wù),不具備開啟新線程的能力
異步:可以在新的線程中執(zhí)行任務(wù),具備開啟新線程的能力
隊列
一、并發(fā)隊列(Concurrent Dispatch Queue)
可以讓多個任務(wù)并發(fā)(同時)執(zhí)行(自動開啟多個線程同時執(zhí)行任務(wù))
并發(fā)功能只有在異步(dispatch_async)函數(shù)下才有效
// 1.使用dispatch_queue_create函數(shù)創(chuàng)建隊列dispatch_queue_tdispatch_queue_create(constchar*label,// 隊列名稱dispatch_queue_attr_tattr);// 隊列的類型// 2.創(chuàng)建并發(fā)隊列dispatch_queue_tqueue= dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);// 3.使用dispatch_get_global_queue函數(shù)獲得全局的并發(fā)隊列dispatch_queue_tdispatch_get_global_queue(dispatch_queue_priority_tpriority,unsignedlongflags);// dispatch_queue_priority_t priority(隊列的優(yōu)先級 )// unsigned long flags( 此參數(shù)暫時無用,用0即可 )// 4.獲得全局并發(fā)隊列dispatch_queue_tqueue= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);// 5.全局并發(fā)隊列的優(yōu)先級#defineDISPATCH_QUEUE_PRIORITY_HIGH2// 高#defineDISPATCH_QUEUE_PRIORITY_DEFAULT0// 默認(rèn)(中)#defineDISPATCH_QUEUE_PRIORITY_LOW (-2)// 低#defineDISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN// 后臺
二、串行隊列(Serial Dispatch Queue)
讓任務(wù)一個接著一個地執(zhí)行(一個任務(wù)執(zhí)行完畢后,再執(zhí)行下一個任務(wù))
// 1.使用dispatch_queue_create函數(shù)創(chuàng)建串行隊列// 創(chuàng)建串行隊列(隊列類型傳遞NULL或者DISPATCH_QUEUE_SERIAL)dispatch_queue_tqueue= dispatch_queue_create("queue",NULL);// 2.使用dispatch_get_main_queue()獲得主隊列dispatch_queue_tqueue= dispatch_get_main_queue();注意:主隊列是GCD自帶的一種特殊的串行隊列,放在主隊列中的任務(wù),都會放到主線程中執(zhí)行。
三、各種隊列的執(zhí)行效果

特別注意:使用sync函數(shù)往當(dāng)前串行隊列中添加任務(wù),會卡住當(dāng)前的串行隊列 (線程卡死)
新手易混淆
有4個術(shù)語比較容易混淆:同步、異步、并發(fā)、串行
1.同步和異步主要影響:能不能開啟新的線程
同步:只是在當(dāng)前線程中執(zhí)行任務(wù),不具備開啟新線程的能力
異步:可以在新的線程中執(zhí)行任務(wù),具備開啟新線程的能力
2.并發(fā)和串行主要影響:任務(wù)的執(zhí)行方式
并發(fā):多個任務(wù)并發(fā)(同時)執(zhí)行
串行:一個任務(wù)執(zhí)行完畢后,再執(zhí)行下一個任務(wù)
GCD運用
一、線程間通信
從子線程回到主線程dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{// 執(zhí)行耗時的異步操作...dispatch_async(dispatch_get_main_queue(), ^{// 回到主線程,執(zhí)行UI刷新操作});});
二、延時執(zhí)行
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,(int64_t)(2.0* NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 2秒后異步執(zhí)行這里的代碼...
});
三、一次性代碼
// 使用dispatch_once函數(shù)能保證某段代碼在程序運行過程中只被執(zhí)行1次staticdispatch_once_tonceToken;dispatch_once(&onceToken, ^{// 只執(zhí)行1次的代碼(這里面默認(rèn)是線程安全的)});
四、快速迭代
// 使用dispatch_apply函數(shù)能進行快速迭代遍歷dispatch_apply(10, dispatch_get_global_queue(0,0), ^(size_tindex){// 執(zhí)行10次代碼,index順序不確定});
五、隊列組
// 分別異步執(zhí)行2個耗時的操作、2個異步操作都執(zhí)行完畢后,再回到主線程執(zhí)行操作dispatch_group_tgroup=? dispatch_group_create();dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{// 執(zhí)行1個耗時的異步操作});dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{// 執(zhí)行1個耗時的異步操作});dispatch_group_notify(group, dispatch_get_main_queue(), ^{// 等前面的異步操作都執(zhí)行完畢后,回到主線程...});
單例模式
作用:
可以保證在程序運行過程,一個類只有一個實例,而且該實例易于供外界訪問。從而方便地控制了實例個數(shù),并節(jié)約系統(tǒng)資源
使用場合
在整個應(yīng)用程序中,共享一份資源(這份資源只需要創(chuàng)建初始化1次)
實現(xiàn)過程:
1.重寫實現(xiàn):
創(chuàng)建一個需要單例模式的文件
// 1.在.m中保留一個全局的static的實例staticid_instance;// 2.重寫allocWithZone:方法,在這里創(chuàng)建唯一的實例(注意線程安全)+ (instancetype)allocWithZone:(struct_NSZone*)zone{staticdispatch_once_tonceToken;dispatch_once(&onceToken, ^{? ? ? ? _instance = [superallocWithZone:zone];? ? });return_instance;}// 3.提供1個類方法讓外界訪問唯一的實例+ (instancetype)sharedInstance{staticdispatch_once_tonceToken;dispatch_once(&onceToken, ^{? ? ? ? _instance = [[selfalloc] init];? ? });return_instance;}// 4.實現(xiàn)copyWithZone:方法- (id)copyWithZone:(struct_NSZone*)zone{return_instance;}
2.宏實現(xiàn):
// .h文件#define SingletonH(name) + (instancetype)shared##name;// .m文件#define SingletonM(name)\static id _instance;\\+ (instancetype)allocWithZone:(struct _NSZone *)zone\{\static dispatch_once_t onceToken;\dispatch_once(&onceToken, ^{\_instance =[super allocWithZone:zone];\});\return _instance;\}\\+ (instancetype)shared##name\{\static dispatch_once_t onceToken;\dispatch_once(&onceToken, ^{\_instance =[[self alloc]init];\});\return _instance;\}\\- (id)copyWithZone:(NSZone *)zone\{\return _instance;\}
NSOperation
NSOperation是蘋果封裝的一套多線程的東西,不像GCD是純C語言的,這個是OC的。但相比較之下GCD會更快一些,但本質(zhì)上NSOPeration是多GDC的封裝。
NSOperation相對于GCD:
NSOperation擁有更多的函數(shù)可用
NSOperationQueue中,可以建立各個NSOperation之間的依賴關(guān)系。
NSOperationQueue支持KVO??梢员O(jiān)測operation是否正在執(zhí)行(isExecuted)、是否結(jié)束(isFinished),是否取消(isCanceld)
GCD 只支持FIFO 的隊列,而NSOperationQueue可以調(diào)整隊列的執(zhí)行順序
NSOperation剖析
NSOperation是個抽象類,并不具備封裝操作的能力,必須使用它的子類
使用NSOperation子類的方式有3種
NSInvocationOperation
NSBlockOperation
自定義子類繼承NSOperation,實現(xiàn)內(nèi)部相應(yīng)的方法
NSOperationQueue
1.NSOperation可以調(diào)用start方法來執(zhí)行任務(wù),但默認(rèn)是同步執(zhí)行的
2.如果將NSOperation添加到NSOperationQueue(操作隊列)中,系統(tǒng)會自動異步執(zhí)行NSOperation中的操作
// 添加操作到NSOperationQueue中- (void)addOperation:(NSOperation*)op;- (void)addOperationWithBlock:(void(^)(void))block;
NSOperation的使用
配合使用NSOperation和NSOperationQueue就能實現(xiàn)多線程編程
具體步驟:
1.將需要執(zhí)行的操作封裝到一個NSOperation對象中
2.將NSOperation對象添加到NSOperationQueue中
3.系統(tǒng)會自動將NSOperationQueue中的NSOperation取出來放到一條新線程中執(zhí)行
NSInvocationOperation子類
// 1.創(chuàng)建NSInvocationOperation對象- (id)initWithTarget:(id)target selector:(SEL)sel object:(id)arg;// 2.調(diào)用start方法開始執(zhí)行操作- (void)start;// 一旦執(zhí)行操作,就會調(diào)用target的sel方法
注意:
默認(rèn)情況下,調(diào)用了start方法后并不會開一條新線程去執(zhí)行操作,而是在當(dāng)前線程同步執(zhí)行操作。
只有將NSOperation放到一個NSOperationQueue中,才會異步執(zhí)行操作
NSBlockOperation子類
// 1.創(chuàng)建NSBlockOperation對象+ (id)blockOperationWithBlock:(void(^)(void))block;// 2.通過addExecutionBlock:方法添加更多的操作- (void)addExecutionBlock:(void(^)(void))block;
注意:
只要NSBlockOperation封裝的操作數(shù) > 1,就會異步執(zhí)行操作
自定義NSOperation
// 1.創(chuàng)建對象繼承NSOperation,重寫- (void)main;// 在里面實現(xiàn)想執(zhí)行的任務(wù)
注意:
自己創(chuàng)建自動釋放池(因為如果是異步操作,無法訪問主線程的自動釋放池)
經(jīng)常通過
-(BOOL)isCancelled
方法檢測操作是否被取消,對取消做出響應(yīng)。
NSOperation方法
一、最大并發(fā)數(shù)
可以通過對最大并發(fā)數(shù)設(shè)置,控制程序中線程的數(shù)量
// 1.最大并發(fā)數(shù)的相關(guān)方法- (NSInteger)maxConcurrentOperationCount;- (void)setMaxConcurrentOperationCount:(NSInteger)cnt;
二、取消、暫停、恢復(fù)
// 1.取消隊列的所有操作- (void)cancelAllOperations;// 2.取消單個操作- (void)cancel// 暫停- (void)setSuspended:(BOOL)b;// YES代表暫停隊列,NO代表恢復(fù)隊列// 恢復(fù)隊列- (BOOL)isSuspended;
三、依賴
NSOperation之間可以設(shè)置依賴來保證執(zhí)行順序
// 1.比如一定要讓操作A執(zhí)行完后,才能執(zhí)行操作B,可以這么寫[operationB addDependency:operationA];// 操作B依賴于操作A注意:可以在不同queue的NSOperation之間創(chuàng)建依賴關(guān)系
四、操作的監(jiān)聽
// 1.可以監(jiān)聽一個操作的執(zhí)行完畢- (void(^)(void))completionBlock;- (void)setCompletionBlock:(void(^)(void))block;