.一.進程
進程:是指在系統(tǒng)中正在運行的一個應(yīng)用程序,每個進程之間是獨立的,每個進程均運行在其專用且受保護的內(nèi)存空間內(nèi)
比如同時打開迅雷、Xcode,系統(tǒng)就會分別啟動2個進程

二.線程
1.什么是線程?
答:1個進程要想執(zhí)行任務(wù),必須得有線程(每1個進程至少要有1條線程),一個進程(程序)的所有任務(wù)都在線程中執(zhí)行比如使用酷狗播放音樂、使用迅雷下載電影,都需要在線程中執(zhí)行

2.線程的串行
<1>.1個線程中任務(wù)的執(zhí)行是串行的,如果要在1個線程中執(zhí)行多個任務(wù),那么只能一個一個地按順序執(zhí)行這些任務(wù),也就是說,在同一時間內(nèi),1個線程只能執(zhí)行1個任務(wù)
<2>.比如在1個線程中下載3個文件(分別是文件A、文件B、文件C)

3.多線程
<1>什么是多線程?
答:1個進程中可以開啟多條線程,每條線程可以并行(同時)執(zhí)行不同的任務(wù),多線程技術(shù)可以提高程序的執(zhí)行效率.比如下載文件:可以同時下載

-
<2>.多線程的原理
多線程的原理 (1).同一時間,CPU只能處理1條線程,只有1條線程在工作(執(zhí)行) (2).多線程并發(fā)(同時)執(zhí)行,其實是CPU快速地在多條線程之間調(diào)度(切換) (3).如果CPU調(diào)度線程的時間足夠快,就造成了多線程并發(fā)執(zhí)行的假象 問題:如果線程非常非常多,會發(fā)生什么情況?
答:1.CPU會在N多線程之間調(diào)度,CPU會累死,消耗大量的CPU資源,2.每條線程被調(diào)度執(zhí)行的頻次會降低(線程的執(zhí)行效率降低)
-
<3>.多線程的優(yōu)缺點
多線程的優(yōu)點:1.能適當(dāng)提高程序的執(zhí)行效率,2.能適當(dāng)提高資源利用率(CPU、內(nèi)存利用率)
多線程的缺點:1.創(chuàng)建線程是有開銷的,iOS下主要成本包括:內(nèi)核數(shù)據(jù)結(jié)構(gòu)(大約1KB)、??臻g(子線程512KB、主線程1MB,也可以使用-setStackSize:設(shè)置,但必須是4K的倍數(shù),而且最小是16K),創(chuàng)建線程大約需要90毫秒的創(chuàng)建時間,2.如果開啟大量的線程,會降低程序的性能,3.線程越多,CPU在調(diào)度線程上的開銷就越大,4.程序設(shè)計更加復(fù)雜:比如線程之間的通信、多線程的數(shù)據(jù)共享
三.多線程在iOS開發(fā)中的應(yīng)用
(1).主線程
1.什么是主線程?
答:一個iOS程序運行后,默認會開啟1條線程,稱為“主線程”或“UI線程”-
2.主線程的主要作用
- 顯示\刷新UI界面
- 處理UI事件(比如點擊事件、滾動事件、拖拽事件等)
-
3.主線程的使用注意
- 別將比較耗時的操作放到主線程中
- 耗時操作會卡住主線程,嚴(yán)重影響UI的流暢度,給用戶一種“卡”的壞體驗
4.如果將耗時操作放在主線程:主線程的的UI無法更新,按鈕不可用,必須等耗時操作完成才有反應(yīng)

耗時操作demo 密碼: qugm
- 5.如果將耗時操作放在子線程(后臺線程、非主線程)

四.iOS中多線程的實現(xiàn)方案

五.對多線程開辟的詳細介紹
-
1.pthread的詳細介紹
1.導(dǎo)入: #import <pthread.h>
-
2.開辟子線程(調(diào)用的是一個函數(shù))
pthread_t thread; /* pthread 屬于POSIX 多線程開發(fā)框架 參數(shù) 1:指向線程的指針 2:線程屬性 3:指向函數(shù)的指針 4:傳遞給函數(shù)的參數(shù) 返回值:特別在C語言框架,非常常見 如果是0,表示正確 noErr 如果是非0,表示錯誤代碼 void * (*) (void *) void * demo (void *param) 返回值 函數(shù)指針 參數(shù) void * 等價于 OC id */ NSString *str = @"我是參數(shù)"; int result = pthread_create(&thread, NULL, &run,(__bridge void *)(@"我是參數(shù)")); if (result == 1) { // 可以把1 換為 noErr NSLog(@"OK"); }else{ NSLog(@"error: %d",result); } -
3.調(diào)用子線程
void *run(void *param) { for (NSInteger i = 0; i<50000; i++) { NSLog(@"-----buttonClick-----%zd",i); } return NULL; }
2.NSThread
-
(1).一個NSThread對象就代表一條線程(這種創(chuàng)建方式可以拿到線程的對象以及可以對線程設(shè)置名字,以及獲取主線程)子線程執(zhí)行完任務(wù)就自動死亡
創(chuàng)建、啟動線程 NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil]; [thread start]; //線程一啟動,就會在線程thread中執(zhí)行self的run方法 -
(2).主線程相關(guān)用法
+(NSThread *)mainThread;// 獲得主線程 -(BOOL)isMainThread;// 是否為主線程 +(BOOL)isMainThread; // 是否為主線程 -
(3).其他用法
-
<1>獲得當(dāng)前線程
NSThread *current = [NSThread currentThread]; -
<2>.線程的屬性:名字(
name)(在項目開發(fā)中,可以根據(jù)這個來查找崩潰原因)- (void)setName:(NSString*)n; -(NSString *)name; NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil]; thread. name = @"JK"; [thread start]; -
<3>.線程的屬性:優(yōu)先級(
threadPriority)/* 優(yōu)先級別:0.0 - 1.0;0.5 0.0:最低 1.0:最高 只是保證CPU的調(diào)度的可能性; 多線程目的:將耗時操作放在后臺執(zhí)行 建議:不要在開發(fā)過程,修改優(yōu)先級 */ NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil]; thread. threadPriority = 1; [thread start];
-
-
(4).其他創(chuàng)建線程方式
-
<1>.創(chuàng)建線程后自動啟動線程
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil]; -
<2>.隱式創(chuàng)建并啟動線程(其實也就是在后臺創(chuàng)建子線程)
[self performSelectorInBackground:@selector(run) withObject:nil];
上述2種創(chuàng)建線程方式的優(yōu)缺點
-
優(yōu)點:簡單快捷
缺點:無法對線程進行更詳細的設(shè)置
NSThread的code 密碼: gsq4
- (5).線程的狀態(tài)

-
(6).控制線程狀態(tài)
-
<1>.啟動線程
-(void)start;進入就緒狀態(tài) -> 運行狀態(tài)。當(dāng)線程任務(wù)執(zhí)行完畢,自動進入死亡狀態(tài)
-
<2>.阻塞(暫停)線程
+(void)sleepUntilDate:(NSDate *)date;// 進入阻塞狀態(tài) +(void)sleepForTimeInterval:(NSTimeInterval)ti;// 進入阻塞狀態(tài) -
<3>.強制停止線程
+(void)exit; //進入死亡狀態(tài) 例如: [NSThread exit];//線程強制退出
-
注意:一旦線程停止(死亡)了,就不能再次開啟任務(wù),必須開辟新的線程
-
(7).多線程的安全隱患:資源共享
<1>.1塊資源可能會被多個線程共享,也就是多個線程可能會訪問同一塊資源
<2>. 比如多個線程訪問同一個對象、同一個變量、同一個文件
<3>.當(dāng)多個線程訪問同一塊資源時,很容易引發(fā)數(shù)據(jù)錯亂和數(shù)據(jù)安全問題
第一個例子: 銀行取錢

第二個例子: 售票


-
<4>.安全隱患解決– 互斥鎖 : 加鎖是消耗資源的
安全解決方案 (1).互斥鎖使用格式

-
(2).互斥鎖的優(yōu)缺點
優(yōu)點:能有效防止因多線程搶奪資源造成的數(shù)據(jù)安全問題
缺點:需要消耗大量的CPU資源 (3).互斥鎖的使用前提:多條線程搶奪同一塊資源
-
(4).相關(guān)專業(yè)術(shù)語:線程同步
線程同步的意思是:多條線程在同一條線上執(zhí)行(按順序地執(zhí)行任務(wù))
互斥鎖,就是使用了線程同步技術(shù)
-
(5)、互斥鎖的使用范圍:盡量要小,范圍大,效率就會差。
搶票資源的解決問題
搶票資源的解決問題
六.線程之間的通信
-
<1>.原子和非原子屬性
OC在定義屬性時有nonatomic和atomic兩種選擇atomic:原子屬性,為setter方法加鎖(默認就是atomic)nonatomic:非原子屬性,不會為setter方法加鎖:避免資源消耗
-
<2>.原子和非原子屬性的選擇
-
nonatomic和atomic對比 -
atomic:線程安全,需要消耗大量的資源 -
nonatomic:非線程安全,適合內(nèi)存小的移動設(shè)備
-
-
<3>.iOS開發(fā)的建議
所有屬性都聲明為
nonatomic盡量避免多線程搶奪同一塊資源
盡量將加鎖、資源搶奪的業(yè)務(wù)邏輯交給服務(wù)器端處理,減小移動客戶端的壓力
-
<4>.什么叫做線程間通信?
答:在1個進程中,線程往往不是孤立存在的,多個線程之間需要經(jīng)常進行通信
<5>.線程間通信的體現(xiàn)
(1).1個線程傳遞數(shù)據(jù)給另1個線程
(2).在1個線程中執(zhí)行完特定任務(wù)后,轉(zhuǎn)到另1個線程繼續(xù)執(zhí)行任務(wù)
<6>.線程間通信常用方法

舉個例子:用UIImageView加載圖片
//1.獲取圖片的路徑
NSString *stringPath = @"http://h.hiphotos.baidu.com/image/h%3D360/sign=48771214b08f8c54fcd3c3290a282dee/c9fcc3cec3fdfc0375633742d03f8794a4c22635.jpg";
//開始時間(秒數(shù),從1970年0點0時0分0秒開始算起)
CFTimeInterval begin = CFAbsoluteTimeGetCurrent();
//2.根據(jù)圖片的路徑去下載圖片
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:stringPath]];
//結(jié)束時間
CFTimeInterval end = CFAbsoluteTimeGetCurrent();
NSLog(@"開始時間=%f 結(jié)束時間=%f 消耗時間==%f",begin,end,end -begin);
//3.加載下載好的圖片
self.picture.image = [UIImage imageWithData:data];
獲取時間間隔的方式:
1.第一種獲取時間
NSDate *begin = [NSDate date];
//根據(jù)圖片的路徑去下載圖片
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:stringPath]];
//結(jié)束時間
NSDate *end = [NSDate date];
NSLog(@"開始時間=%@ 結(jié)束時間=%@ 消耗時間==%f",begin,end,[end timeIntervalSinceDate:begin]);
2.第二種獲取時間
//開始時間(秒數(shù),從1970年0點0時0分0秒開始算起)
CFTimeInterval begin = CFAbsoluteTimeGetCurrent();
//2.根據(jù)圖片的路徑去下載圖片
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:stringPath]];
//結(jié)束時間
CFTimeInterval end = CFAbsoluteTimeGetCurrent();
NSLog(@"開始時間=%f 結(jié)束時間=%f 消耗時間==%f",begin,end,end -begin);
-
<7>.子線程下載圖片,在主線程刷新UI
1.跳轉(zhuǎn)刷新UI
[self performSelectorOnMainThread:@selector(refreshUI:) withObject:image waitUntilDone:YES]; /** * 刷新UI */ -(void)refreshUI:(UIImage *)image { //3.加載下載好的圖片 self.picture.image = image; }2.直接不跳轉(zhuǎn)刷新UI
[self.picture performSelector:@selector(setImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];
最后解釋一下方法的最后一個參數(shù)waitUntilDone:YES或者NO的問題
YES:意思是刷新UI之后再走子線程之后的其他任務(wù)
NO:意思是主線程刷新的同時,其他線程也在執(zhí)行任務(wù)

線程通信方式的demo 密碼: 85x4
七.GCD
<1> 什么是GCD ?
GCD全稱是Grand Central Dispatch,可譯為“牛逼的中樞調(diào)度器”,純C語言,提供了非常多強大的函數(shù)-
<2>.GCD的優(yōu)勢
- GCD是蘋果公司為多核的并行運算提出的解決方案
- GCD會自動利用更多的CPU內(nèi)核(比如雙核、四核)
- GCD會自動管理線程的生命周期(創(chuàng)建線程、調(diào)度任務(wù)、銷毀線程)
- 程序員只需要告訴GCD想要執(zhí)行什么任務(wù),不需要編寫任何線程管理代碼
-
<3>.GCD中有2個核心概念:任務(wù)和隊列
- 任務(wù):執(zhí)行什么操作
- 隊列:用來存放任務(wù)
-
<4>.GCD的使用就2個步驟
- 1.定制任務(wù),確定想做的事情
- 2.將任務(wù)添加到隊列中
- GCD會自動將隊列中的任務(wù)取出,放到對應(yīng)的線程中執(zhí)行
- 任務(wù)的取出遵循隊列的FIFO原則:先進先出,后進后出
-
<5>.執(zhí)行任務(wù)
-
GCD中有2個用來執(zhí)行任務(wù)的函數(shù)
1.用同步的方式執(zhí)行任務(wù)(不具備開辟新線程的能力)
dispatch_sync(dispatch_queue_t queue,dispatch_block_t block);
queue:隊列
block:任務(wù)2.用異步的方式執(zhí)行任務(wù)
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
同步和異步的區(qū)別:
同步:只能在當(dāng)前線程中執(zhí)行任務(wù),不具備開啟新線程的能力
異步:可以在新的線程中執(zhí)行任務(wù),具備開啟新線程的能力
-
-
<6>.GCD的隊列可以分為
2大類型1.并發(fā)隊列(
Concurrent Dispatch Queue)(1).可以讓多個任務(wù)并發(fā)(同時)執(zhí)行(自動開啟多個線程同時執(zhí)行任務(wù))
(2).并發(fā)功能只有在異步(dispatch_async)函數(shù)下才有效
2.串行隊列(
Serial Dispatch Queue)- 讓任務(wù)一個接著一個地執(zhí)行(一個任務(wù)執(zhí)行完畢后,再執(zhí)行下一個任務(wù))
-
<7>.有4個術(shù)語比較容易混淆:同步、異步、并發(fā)、串行
-
1.同步和異步主要影響:能不能開啟新的線程
同步:在當(dāng)前線程中執(zhí)行任務(wù),不具備開啟新線程的能力
異步:在新的線程中執(zhí)行任務(wù),具備開啟新線程的能力 -
2.并發(fā)和串行主要影響:任務(wù)的執(zhí)行方式
并發(fā):多個任務(wù)并發(fā)(同時)執(zhí)行
串行:一個任務(wù)執(zhí)行完畢后,再執(zhí)行下一個任務(wù)
-
-
<8>.并發(fā)隊列
GCD默認已經(jīng)提供了全局的并發(fā)隊列,供整個應(yīng)用使用,不需要手動創(chuàng)建
使用dispatch_get_global_queue函數(shù)獲得全局的并發(fā)隊列
dispatch_queue_priority_t priority // 隊列的優(yōu)先級 ,蘋果推薦使用DISPATCH_QUEUE_PRIORITY_DEFAULT
unsigned long flags //此參數(shù)暫時無用,用0即可
dispatch_queue_t dispatch_get_global_queue( dispatch_queue_priority_t priority, unsigned long flags);-
獲得全局并發(fā)隊列
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0); -
全局并發(fā)隊列的優(yōu)先級
#define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高 #define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默認(中) #define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低 #define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后
-
<9>.全局并發(fā)隊列與全局串行隊列獲取方式
-
并發(fā)隊列:創(chuàng)建的2種方式
獲得全局并發(fā)隊列dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 獲取不是全局的并發(fā)隊列 dispatch_queue_t queue = dispatch_queue_create("com.520it.queue", DISPATCH_QUEUE_CONCURRENT); -
串行隊列:創(chuàng)建的2種方式
獲得全局串行隊列dispatch_queue_t queue = dispatch_queue_create("com.520it.queue", DISPATCH_QUEUE_SERIAL); dispatch_queue_t queue = dispatch_queue_create("com.520it.queue", NULL); dispatch_release(queue);非ARC需要釋放手動創(chuàng)建的隊列
-
-
<10>.主隊列
主隊列(跟主線程相關(guān)聯(lián)的隊列)
主隊列是GCD自帶的一種特殊的串行隊列
放在主隊列中的任務(wù),都會放到主線程中執(zhí)行
-
使用
dispatch_get_main_queue()獲得主隊列dispatch_queue_t queue = dispatch_get_main_queue();
<11>.各種隊列的執(zhí)行效果

注意:使用sync函數(shù)往當(dāng)前串行隊列中添加任務(wù),會卡住當(dāng)前的串行隊列
下面寫一個GCD線程阻塞的代碼

-
<12>.GCD主線程與子線程之間的通信
從子線程回到主線程
GCD主線程與子線程之間的通信 -
<13>.GCD里面還有一個來執(zhí)行任務(wù)的函數(shù):
barrier是柵欄-
barrier是柵欄的意思,在它前面的任務(wù)執(zhí)行完之后才執(zhí)行,而且后面的任務(wù)等它執(zhí)行結(jié)束后才去執(zhí)行.
-
-
注意 這個隊列
queue不能是全局的并發(fā)隊列dispatch_queue_t queue = dispatch_queue_create("12235", DISPATCH_QUEUE_CONCURRENT); dispatch_barrier_async(queue, ^{ NSLog(@"您在執(zhí)行barrier"); });
例如下面的代碼:
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
dispatch_queue_t queue = dispatch_queue_create("12235", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"1111");
});
dispatch_async(queue, ^{
NSLog(@"2222");
});
dispatch_barrier_async(queue, ^{
NSLog(@"您在執(zhí)行barrier");
});
dispatch_async(queue, ^{
NSLog(@"3333");
});
dispatch_async(queue, ^{
NSLog(@"4444");
});
}
打印結(jié)果如下

八.iOS常見的延時執(zhí)行有
2種方式
-
<1>.調(diào)用
NSObject的方法2秒后再調(diào)用self的run方法 [self performSelector:@selector(run) withObject:nil afterDelay:2]; -
<2>.使用GCD函數(shù)
2s后的代碼在哪里執(zhí)行是和隊列有關(guān)系的dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ NSLog(@"2s了"); }); -
<3>.NSTimer定時器
NO代表不重復(fù),定時器執(zhí)行完就進行銷毀,如果是
YES就代表每隔2s調(diào)用一次run方法[NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(run) userInfo:nil repeats:NO];
九.一次性代碼
使用dispatch_once函數(shù)能保證某段代碼在程序運行過程中只被執(zhí)行1次
static dispatch_once_t onceToken;
dispatch_once(&onceToken,^{
只執(zhí)行1次的代碼(這里面默認是線程安全的)
});
提醒:一次性代碼慎用,它是程序在運行過程中只執(zhí)行一次
十.剪切利用多線程分析 (剪切是耗時的,要放到子線程里面)

- 1.傳統(tǒng)的剪切方式
#pragma mark 傳統(tǒng)的剪切文件
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
NSString *form = @"/Users/jinqianxiang/Desktop/Form";
NSString *to = @"/Users/jinqianxiang/Desktop/To";
/**
* 創(chuàng)建文件管理對象
*/
NSFileManager *filemanger = [NSFileManager defaultManager];
//獲取要剪切的文件夾里面的每個對象名字
NSArray *array = [filemanger subpathsAtPath:form];
for (NSString *subpath in array) {
//全路徑
NSString *formPath = [form stringByAppendingPathComponent:subpath];
NSString *toPath = [to stringByAppendingPathComponent:subpath];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//剪切(全路徑剪切)
[filemanger moveItemAtPath:formPath toPath:toPath error:nil];
});
}
}
-
2.比較快的剪切方式
-(void)apply { NSString *form = @"/Users/jinqianxiang/Desktop/Form"; NSString *to = @"/Users/jinqianxiang/Desktop/To"; /** * 創(chuàng)建文件管理對象 */ NSFileManager *filemanger = [NSFileManager defaultManager]; //獲取要剪切的文件夾里面的每個對象名字 NSArray *array = [filemanger subpathsAtPath:form]; dispatch_apply(array.count, dispatch_get_global_queue(0, 0), ^(size_t index) { //NSLog(@"456--%@",[NSThread currentThread]); NSString *sunPath = array[index]; //全路徑 NSString *formPath = [form stringByAppendingPathComponent:sunPath]; NSString *toPath = [to stringByAppendingPathComponent:sunPath]; //剪切(全路徑剪切) [filemanger moveItemAtPath:formPath toPath:toPath error:nil]; NSLog(@"---%@---%zd",[NSThread currentThread],index); }); }

文件的剪切demo 密碼: 7edp
十一.GCD隊列組和柵欄(barrier)一樣的效果:確定線程的優(yōu)先級順序
- 有這么1種需求?
- 首先:分別異步執(zhí)行2個耗時的操作
- 其次:等2個異步操作都執(zhí)行完畢后,再回到主線程執(zhí)行操作
如果想要快速高效地實現(xiàn)上述需求,可以考慮用隊列組
dispatch_group_t group = dispatch_group_create();
// 1.
dispatch_group_async(group,dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^{
//執(zhí)行1個耗時的異步操作
});
//2.
dispatch_group_async(group,dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^{
//執(zhí)行1個耗時的異步操作
});
//3.
dispatch_group_notify(group,dispatch_get_main_queue(),^{
//等前面的異步操作都執(zhí)行完畢后,回到主線程...
});
這樣就是1和2執(zhí)行完了才會執(zhí)行3
下面以兩張圖片合成為例:

兩張圖片合成demo 密碼: 3w5f
關(guān)鍵性代碼:
/**
* 3.將兩個圖片合成
*/
dispatch_group_notify(group, queue, ^{
/**
* 開啟新的圖形上下文
*/
UIGraphicsBeginImageContext(CGSizeMake(200, 200));
//繪制圖片
[self.image1 drawInRect:CGRectMake(0, 0, 200, 100)];
//繪制圖片
[self.image2 drawInRect:CGRectMake(0, 100, 200, 100)];
/**
* 取得上下文里面的圖片
*/
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
/**
* 結(jié)束上下文
*/
UIGraphicsEndImageContext();
/**
* 4.進入主隊列加載合成的圖片
*/
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = image;
});
十二.GCD單例模式
-
1.單例模式的作用
可以保證在程序運行過程,一個類只有一個實例,而且該實例易于供外界訪問
從而方便地控制了實例個數(shù),并節(jié)約系統(tǒng)資源
-
2.單例模式的使用場合
- 在整個應(yīng)用程序中,共享一份資源(這份資源只需要創(chuàng)建初始化1次)
-
3.單例模式在ARC\MRC環(huán)境下的寫法有所不同,需要編寫2套不同的代碼
- 可以用宏判斷是否為ARC環(huán)境
#if __has_feature(objc_arc)
//ARC
#else
//MRC
#endif
- 4.GCD單例模式1(比較簡單)
#import "PersonMessages.h"
static PersonMessages *personMessages;
@implementation PersonMessages
+(instancetype)allocWithZone:(struct _NSZone *)zone
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
personMessages = [super allocWithZone:zone];
});
return personMessages;
}
@end

說明:不管是alloc還是allocWithZone 都會調(diào)用allocWithZone
-
5.完整的一個單例(粒)對象的.m包含3個部分
下面還是以PersonMessages類為例
.h里面
#import <Foundation/Foundation.h>
@interface PersonMessages : NSObject
+(instancetype)sharePersonMessages;
@end
.m里面
#import "PersonMessages.h"
@implementation PersonMessages
static PersonMessages *personMessages;
/**
* 1.保證調(diào)用 [ PersonMessages sharePersonMessages] 只會調(diào)用一次init
*
* @return personMessages
*/
+(instancetype)sharePersonMessages
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
personMessages = [[self alloc]init];
});
return personMessages;
}
/**
* 2.外面調(diào)用n次訪問同一個對象
*
* @return personMessages
*/
+(instancetype)allocWithZone:(struct _NSZone *)zone
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
personMessages = [super allocWithZone:zone];
});
return personMessages;
}
/**
* 3.外面進行copy的時候還是同一個對象
*
* @return personMessages;
*/
-(id)copy
{
return personMessages;
}
@end

解釋一下:
1.static:意思防止外面人訪問更改
static PersonMessages *personMessages;
2.once默認是安全的(已經(jīng)加鎖), onceToken是來記錄訪問過block
dispatch_once(&onceToken, ^{
personMessages = [super allocWithZone:zone];
});
- 6.對單例(粒)的一種封裝,只需要傳類名
下面我對單例類進行封裝解釋

GCD單例的封裝 密碼: yxan
傳統(tǒng)單例的封裝 密碼: xxne
使用方法:圖中3步
.h

.m

十三.NSOperation
-
<1>.NSOperation的作用
配合使用
NSOperation和NSOperationQueue也能實現(xiàn)多線程編程 -
<2>.NSOperation和NSOperationQueue實現(xiàn)多線程的具體步驟
- (1).先將需要執(zhí)行的操作封裝到一個
NSOperation對象中 - (2).然后將
NSOperation對象添加到NSOperationQueue中 - (3).系統(tǒng)會自動將
NSOperationQueue中的NSOperation取出來 - (4).將取出的
NSOperation封裝的操作放到一條新線程中執(zhí)行
- (1).先將需要執(zhí)行的操作封裝到一個
注意:
- <3>.NSOperation的子類
NSOperation是個抽象類,并不具備封裝操作的能力,必須使用它的子類
-
<4>.使用NSOperation子類的方式有3種
(1).
NSInvocationOperation(2).
NSBlockOperation(3).自定義子類繼承
NSOperation,實現(xiàn)內(nèi)部相應(yīng)的方法
<5>.NSInvocationOperation
創(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方法 -
3.注意:
默認情況下,調(diào)用了start方法后并不會開一條新線程去執(zhí)行操作,而是在當(dāng)前線程同步執(zhí)行操作
只有將NSOperation放到一個NSOperationQueue中,才會異步執(zhí)行操作(也就是進入子線程)

- <6>.NSBlockOperation
** 一張圖**

NSOperationQueue的作用
- 如果將`NSOperation`添加到`NSOperationQueue`(操作隊列)中,系統(tǒng)會**自動異步執(zhí)行**NSOperation中的操作:相當(dāng)于`start`
另外還可以直接用隊列添加Operation
/**
* 1.創(chuàng)建隊列
*/
NSOperationQueue *operationQueue = [NSOperationQueue new];
/**
* 特殊情況,隊列直接添加operation(添加任務(wù))
*/
[operationQueue addOperationWithBlock:^{
NSLog(@"%@",[NSThread currentThread]);
}];
- <7>.自定義創(chuàng)建NSOperation


注意:放到隊列之后,它會自動開啟start
NSOperation創(chuàng)建的3中形式 密碼: qaex
-
<8>.最大并發(fā)數(shù)
- 并發(fā)數(shù)是指:同時執(zhí)行的任務(wù)數(shù),比如,同時開3個線程執(zhí)行3個任務(wù),并發(fā)數(shù)就是3
-
最大并發(fā)數(shù)也就是
NSOperationQueue的屬性
maxConcurrentOperationCount,當(dāng)=1時就是串行隊列了/** * 1.創(chuàng)建隊列 */ NSOperationQueue *operationQueue = [NSOperationQueue new]; /** * 2.設(shè)置最大并發(fā)數(shù) */ operationQueue.maxConcurrentOperationCount = 1;最大并發(fā)數(shù)只要大于1,是幾就代表幾條線程同時執(zhí)行,執(zhí)行完的線程有可能會被重復(fù)利用
最大并發(fā)數(shù) 密碼: xxwk
-
<9>.隊列的取消、暫停、恢復(fù)(操作對象是:隊列
NSOperationQueue)- 取消隊列的所有操作
-(void)cancelAllOperations;
提示:也可以調(diào)用NSOperation的-(void)cancel方法取消單個操作
- 取消隊列的所有操作
提示:在自定義的NSOperation的-(void)main{}方法里面,如果線程取消了,我們應(yīng)該進行判斷,防止性能消耗

-
暫停和恢復(fù)隊列
@property (getter=isSuspended) BOOL suspended; -(void)setSuspended:(BOOL)b;// YES代表暫停隊列,NO代表恢復(fù)隊列 -(BOOL)isSuspended;
舉個例子:

NSOperationQueue的掛起和取消 密碼: 58is
-
<10>.操作依賴
注意:依賴必須添加到NSOperation對象添加到隊列之前進行依賴
NSOperation之間可以設(shè)置依賴來保證執(zhí)行順序
-
比如一定要讓操作A執(zhí)行完后,才能執(zhí)行操作B,可以這么寫
[operationB addDependency:operationA];// 操作B依賴于操作A 可以在不同queue的NSOperation之間創(chuàng)建依賴關(guān)系(添加的是NSOperation的對象,不管是不是在同一個隊列:也可以說能夠跨隊列)

-
<11>.操作的監(jiān)聽
可以監(jiān)聽一個操作的執(zhí)行完畢-(void(^)(void))completionBlock; -(void)setCompletionBlock:(void(^)(void))block;

最后:提一下圖片的緩存機制
內(nèi)存緩存
沙盒緩存
-
硬盤緩存
/** * 1.內(nèi)存緩存圖片 */ @property(nonatomic,strong) NSMutableDictionary *dataDictionary; NSString *stringImage = [NSString stringWithFormat:@"%@",self.plist[indexPath.row]]; //先從內(nèi)存緩存中取出圖片 UIImage *image = self.dataDictionary[stringImage]; if (image) {//內(nèi)存有圖片 cell.imageCellPicture.image = image; }else{// 內(nèi)存中沒有圖片 dispatch_async(dispatch_get_global_queue(0, 0), ^{ //獲取Library/Caches 文件夾 NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject]; //獲取文件名(也就是取得路徑的最后一個路徑) NSString *fileName = [stringImage lastPathComponent]; //計算出全路徑 NSString *filePath = [cachesPath stringByAppendingPathComponent:fileName]; //取出圖片 NSData *data = [NSData dataWithContentsOfFile:filePath]; if(data){//直接利用沙盒中圖片 dispatch_async(dispatch_get_main_queue(), ^{ cell.imageCellPicture.image = [UIImage imageWithData:data]; }); //存到字典中 self.dataDictionary[stringImage] = cell.imageCellPicture.image; }else { // 下載圖片 data = [NSData dataWithContentsOfURL:[NSURL URLWithString:stringImage]]; UIImage *image1 = [UIImage imageWithData:data]; dispatch_async(dispatch_get_main_queue(), ^{ cell.imageCellPicture.image = image1; }); // 存到字典中 self.dataDictionary[stringImage] = cell.imageCellPicture.image; // 將圖片文件數(shù)據(jù)寫入沙盒中 [data writeToFile:filePath atomically:YES]; } }); }
圖片緩存代碼<不完善> 密碼: wjhv

完善的圖片緩存 密碼: gr3k

最后再總結(jié)一下隊列類型
GCD 的隊列類型
-
1.并發(fā)隊列
自己創(chuàng)建的
-
全局
并發(fā)隊列:創(chuàng)建的2種方式
獲得全局并發(fā)隊列 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 獲取不是全局的并發(fā)隊列 dispatch_queue_t queue = dispatch_queue_create("com.520it.queue", DISPATCH_QUEUE_CONCURRENT);
-
2.串行隊列
主隊列
-
自己創(chuàng)建的
串行隊列:創(chuàng)建的2種方式 獲得全局串行隊列 dispatch_queue_t queue = dispatch_queue_create("com.520it.queue", DISPATCH_QUEUE_SERIAL); dispatch_queue_t queue = dispatch_queue_create("com.520it.queue", NULL); dispatch_release(queue);非ARC需要釋放手動創(chuàng)建的隊列
NSOperationQueue隊列類型
-
主隊列
- [NSOperationQueue mainQueue];
- 凡是添加到主隊列中的任務(wù)NSOperation,都會到主線程中執(zhí)行
-
非主隊列(其他隊列)
- [[NSOperationQueue alloc]init];
- 同時包含了:串行,并發(fā)功能
- 添加到這種隊列中的任務(wù)(NSOperation),就會自動放到主線程中執(zhí)行
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
相關(guān)閱讀更多精彩內(nèi)容
- 原文:http://www.cocoachina.com/ios/20170707/19769.html 本文主要...
- 偶然機會接觸劉未鵬的《暗時間》 ,發(fā)現(xiàn)原來心理學(xué)并不是以往狹隘的觀念。打開豆瓣搜羅了一大堆評分較高的心...


