進程是指在系統中正在運行的一個應用程序
每一個進程之間是獨立的,每一個進程均運行在其專用且受保護的內存空間內程序員
1個進程要想執行任務,必須得有線程(每1個進程至少要有1條線程,稱爲主線程)
一個進程(程序)的全部任務都在線程中執行安全
1.線程是CPU調用(執行任務)的最小單位。
2.進程是CPU分配資源的最小單位。
3.一個進程中至少要有一個線程。
4.同一個進程內的線程共享進程的資源。數據結構
1個線程中任務的執行是串行的
若是要在1個線程中執行多個任務,那麼只能一個一個地按順序執行這些任務
也就是說,在同一時間內,1個線程只能執行1個任務多線程
1個進程中能夠開啓多條線程,每條線程能夠並行(同時)執行不一樣的任務
多線程技術能夠提升程序的執行效率併發
同一時間,CPU只能處理1條線程,只有1條線程在工做(執行),多線程併發(同時)執行,實際上是CPU快速地在多條線程之間調度(切換),若是CPU調度線程的時間足夠快,就形成了多線程併發執行的假象。
那麼若是線程很是很是多,會發生什麼狀況?
CPU會在N多線程之間調度,CPU會累死,消耗大量的CPU資源,同時每條線程被調度執行的頻次也會會下降(線程的執行效率下降)。
所以咱們通常只開3-5條線程。app
多線程的優勢
能適當提升程序的執行效率
能適當提升資源利用率(CPU、內存利用率)
多線程的缺點
建立線程是有開銷的,iOS下主要成本包括:內核數據結構(大約1KB)、棧空間(子線程512KB、主線程1MB,也可使用-setStackSize:設置,但必須是4K的倍數,並且最小是16K),建立線程大約須要90毫秒的建立時間
若是開啓大量的線程,會下降程序的性能,線程越多,CPU在調度線程上的開銷就越大。
程序設計更加複雜:好比線程之間的通訊、多線程的數據共享等問題。異步
主線程的主要做用
顯示\刷新UI界面
處理UI事件(好比點擊事件、滾動事件、拖拽事件等)
主線程的使用注意
別將比較耗時的操做放到主線程中
耗時操做會卡住主線程,嚴重影響UI的流暢度,給用戶一種「卡」的壞體驗
將耗時操做放在子線程中執行,提升程序的執行效率async
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { //建立線程 pthread_t thread; /* 第一個參數pthread_t *restrict:線程對象 第二個參數const pthread_attr_t *restrict:線程屬性 第三個參數void *(*)(void *) :指向函數的指針 第四個參數void *restrict:函數的參數 */ pthread_create(&thread, NULL,run ,NULL); } //void *(*)(void *) void *run(void *param) { for (NSInteger i =0 ; i<10000; i++) { NSLog(@"%zd--%@-",i,[NSThread currentThread]); } return NULL; }
// 方法一:建立線程,須要本身開啓線程 NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(run) object:nil]; // 開啓線程 [thread start]; // 方法二:建立線程後自動啓動線程 [NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil]; // 方法三:隱式建立並啓動線程 [self performSelectorInBackground:@selector(run) withObject:nil];
後面兩種方法都不用咱們開啓線程,相對方便快捷,可是沒有辦法拿到子線程對象,沒有辦法對子線程進行更詳細的設置,例如線程名字和優先級等。函數
// 獲取當前線程 + (NSThread *)currentThread; // 建立啓動線程 + (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument; // 判斷是不是多線程 + (BOOL)isMultiThreaded; // 線程休眠 NSDate 休眠到何時 + (void)sleepUntilDate:(NSDate *)date; // 線程休眠時間 + (void)sleepForTimeInterval:(NSTimeInterval)ti; // 結束/退出當前線程 + (void)exit; // 獲取當前線程優先級 + (double)threadPriority; // 設置線程優先級 默認爲0.5 取值範圍爲0.0 - 1.0 // 1.0優先級最高 // 設置優先級 + (BOOL)setThreadPriority:(double)p; // 獲取指定線程的優先級 - (double)threadPriority NS_AVAILABLE(10_6, 4_0); - (void)setThreadPriority:(double)p NS_AVAILABLE(10_6, 4_0); // 設置線程的名字 - (void)setName:(NSString *)n NS_AVAILABLE(10_5, 2_0); - (NSString *)name NS_AVAILABLE(10_5, 2_0); // 判斷指定的線程是不是 主線程 - (BOOL)isMainThread NS_AVAILABLE(10_5, 2_0); // 判斷當前線程是不是主線程 + (BOOL)isMainThread NS_AVAILABLE(10_5, 2_0); // reports whether current thread is main // 獲取主線程 + (NSThread *)mainThread NS_AVAILABLE(10_5, 2_0); - (id)init NS_AVAILABLE(10_5, 2_0); // designated initializer // 建立線程 - (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument NS_AVAILABLE(10_5, 2_0); // 指定線程是否在執行 - (BOOL)isExecuting NS_AVAILABLE(10_5, 2_0); // 線程是否完成 - (BOOL)isFinished NS_AVAILABLE(10_5, 2_0); // 線程是否被取消 (是否給當前線程發過取消信號) - (BOOL)isCancelled NS_AVAILABLE(10_5, 2_0); // 發送線程取消信號的 最終線程是否結束 由 線程自己決定 - (void)cancel NS_AVAILABLE(10_5, 2_0); // 啓動線程 - (void)start NS_AVAILABLE(10_5, 2_0); // 線程主函數 在線程中執行的函數 都要在-main函數中調用,自定義線程中重寫-main方法 - (void)main NS_AVAILABLE(10_5, 2_0); // thread body metho
啓動線程 - (void)start; // 進入就緒狀態 -> 運行狀態。當線程任務執行完畢,自動進入死亡狀態 阻塞(暫停)線程 + (void)sleepUntilDate:(NSDate *)date; + (void)sleepForTimeInterval:(NSTimeInterval)ti; // 進入阻塞狀態 強制中止線程 + (void)exit; // 進入死亡狀態
多線程安全隱患的緣由:1塊資源可能會被多個線程共享,也就是多個線程可能會訪問同一塊資源,好比多個線程訪問同一個對象、同一個變量、同一個文件。
那麼當多個線程訪問同一塊資源時,很容易引起數據錯亂和數據安全問題。性能
@synchronized(鎖對象) { // 須要鎖定的代碼 }
互斥鎖的使用前提:多條線程搶奪同一塊資源時
注意:鎖定1份代碼只用1把鎖,用多把鎖是無效的
互斥鎖的優缺點
優勢:能有效防止因多線程搶奪資源形成的數據安全問題
缺點:須要消耗大量的CPU資源
下面經過一個售票實例來看一下線程安全的重要性
#import "ViewController.h" @interface ViewController () @property(nonatomic,strong)NSThread *thread01; @property(nonatomic,strong)NSThread *thread02; @property(nonatomic,strong)NSThread *thread03; @property(nonatomic,assign)NSInteger numTicket; //@property(nonatomic,strong)NSObject *obj; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // 總票數爲30 self.numTicket = 30; self.thread01 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil]; self.thread01.name = @"售票員01"; self.thread02 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil]; self.thread02.name = @"售票員02"; self.thread03 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil]; self.thread03.name = @"售票員03"; } -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { [self.thread01 start]; [self.thread02 start]; [self.thread03 start]; } // 售票 -(void)saleTicket { while (1) { // 建立對象 // self.obj = [[NSObject alloc]init]; // 鎖對象,自己就是一個對象,因此self就能夠了 // 鎖定的時候,其餘線程沒有辦法訪問這段代碼 @synchronized (self) { // 模擬售票時間,咱們讓線程休息0.05s [NSThread sleepForTimeInterval:0.05]; if (self.numTicket > 0) { self.numTicket -= 1; NSLog(@"%@賣出了一張票,還剩下%zd張票",[NSThread currentThread].name,self.numTicket); }else{ NSLog(@"票已經賣完了"); break; } } } } @end
當沒有加互斥鎖的時候咱們看一下輸出
咱們發現第29張,第27張都被銷售了3次,這顯然是不容許的,這就是數據錯亂,那麼當咱們加上互斥鎖時,其鎖定的時候其餘線程沒有辦法訪問鎖定的內容,等其訪問完畢以後,其餘線程才能夠訪問,咱們愛來看一下輸出
此時就不會出現同一張票被屢次出售的數據錯亂的狀況了。
什麼叫作線程間通訊
在1個進程中,線程每每不是孤立存在的,多個線程之間須要常常進行通訊,例如咱們在子線程完成下載圖片後,回到主線程刷新UI顯示圖片
線程間通訊的體現
1個線程傳遞數據給另1個線程
在1個線程中執行完特定任務後,轉到另1個線程繼續執行任務
線程間通訊經常使用的方法
// 返回主線程 - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait; // 返回指定線程 - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;
下面咱們經過一個實例看一下線程之間的通訊
#import "ViewController.h" @interface ViewController () @property (weak, nonatomic) IBOutlet UIImageView *imageView; @end @implementation ViewController -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { [NSThread detachNewThreadSelector:@selector(donwLoadImage) toTarget:self withObject:nil]; } -(void)donwLoadImage { // 獲取圖片url地址 http://www.itunes123.com/uploadfile/2016/0421/20160421014340186.jpg NSURL *url = [NSURL URLWithString:@"http://www.itunes123.com/uploadfile/2016/0421/20160421014340186.jpg"]; // 下載圖片二進制文件 NSData *data = [NSData dataWithContentsOfURL:url]; // 將圖片二進制文件轉化爲image; UIImage *image = [UIImage imageWithData:data]; // 參數 waitUntilDone 是否等@selector(showImage:) 執行完畢之後再執行下面的操做 YES :等 NO:不等 // 返回主線程顯示圖片 // [self performSelectorOnMainThread:@selector(showImage:) withObject:image waitUntilDone:YES]; // self.imageView 也能夠直接調用這個方法 直接選擇 setImage方法,傳入參數image便可 // [self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:YES]; // 返回特定的線程,[NSThread mainThread] 得到主線程 [self performSelector:@selector(showImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES]; } -(void)showImage:(UIImage *)image { self.imageView.image = image; } @end
GCD的全稱是Grand Central Dispatch,是純C語言,提供了很是多強大的函數
GCD的優點
GCD是蘋果公司爲多核的並行運算提出的解決方案
GCD會自動利用更多的CPU內核(好比雙核、四核)
GCD會自動管理線程的生命週期(建立線程、調度任務、銷燬線程)
程序員只須要告訴GCD想要執行什麼任務,不須要編寫任何線程管理代碼
GCD中有2個核心概念:任務和隊列
任務:執行什麼操做,任務有兩種執行方式: 同步函數 和 異步函數,他們之間的區別是
同步:只能在當前線程中執行任務,不具有開啓新線程的能力,任務馬上立刻執行,會阻塞當前線程並等待 Block中的任務執行完畢,而後當前線程纔會繼續往下運行
異步:能夠在新的線程中執行任務,具有開啓新線程的能力,但不必定會開新線程,當前線程會直接往下執行,不會阻塞當前線程
隊列:用來存聽任務,分爲串行隊列 和 並行隊列
串行隊列(Serial Dispatch Queue)
讓任務一個接着一個地執行(一個任務執行完畢後,再執行下一個任務)
併發隊列(Concurrent Dispatch Queue)
可讓多個任務併發(同時)執行(自動開啓多個線程同時執行任務)
併發功能只有在異步(dispatch_async)函數下才有效
GCD的使用就2個步驟
定製任務
肯定想作的事情
將任務添加到隊列中
GCD會自動將隊列中的任務取出,放到對應的線程中執行
任務的取出遵循隊列的FIFO原則:先進先出,後進後出
// 第一個參數const char *label : C語言字符串,用來標識 // 第二個參數dispatch_queue_attr_t attr : 隊列的類型 // 併發隊列:DISPATCH_QUEUE_CONCURRENT // 串行隊列:DISPATCH_QUEUE_SERIAL 或者 NULL dispatch_queue_t queue = dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);
建立併發隊列
dispatch_queue_t queue = dispatch_queue_create("com.xxcc", DISPATCH_QUEUE_CONCURRENT);
建立串行隊列
dispatch_queue_t queue = dispatch_queue_create("com.xxcc", DISPATCH_QUEUE_SERIAL);
GCD默認已經提供了全局併發隊列,供整個應用使用,能夠無需手動建立
/** 第一個參數:優先級 也可直接填後面的數字 #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 // 後臺 第二個參數: 預留參數 0 */ dispatch_queue_t quque1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
得到主隊列
dispatch_queue_t queue = dispatch_get_main_queue();
/* 第一個參數:隊列 第二個參數:block,在裏面封裝任務 */ dispatch_sync(queue, ^{ });
開啓異步函數 異步函數 :等主線程執行完畢以後,回過頭開線程執行任務
dispatch_async(queue, ^{
});
dispatch_queue_t queue = dispatch_get_global_queue(0, 0); dispatch_async(queue, ^{ NSLog(@"---download1---%@",[NSThread currentThread]); });
異步函數+串行隊列:會開啓一條線程,任務串行執行
dispatch_queue_t queue = dispatch_queue_create("com.xxcc", DISPATCH_QUEUE_SERIAL); dispatch_async(queue, ^{ NSLog(@"---download1---%@",[NSThread currentThread]); });
同步函數+併發隊列:不會開線程,任務串行執行
dispatch_queue_t queue = dispatch_get_global_queue(0, 0); dispatch_sync(queue, ^{ NSLog(@"---download1---%@",[NSThread currentThread]); });
同步函數+串行隊列:不會開線程,任務串行執行
dispatch_queue_t queue = dispatch_queue_create("com.xxcc", DISPATCH_QUEUE_SERIAL); dispatch_sync(queue, ^{ NSLog(@"---download1---%@",[NSThread currentThread]); });
異步函數+主隊列:不會開線程,任務串行執行
使用主隊列(跟主線程相關聯的隊列)
主隊列是GCD自帶的一種特殊的串行隊列,放在主隊列中的任務,都會放到主線程中執行
//1.得到主隊列 dispatch_queue_t queue = dispatch_get_main_queue(); //2.異步函數 dispatch_async(queue, ^{ NSLog(@"---download1---%@",[NSThread currentThread]); });
同步函數+主隊列:死鎖
//1.得到主隊列 dispatch_queue_t queue = dispatch_get_main_queue(); //2.同步函數 dispatch_sync(queue, ^{ NSLog(@"---download1---%@",[NSThread currentThread]); });
由於這個方法在主線程中,給主線程中添加任務,而同步函數要求馬上立刻執行,所以就會相互等待而發生死鎖。將這個方法放入子線程中,則不會發生死鎖,任務串行執行。
總結:
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { [self syncConcurrent]; } //同步函數+併發隊列:不會開線程,任務串行執行 -(void)syncConcurrent { dispatch_queue_t queue = dispatch_get_global_queue(0, 0); NSLog(@"--syncConcurrent--start-"); dispatch_sync(queue, ^{ NSLog(@"---download1---%@",[NSThread currentThread]); }); dispatch_sync(queue, ^{ NSLog(@"---download2---%@",[NSThread currentThread]); }); dispatch_sync(queue, ^{ NSLog(@"---download3---%@",[NSThread currentThread]); }); dispatch_sync(queue, ^{ NSLog(@"---download4---%@",[NSThread currentThread]); }); NSLog(@"--syncConcurrent--end-"); }
咱們看一下輸出
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { [self syncConcurrent]; }
//異步函數+併發隊列:會開啓新的線程,併發執行
-(void)asyncCONCURRENT { NSLog(@"--asyncCONCURRENT--start-"); dispatch_queue_t queue = dispatch_get_global_queue(0, 0); dispatch_async(queue, ^{ NSLog(@"---download1---%@",[NSThread currentThread]); }); dispatch_async(queue, ^{ NSLog(@"---download2---%@",[NSThread currentThread]); }); dispatch_async(queue, ^{ NSLog(@"---download3---%@",[NSThread currentThread]); }); dispatch_async(queue, ^{ NSLog(@"---download4---%@",[NSThread currentThread]); }); NSLog(@"--asyncCONCURRENT--end-"); }
咱們來看一下輸出
注意:GCD中開多少條線程是由系統根據CUP繁忙程度決定的,若是任務不少,GCD會開啓適當的子線程,並不會讓全部任務同時執行。
咱們一樣經過一個實例來看
#import "ViewController.h" @interface ViewController () @property (weak, nonatomic) IBOutlet UIImageView *imageView; @end @implementation ViewController -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { dispatch_queue_t queue = dispatch_get_global_queue(0, 0); dispatch_async(queue, ^{ // 得到圖片URL NSURL *url = [NSURL URLWithString:@"//upload-images.jianshu.io/upload_images/2301429-d5cc0a007447e469.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"]; // 將圖片URL下載爲二進制文件 NSData *data = [NSData dataWithContentsOfURL:url]; // 將二進制文件轉化爲image UIImage *image = [UIImage imageWithData:data]; NSLog(@"%@",[NSThread currentThread]); // 返回主線程 這裏用同步函數不會發生死鎖,由於這個方法在子線程中被調用。 // 也可使用異步函數 dispatch_async(dispatch_get_main_queue(), ^{ self.imageView.image = image; NSLog(@"%@",[NSThread currentThread]); }); }); } @end
GCD線程間的通訊很是簡單,使用同步或異步函數,傳入主隊列便可。
dispatch_barrier_async(queue, ^{ NSLog(@"--dispatch_barrier_async-"); });
咱們來看一下柵欄函數的做用
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { [self barrier]; } -(void)barrier { //1.建立隊列(併發隊列) dispatch_queue_t queue = dispatch_queue_create("com.xxccqueue", DISPATCH_QUEUE_CONCURRENT); dispatch_async(queue, ^{ for (NSInteger i = 0; i<3; i++) { NSLog(@"%zd-download1--%@",i,[NSThread currentThread]); } }); dispatch_async(queue, ^{ for (NSInteger i = 0; i<3; i++) { NSLog(@"%zd-download2--%@",i,[NSThread currentThread]); } }); //柵欄函數 dispatch_barrier_async(queue, ^{ NSLog(@"我是一個柵欄函數"); }); dispatch_async(queue, ^{ for (NSInteger i = 0; i<3; i++) { NSLog(@"%zd-download3--%@",i,[NSThread currentThread]); } }); dispatch_async(queue, ^{ for (NSInteger i = 0; i<3; i++) { NSLog(@"%zd-download4--%@",i,[NSThread currentThread]); } }); }
咱們來看一下輸出
柵欄函數能夠控制任務執行的順序,柵欄函數以前的執行完畢以後,執行柵欄函數,而後在執行柵欄函數以後的
/* 第一個參數:延遲時間 第二個參數:要執行的代碼 若是想讓延遲的代碼在子線程中執行,也能夠更改在哪一個隊列中執行 dispatch_get_main_queue() -> dispatch_get_global_queue(0, 0) */ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ NSLog(@"---%@",[NSThread currentThread]); });
延遲執行的其餘方法:
// 2s中以後調用run方法 [self performSelector:@selector(run) withObject:nil afterDelay:2.0]; // repeats:YES 是否重複 [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
-(void)once { //整個程序運行過程當中只會執行一次 //onceToken用來記錄該部分的代碼是否被執行過 static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSLog(@"-----"); }); }
一次性代碼主要應用在單例模式中,關於單例模式詳解你們能夠去看iOS-單例模式寫一次就夠了這裏不在贅述。
/* 第一個參數:迭代的次數 第二個參數:在哪一個隊列中執行 第三個參數:block要執行的任務 */ dispatch_apply(10, queue, ^(size_t index) { });
快速迭代:開啓多條線程,併發執行,相比於for循環在耗時操做中極大的提升效率和速度
// 建立隊列組 dispatch_group_t group = dispatch_group_create(); // 建立並行隊列 dispatch_queue_t queue = dispatch_get_global_queue(0, 0); // 執行隊列組任務 dispatch_group_async(group, queue, ^{ }); //隊列組中的任務執行完畢以後,執行該函數 dispatch_group_notify(group, queue, ^{ });
下面看一了實例使用group下載兩張圖片而後合成在一塊兒
#import "ViewController.h" @interface ViewController () @property (weak, nonatomic) IBOutlet UIImageView *imageView; @property (nonatomic, strong) UIImage *image1; /**< 圖片1 */ @property (nonatomic, strong) UIImage *image2; /**< 圖片2 */ @end @implementation ViewController -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { [self group]; } -(void)group { //下載圖片1 //建立隊列組 dispatch_group_t group = dispatch_group_create(); //1.開子線程下載圖片 //建立隊列(併發) dispatch_queue_t queue = dispatch_get_global_queue(0, 0); dispatch_group_async(group, queue, ^{ //1.獲取url地址 NSURL *url = [NSURL URLWithString:@"http://www.huabian.com/uploadfile/2015/0914/20150914014032274.jpg"]; //2.下載圖片 NSData *data = [NSData dataWithContentsOfURL:url]; //3.把二進制數據轉換成圖片 self.image1 = [UIImage imageWithData:data]; NSLog(@"1---%@",self.image1); }); //下載圖片2 dispatch_group_async(group, queue, ^{ //1.獲取url地址 NSURL *url = [NSURL URLWithString:@"http://img1.3lian.com/img2011/w12/1202/19/d/88.jpg"]; //2.下載圖片 NSData *data = [NSData dataWithContentsOfURL:url]; //3.把二進制數據轉換成圖片 self.image2 = [UIImage imageWithData:data]; NSLog(@"2---%@",self.image2); }); //合成,隊列組執行完畢以後執行 dispatch_group_notify(group, queue, ^{ //開啓圖形上下文 UIGraphicsBeginImageContext(CGSizeMake(200, 200)); //畫1 [self.image1 drawInRect:CGRectMake(0, 0, 200, 100)]; //畫2 [self.image2 drawInRect:CGRectMake(0, 100, 200, 100)]; //根據圖形上下文拿到圖片 UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); //關閉上下文 UIGraphicsEndImageContext(); //回到主線程刷新UI dispatch_async(dispatch_get_main_queue(), ^{ self.imageView.image = image; NSLog(@"%@--刷新UI",[NSThread currentThread]); }); }); }
4. NSOperation的使用(重點)
NSOperation 是蘋果公司對 GCD 的封裝,徹底面向對象,並比GCD多了一些更簡單實用的功能,因此使用起來更加方便易於理解。NSOperation 和NSOperationQueue 分別對應 GCD 的 任務 和 隊列。
NSOperation和NSOperationQueue實現多線程的具體步驟
1.將須要執行的操做封裝到一個NSOperation對象中
2.將NSOperation對象添加到NSOperationQueue中
系統會自動將NSOperationQueue中的NSOperation取出來,並將取出的NSOperation封裝的操做放到一條新線程中執行
NSOperation是個抽象類,並不具有封裝操做的能力,必須使用它的子類
使用NSOperation子類的方式有3種
/* 第一個參數:目標對象 第二個參數:選擇器,要調用的方法 第三個參數:方法要傳遞的參數 */ NSInvocationOperation *op = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download) object:nil]; //啓動操做 [op start];
//1.封裝操做 NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{ //要執行的操做,在主線程中執行 NSLog(@"1------%@",[NSThread currentThread]); }]; //2.追加操做,追加的操做在子線程中執行,能夠追加多條操做 [op addExecutionBlock:^{ NSLog(@"---download2--%@",[NSThread currentThread]); }]; [op start];
// 重寫自定義類的main方法實現封裝操做
-(void)main { // 要執行的操做 } // 實例化一個自定義對象,並執行操做 CLOperation *op = [[CLOperation alloc]init]; [op start];
自定義類封裝性高,複用性高。
NSOperation中的兩種隊列
主隊列:經過mainQueue得到,凡是放到主隊列中的任務都將在主線程執行
非主隊列:直接alloc init出來的隊列。非主隊列同時具有了併發和串行的功能,經過設置最大併發數屬性來控制任務是併發執行仍是串行執行
NSOperationQueue的做用
NSOperation能夠調用start方法來執行任務,但默認是同步執行的
若是將NSOperation添加到NSOperationQueue(操做隊列)中,系統會自動異步執行NSOperation中的操做
添加操做到NSOperationQueue中
- (void)addOperation:(NSOperation *)op; - (void)addOperationWithBlock:(void (^)(void))block;
注意:將操做添加到NSOperationQueue中,就會自動啓動,不須要再本身啓動了addOperation 內部調用 start方法
start方法 內部調用 main方法
注:這裏使用NSBlockOperation示例,其餘兩種方法同樣 // 1. 建立非主隊列 同時具有併發和串行的功能,默認是併發隊列 NSOperationQueue *queue =[[NSOperationQueue alloc]init]; //NSBlockOperation 不論封裝操做仍是追加操做都是異步併發執行 // 2. 封裝操做 NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"download1 -- %@",[NSThread currentThread]); }]; // 3. 將封裝操做加入主隊列 // 也能夠不獲取封裝操做對象 直接添加操做到隊列中 //[queue addOperationWithBlock:^{ // 操做 //}]; [queue addOperation:op1];
NSOperation
- (void)addDependency:(NSOperation *)op;
// 操做op1依賴op5,即op1必須等op5執行完畢以後纔會執行 // 添加操做依賴,注意不能循環依賴,若是循環依賴會形成兩個任務都不會執行 // 也能夠誇隊列依賴,依賴別的隊列的操做 [op1 addDependency:op5];
void (^completionBlock)(void)
// 監聽操做的完成 // 當op1線程完成以後,馬上就會執行block塊中的代碼 // block中的代碼與op1不必定在一個線程中執行,可是必定在子線程中執行 op1.completionBlock = ^{ NSLog(@"op1已經完成了---%@",[NSThread currentThread]); };
NSOperationQueue
//1.建立隊列 NSOperationQueue *queue = [[NSOperationQueue alloc]init]; /* 默認是併發隊列,若是最大併發數>1,併發 若是最大併發數==1,串行隊列 系統的默認是最大併發數-1 ,表示不限制 設置成0則不會執行任何操做 */ queue.maxConcurrentOperationCount = 1;
//當值爲YES的時候暫停,爲NO的時候是恢復 queue.suspended = YES;
//取消全部的任務,再也不執行,不可逆 [queue cancelAllOperations];
注意:暫停和取消只能暫停或取消處於等待狀態的任務,不能暫停或取消正在執行中的任務,必須等正在執行的任務執行完畢以後纔會暫停,若是想要暫停或者取消正在執行的任務,能夠在每一個任務之間即每當執行完一段耗時操做以後,判斷是否任務是否被取消或者暫停。若是想要精確的控制,則須要將判斷代碼放在任務之中,可是不建議這麼作,頻繁的判斷會消耗太多時間
NSOperation
// 開啓線程 - (void)start; - (void)main; // 判斷線程是否被取消 @property (readonly, getter=isCancelled) BOOL cancelled; // 取消當前線程 - (void)cancel; //NSOperation任務是否在運行 @property (readonly, getter=isExecuting) BOOL executing; //NSOperation任務是否已結束 @property (readonly, getter=isFinished) BOOL finished; // 添加依賴 - (void)addDependency:(NSOperation *)op; // 移除依賴 - (void)removeDependency:(NSOperation *)op; // 優先級 typedef NS_ENUM(NSInteger, NSOperationQueuePriority) { NSOperationQueuePriorityVeryLow = -8L, NSOperationQueuePriorityLow = -4L, NSOperationQueuePriorityNormal = 0, NSOperationQueuePriorityHigh = 4, NSOperationQueuePriorityVeryHigh = 8 }; // 操做監聽 @property (nullable, copy) void (^completionBlock)(void) NS_AVAILABLE(10_6, 4_0); // 阻塞當前線程,直到該NSOperation結束。可用於線程執行順序的同步 - (void)waitUntilFinished NS_AVAILABLE(10_6, 4_0); // 獲取線程的優先級 @property double threadPriority NS_DEPRECATED(10_6, 10_10, 4_0, 8_0); // 線程名稱 @property (nullable, copy) NSString *name NS_AVAILABLE(10_10, 8_0); @end
NSOperationQueue
// 獲取隊列中的操做 @property (readonly, copy) NSArray<__kindof NSOperation *> *operations; // 隊列中的操做數 @property (readonly) NSUInteger operationCount NS_AVAILABLE(10_6, 4_0); // 最大併發數,同一時間最多隻能執行三個操做 @property NSInteger maxConcurrentOperationCount; // 暫停 YES:暫停 NO:繼續 @property (getter=isSuspended) BOOL suspended; // 取消全部操做 - (void)cancelAllOperations; // 阻塞當前線程直到此隊列中的全部任務執行完畢 - (void)waitUntilAllOperationsAreFinished;
NSOperation線程之間的通訊方法
// 回到主線程刷新UI [[NSOperationQueue mainQueue]addOperationWithBlock:^{ self.imageView.image = image; }];
咱們一樣使用下載多張圖片合成綜合案例
#import "ViewController.h" @interface ViewController () @property (weak, nonatomic) IBOutlet UIImageView *imageView; @property(nonatomic,strong)UIImage *image1; @property(nonatomic,strong)UIImage *image2; @end @implementation ViewController -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { // 建立非住隊列 NSOperationQueue *queue = [[NSOperationQueue alloc]init]; // 下載第一張圖片 NSBlockOperation *download1 = [NSBlockOperation blockOperationWithBlock:^{ NSURL *url = [NSURL URLWithString:@"http://img2.3lian.com/2014/c7/12/d/77.jpg"]; NSData *data = [NSData dataWithContentsOfURL:url]; self.image1 = [UIImage imageWithData:data]; }]; // 下載第二張圖片 NSBlockOperation *download2 = [NSBlockOperation blockOperationWithBlock:^{ NSURL *url = [NSURL URLWithString:@"http://img2.3lian.com/2014/c7/12/d/77.jpg"]; NSData *data = [NSData dataWithContentsOfURL:url]; self.image2 = [UIImage imageWithData:data]; }]; // 合成操做 NSBlockOperation *combie = [NSBlockOperation blockOperationWithBlock:^{ // 開啓圖形上下文 UIGraphicsBeginImageContext(CGSizeMake(375, 667)); // 繪製圖片1 [self.image1 drawInRect:CGRectMake(0, 0, 375, 333)]; // 繪製圖片2 [self.image2 drawInRect:CGRectMake(0, 334, 375, 333)]; // 獲取合成圖片 UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); // 關閉圖形上下文 UIGraphicsEndImageContext(); // 回到主線程刷新UI [[NSOperationQueue mainQueue]addOperationWithBlock:^{ self.imageView.image = image; }]; }]; // 添加依賴,合成圖片須要等圖片1,圖片2都下載完畢以後合成 [combie addDependency:download1]; [combie addDependency:download2]; // 添加操做到隊列 [queue addOperation:download1]; [queue addOperation:download2]; [queue addOperation:combie]; } @end
注意:子線程執行完操做以後就會當即釋放,即便咱們使用強引用引用子線程使子線程不被釋放,也不能給子線程再次添加操做,或者再次開啓。
聲明:本文非原創,僅僅整理一些開發技能知識文章,以做存檔學習用
參考
http://www.jianshu.com/p/6e6f4e005a0b
http://www.jianshu.com/p/f28a50f72bb1
http://www.jianshu.com/p/6e74f5438f2c