首先,在瞭解多線程以前要了解什麼是進程,什麼是線程
進程是指在系統中正在運行的一個應用程序。每一個進程之間是獨立的,每一個進程均運行在其專用且受保護的內存空間內。
一個進程要想執行任務,必須得有至少一個線程,線程是進程的基本執行單元,一個進程(程序)的全部任務都在線程中執行。
一個線程中任務的執行是串行的,若是要在1個線程中執行多個任務,那麼只能一個一個地按順序執行這些任務。也就是說,在同一時間內,1個線程只能執行1個任務。html
什麼是多線程?
即在一個進程(程序)中能夠開啓多條線程,每條線程能夠並行(同時)執行不一樣的任務。ios
並行即同時執行。好比同時開啓3條線程分別下載3個文件(分別是文件A、文件B、文件C)。程序員
在同一時間裏,CPU只能處理一條線程,只有一條線程在工做(執行)。多線程併發(同時)執行,實際上是CPU快速地在多條線程之間快速切換,若是CPU調度線程的時間足夠快,就形成了多線程併發執行的假象。設計模式
多線程優缺點
優勢:安全
- 能適當提升程序的執行效率。
- 能適當提升資源利用率(CPU、內存利用率)
缺點:網絡
- 開啓線程須要佔用必定的內存空間(默認狀況下,主線程佔用1M,子線程佔用512KB),若是開啓大量的線程,會佔用大量的內存空間,下降程序的性能。
- 線程越多,CPU在調度線程上的開銷就越大。
- 程序設計更加複雜:好比線程之間的通訊、多線程的數據共享
開啓多線程的方式
當一個iOS程序運行後,默認會開啓1條線程,稱爲「主線程」或「UI線程」;它的做用就是刷新顯示UI,處理UI事件。多線程
- 不要將耗時操做放到主線程中去處理,會卡住線程。
- 和UI相關的刷新操做必須放到主線程中進行處理。
pthread
特色:併發
- 一套通用的多線程API
- 適用於UnixLinuxWindows等系統
- 跨平臺可移植
使用難度:*****
使用語言:c語言
使用頻率:幾乎不用
線程生命週期:由程序員進行管理app
使用說明:pthread的基本使用(須要包含頭文件)異步
具體實現代碼:
1 2 3 4 5 6 7 8 9
|
pthread_t thread;
pthread_create(&thread, NULL, run, NULL);
|
NSThread
特色:
- 使用更加面向對象
- 簡單易用,可直接操做線程對象
使用難度:***
使用語言:OC語言
使用頻率:偶爾使用
線程生命週期:由程序員進行管理
建立線程
第一種建立線程的方式:alloc init
1 2 3 4 5 6 7 8 9
|
NSThread *newThread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
[newThread start];
|
第二種建立線程的方式:分離出一條子線程
1 2 3 4 5 6 7
|
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
|
第三種建立線程的方式:後臺線程
1 2 3 4 5 6
|
[self performSelectorInBackground:@selector(run) withObject:nil];
|
設置線程的屬性
1 2 3 4 5
|
thread.name = @"線程A";
thread.threadPriority = 1.0;
|
線程的狀態
線程的各類狀態:新建-就緒-運行-阻塞-死亡
1 2 3 4 5
|
[NSThread exit]; [NSThread sleepForTimeInterval:2.0]; [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.0]];
|
線程安全
前提:多個線程訪問同一塊資源會發生數據安全問題
解決方案:加互斥鎖
相關代碼:@synchronized(self){}
專業術語-線程同步
原子和非原子屬性(是否對setter方法加鎖)
線程間通訊
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
-(void)touchesBegan:(nonnull NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event { [NSThread detachNewThreadSelector:@selector(downloadImage) toTarget:self withObject:nil]; } -(void)downloadImage { NSURL *url = [NSURL URLWithString:@"http://http://p2.wmpic.me/article/2016/03/17/1458205813_mEsdeUon.jpg"]; NSData *data = [NSData dataWithContentsOfURL:url]; UIImage *image = [UIImage imageWithData:data];
[self.imageView performSelector:@selector(setImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES]; }
|
如何計算代碼段的執行時間
1 2 3 4 5 6 7 8 9 10 11
|
NSDate *start = [NSDate date];
NSData *data = [NSData dataWithContentsOfURL:url]; NSDate *end = [NSDate date]; NSLog(@"第二步操做花費的時間爲%f",[end timeIntervalSinceDate:start]);
CFTimeInterval start = CFAbsoluteTimeGetCurrent(); NSData *data = [NSData dataWithContentsOfURL:url]; CFTimeInterval end = CFAbsoluteTimeGetCurrent(); NSLog(@"第二步操做花費的時間爲%f",end - start);
|
GCD
特色:
- 旨在替代NSThread等線程技術
- 充分利用設備的多核(自動)
使用難度:**
使用語言:C語言
使用頻率:常用
線程生命週期:自動管理
GCD基本使用
異步函數+併發隊列:開啓多條線程,併發執行任務
異步函數+串行隊列:開啓一條線程,串行執行任務
同步函數+併發隊列:不開線程,串行執行任務
同步函數+串行隊列:不開線程,串行執行任務
異步函數+主隊列:不開線程,在主線程中串行執行任務
同步函數+主隊列:不開線程,串行執行任務(注意死鎖發生)
注意同步函數和異步函數在執行順序上面的差別
GCD線程間通訊
建立串行隊列
1 2 3 4 5
|
dispatch_queue_t queue2 = dispatch_queue_create(const char *label, DISPATCH_QUEUE_SERIAL);
|
建立併發隊列
1 2 3 4 5
|
dispatch_queue_t queue2 = dispatch_queue_create(const char *label, DISPATCH_QUEUE_CONCURRENT);
|
建立全局隊列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{ NSURL *url = [NSURL URLWithString:@"http://p2.wmpic.me/article/2016/03/17/1458205813_mEsdeUon.jpg"]; NSData *data = [NSData dataWithContentsOfURL:url]; UIImage *image = [UIImage imageWithData:data];
NSLog(@"下載操做所在的線程--%@",[NSThread currentThread]);
dispatch_async(dispatch_get_main_queue(), ^{ self.imageView.image = image; NSLog(@"刷新UI---%@",[NSThread currentThread]); }); });
|
GCD其它經常使用函數
柵欄函數(控制任務的執行順序)
1 2 3
|
dispatch_barrier_async(queue, ^{ NSLog(@"--dispatch_barrier_async-"); });
|
延遲執行(延遲·控制在哪一個線程執行)
1 2 3
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"---%@",[NSThread currentThread]); });
|
一次性代碼(注意不能放到懶加載)
1 2 3 4 5 6 7 8 9
|
-(void)once { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSLog(@"-----"); }); }
|
快速迭代(開多個線程併發完成迭代操做)
1 2 3
|
dispatch_apply(subpaths.count, queue, ^(size_t index) {
});
|
隊列組(同柵欄函數)
1 2 3 4 5 6
|
dispatch_group_t group = dispatch_group_create(); dispatch_group_notify(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);
|
注意事項:
在iOS6.0以前,在GCD中凡是使用了帶Crearte和retain的函數在最後都須要作一次release操做。而主隊列和全局併發隊列不須要咱們手動release。在iOS6.0以後GCD已經被歸入到了ARC的內存管理範疇中,即使是使用retain或者create函數建立的對象也再也不須要開發人員手動釋放,咱們像對待普通OC對象同樣對待GCD就OK。
在使用柵欄函數的時候,蘋果官方明確規定柵欄函數只有在和使用create函數本身的建立的併發隊列一塊兒使用的時候纔有效
NSOperation
特色:
- 基於GCD(底層是GCD)
- 比GCD多了一些更簡單實用的功能
- 使用更加面向對象
使用難度:**
使用語言:OC語言
使用頻率:常用
線程生命週期:自動管理
NSOperation是對GCD的包裝,其自己是隻是抽象類,只有它的子類(三個子類分別是:NSBlockOperation、NSInvocationOperation以及自定義繼承自NSOperation的類)才能建立對象
NSInvocationOperation
1 2 3 4 5 6 7 8 9
|
NSInvocationOperation *operation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(run) object:nil];
[operation start];
|
NSBlockOperation
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"---download1--%@",[NSThread currentThread]); }];
[operation addExecutionBlock:^{ NSLog(@"---download2--%@",[NSThread currentThread]); }]; [operation addExecutionBlock:^{ NSLog(@"---download3--%@",[NSThread currentThread]); }];
[operation start];
|
自定義NSOperation
1 2 3 4 5 6 7 8 9 10 11
|
-(void)main { NSLog(@"--main--%@",[NSThread currentThread]); }
TYOperation *op = [[TYOperation alloc]init];
[op start];
|
NSOperationQueue
NSOperation中的兩種隊列
- 主隊列 經過mainQueue得到,凡是放到主隊列中的任務都將在主線程執行
- 非主隊列 直接alloc init出來的隊列。非主隊列同時具有了併發和串行的功能,經過設置最大併發數屬性來控制任務是併發執行仍是串行執行
NSInvocationOperation
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
- (void)invocation {
NSOperationQueue *queue = [[NSOperationQueue alloc]init]; NSInvocationOperation *op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download1) object:nil]; NSInvocationOperation *op2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download2) object:nil]; NSInvocationOperation * 大專欄 iOS之多線程op3 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download3) object:nil]; [queue addOperation:op1]; [queue addOperation:op2]; [queue addOperation:op3]; }
|
NSBlockOperation
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
|
- (void)freeBlock { NSOperationQueue *queue = [[NSOperationQueue alloc]init]; NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"1----%@",[NSThread currentThread]); }]; NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"2----%@",[NSThread currentThread]); }]; [op2 addExecutionBlock:^{ NSLog(@"3----%@",[NSThread currentThread]); }]; [op2 addExecutionBlock:^{ NSLog(@"4----%@",[NSThread currentThread]); }]; [queue addOperation:op1]; [queue addOperation:op2] [queue addOperationWithBlock:^{ NSLog(@"5----%@",[NSThread currentThread]); }]; }
|
自定義NSOperation
1 2 3 4 5 6 7 8 9 10 11 12 13
|
-(void)freeOperation {
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
TYOperation *op1 = [[TYOperation alloc]init]; TYOperation *op2 = [[TYOperation alloc]init];
[queue addOperation:op1]; [queue addOperation:op2]; }
|
NSOperation其它用法
設置最大併發數[最大併發數關係着隊列是串行仍是並行]
建立隊列
1
|
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
|
設置最大併發數
- 該屬性須要在任務添加到隊列中以前進行設置
- 該屬性控制隊列是串行執行仍是併發執行
- 若是最大併發數等於1,那麼該隊列是串行的,若是大於1那麼是並行的
4.系統的最大併發數有個默認的值,爲-1,若是該屬性設置爲0,那麼不會執行任何任務
1
|
queue.maxConcurrentOperationCount = 2;
|
暫停和恢復以及取消
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
|
if (self.queue.isSuspended) { self.queue.suspended = NO; }else{ self.queue.suspended = YES; }
[self.queue cancelAllOperations]; ---------自定義NSOperation取消操做-------------------------- -(void)main { for (int i = 0; i<1000; i++) { NSLog(@"任務1-%d--%@",i,[NSThread currentThread]); } NSLog(@"+++++++++++++++++++++++++++++++++");
if (self.isCancelled) { return; }
for (int i = 0; i<1000; i++) { NSLog(@"任務1-%d--%@",i,[NSThread currentThread]); }
NSLog(@"+++++++++++++++++++++++++++++++++"); }
|
NSOperation實現線程間通訊
開子線程下載圖片
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[queue addOperationWithBlock:^{ NSURL *url = [NSURL URLWithString:@"http://p2.wmpic.me/article/2016/03/17/1458205813_mEsdeUon.jpg"]; NSData *data = [NSData dataWithContentsOfURL:url]; UIImage *image = [UIImage imageWithData:data]; NSLog(@"下載圖片操做--%@",[NSThread currentThread]); [[NSOperationQueue mainQueue] addOperationWithBlock:^{ self.imageView.image = image; NSLog(@"刷新UI操做---%@",[NSThread currentThread]); }]; }];
|
下載多張圖片合成綜合案例(設置操做依賴)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
|
- (void)download { NSOperationQueue *queue = [[NSOperationQueue alloc]init]; NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{ NSURL *url = [NSURL URLWithString:@"http://p2.wmpic.me/article/2016/03/14/1457926891_nZGraHTj.jpg"]; NSData *data = [NSData dataWithContentsOfURL:url]; self.image1 = [UIImage imageWithData:data]; }]; NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{ NSURL *url = [NSURL URLWithString:@"http://p3.wmpic.me/article/2016/01/08/1452222281_PmFnXZHU.jpg"]; NSData *data = [NSData dataWithContentsOfURL:url]; self.image2 = [UIImage imageWithData:data]; }];
NSBlockOperation *combine = [NSBlockOperation blockOperationWithBlock:^{
UIGraphicsBeginImageContext(CGSizeMake(200, 200));
[self.image1 drawInRect:CGRectMake(0, 0, 200, 100)];
[self.image2 drawInRect:CGRectMake(0, 100, 200, 100)];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[[NSOperationQueue mainQueue]addOperationWithBlock:^{ self.imageView.image = image; NSLog(@"刷新UI---%@",[NSThread currentThread]); }];
}];
[combine addDependency:op1]; [combine addDependency:op2];
[queue addOperation:op1]; [queue addOperation:op2]; [queue addOperation:combine]; }
|
單例設計模式
iOS開發多種設計模式之一—-單例模式
什麼是單例
在程序運行過程,一個類有且只有一個實例對象
使用場合
在整個應用程序中,共享一份資源(這份資源只須要建立初始化1次)
在不一樣的內存管理機制下實現單例:
ARC實現單例
步驟:
- 在類的內部提供一個static修飾的全局變量
- 提供一個類方法,方便外界訪問
- 重寫+allocWithZone方法,保證永遠都只爲單例對象分配一次內存空間
- 嚴謹起見,重寫-copyWithZone方法和-MutableCopyWithZone方法
代碼實現:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
|
static TYSingleTools *_instance;
+(instancetype)shareTools { return [[self alloc]init]; }
+(instancetype)allocWithZone:(struct _NSZone *)zone {
@synchronized(self) { if (_instance == nil) { _instance = [super allocWithZone:zone]; } } return _instance; }
-(nonnull id)copyWithZone:(nullable NSZone *)zone {
return _instance; } -(nonnull id)mutableCopyWithZone:(nullable NSZone *)zone { return _instance; }
|
MRC實現單例
步驟:
- 在類的內部提供一個static修飾的全局變量
- 提供一個類方法,方便外界訪問
- 重寫+allocWithZone方法,保證永遠都只爲單例對象分配一次內存空間
- 嚴謹起見,重寫-copyWithZone方法和-MutableCopyWithZone方法
- 重寫release和retain方法
- 建議在retainCount方法中返回一個最大值(有經驗的程序員經過打印retainCount這個值能夠猜到這是一個單例)
配置MRC環境知識:
- 注意ARC不是垃圾回收機制,是編譯器特性
- 配置MRC環境:build setting ->搜索automatic ref->修改成NO
代碼實現:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
|
static TYSingleTools *_instance;
+(instancetype)shareTools { return [[self alloc]init]; }
+(instancetype)allocWithZone:(struct _NSZone *)zone {
@synchronized(self) { if (_instance == nil) { _instance = [super allocWithZone:zone]; } } return _instance; }
-(nonnull id)copyWithZone:(nullable NSZone *)zone {
return _instance; } -(nonnull id)mutableCopyWithZone:(nullable NSZone *)zone { return _instance; }
-(oneway void)release { } -(instancetype)retain { return _instance; }
-(NSUInteger)retainCount { return MAXFLOAT; }
|
- 忽略ARC和MRC的單例通用版本
可使用條件編譯來判斷當前項目環境是ARC仍是MRC,從而實現一份代碼在不一樣的內存管理機制下均可以實現單例。
1 2 3 4 5 6
|
條件編譯: #if __has_feature(objc_arc) //若是是ARC,那麼就執行這裏的代碼1 #else //若是不是ARC,那麼就執行代理的代碼2 #endif
|
注意:單例是不能夠用繼承的。
參考資料