一個java程序員自學IOS開發之路(十三)

2015/12/09程序員

Day 46編程

今天學習多線程swift

多線程的優缺點後端

  • 優勢
    1. 充分發揮多核處理器優點,將不一樣線程任務分配給不一樣的處理器,真正進入「並行運算」狀態
    2. 將耗時的任務分配到其餘線程執行,由主線程負責統一更新界面會使應用程序更加流暢,用戶體驗更好
    3. 當硬件處理器的數量增長,程序會運行更快,而程序無需作任何調整
  • 缺點

  新建線程會消耗內存空間和CPU時間,線程太多會下降系統的運行性能設計模式

iOS的三種多線程技術安全

  • NSThread 
    1. 使用NSThread對象創建一個線程很是方便
    2. 可是!要使用NSThread管理多個線程很是困難,不推薦使用
    3. 技巧!使用[NSThread currentThread]跟蹤任務所在線程,適用於這三種技術
  • NSOperation/NSOperationQueue
    1. 是使用GCD實現的一套Objective-C的API
    2. 是面向對象的線程技術
    3. 提供了一些在GCD中不容易實現的特性,如:限制最大併發數量、操做之間的依賴關係
  • GCD —— Grand Central Dispatch 
    1. 是基於C語言的底層API
    2. Block定義任務,使用起來很是靈活便捷
    3. 提供了更多的控制能力以及操做隊列中所不能使用的底層函數
  • 提示:iOS的開發者,須要瞭解三種多線程技術的基本使用,由於在實際開發中會根據實際狀況選擇不一樣的多線程技術

GCD基本思想網絡

  • GCD的基本思想是就將操做s放在隊列s中去執行
    1. 操做使用Blocks定義(Swift裏用閉包)
    2. 隊列負責調度任務執行所在的線程以及具體的執行時間
    3. 隊列的特色是先進先出(FIFO)的,新添加至對列的操做都會排在隊尾
  • 提示

GCD的函數都是以dispatch(分派、調度)開頭的多線程

  • 隊列

dispatch_queue_t 閉包

    • 串行隊列,隊列中的任務會順序執行
    • 並行隊列,隊列中的任務一般會併發執行
  • 操做

dispatch_async 異步操做,會併發執行,沒法肯定任務的執行順序併發

dispatch_sync 同步操做,會依次順序執行,可以決定任務的執行順序

 

下面是演練GCD的OC代碼

串行隊列(一個接一個,排隊跑步,保持隊形)

 1     NSLog(@"%@", [NSThread currentThread]);
 2     dispatch_queue_t q = dispatch_queue_create(「yu1」, DISPATCH_QUEUE_SERIAL);
 3     // 非ARC開發時,千萬別忘記release
 4     //    dispatch_release(q);
 5     
 6     // 串行行隊列的同步任務,一樣會在主線程上運行
 7     // 提示:在開發中極少用
 8     for (int i = 0; i < 5; ++i) {
 9         // 同步任務順序執行
10         dispatch_sync(q, ^{
11             NSLog(@"%@ %d", [NSThread currentThread], i);
12         });
13     }
14     
15     for (int i = 0; i < 5; ++i) {
16         // 異步任務,併發執行,可是若是在串行隊列中,仍然會依次順序執行
17         dispatch_async(q, ^{
18             // [NSThread currentThread] 能夠在開發中,跟蹤當前線程
19             // num = 1,表示主線程
20             // num = 2,表示第2個子線程。。。
21             NSLog(@"%@ %d", [NSThread currentThread], i);
22         });
23     }

打印結果以下

 

而後是並行隊列(並排跑,相似於賽跑)

 1     NSLog(@"%@", [NSThread currentThread]);
 2     // 特色:沒有隊形,執行順序程序員不能控制!
 3     // 應用場景:併發執行任務,沒有前後順序關係
 4     // 並行隊列容易出錯!並行隊列不能控制新建線程的數量!
 5     dispatch_queue_t q = dispatch_queue_create("yu2", DISPATCH_QUEUE_CONCURRENT);
 6     
 7     for (int i = 0; i < 10; ++i) {
 8         // 異步任務
 9         dispatch_async(q, ^{
10             // [NSThread currentThread] 能夠在開發中,跟蹤當前線程
11             // num = 1,表示主線程
12             // num = 2,表示第2個子線程。。。
13             NSLog(@"%@ %d", [NSThread currentThread], i);
14         });
15     }
16     
17     for (int i = 0; i < 10; ++i) {
18         // 同步任務順序執行
19         dispatch_sync(q, ^{
20             NSLog(@"%@ %d", [NSThread currentThread], i);
21         });
22     }

控制檯打印

 

注意:關於多線程不要只看一次運行結果,像上面的代碼,多運行幾回結果是不同的

 

全局隊列(蘋果爲了方便多線程的設計,提供一個全局隊列,供全部的APP共同使用)

 1     // 全局隊列與並行隊列的區別
 2     // 1> 不須要建立,直接GET就能用
 3     // 2> 兩個隊列的執行效果相同
 4     // 3> 全局隊列沒有名稱,調試時,沒法確認準確隊列
 5     NSLog(@"%@", [NSThread currentThread]);
 6     // 優先級在開發中通常用DISPATCH_QUEUE_PRIORITY_DEFAULT
 7     dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 8     
 9     for (int i = 0; i < 5; ++i) {
10         // 同步任務順序執行
11         dispatch_sync(q, ^{
12             NSLog(@"%@ %d", [NSThread currentThread], i);
13         });
14     }
15     
16     for (int i = 0; i < 5; ++i) {
17         dispatch_async(q, ^{
18             // [NSThread currentThread] 能夠在開發中,跟蹤當前線程
19             // num = 1,表示主線程
20             // num = 2,表示第2個子線程。。。
21             NSLog(@"%@ %d", [NSThread currentThread], i);
22         });
23     }

打印以下

 

(線程)隊列,保證操做在主線程上執行

 1     // 每個應用程序都只有一個主線程
 2     // 爲何須要在主線程上工做呢?
 3     // 在iOS開發中,全部UI的更新工做,都必須在主線程上執行!
 4     dispatch_queue_t q = dispatch_get_main_queue();
 5     
 6     // 主線程是有工做的,並且除非將程序殺掉,不然主線程的工做永遠不會結束!
 7     // 同步任務,阻塞了!!!
 8    // dispatch_sync(q, ^{
 9    //     NSLog(@"come here baby!");
10    //});
11     
12     // 異步任務,在主線程上運行,同時是保持隊形的
13     for (int i = 0; i < 5; ++i) {
14         dispatch_async(q, ^{
15             NSLog(@"%@ - %d", [NSThread currentThread], i);
16         });
17     }

注意阻塞狀況,打印以下

 

接下來用swift將上面4個demo演練了一遍

串行隊列(一個接一個,排隊跑步,保持隊形)

        let q = dispatch_queue_create("串行隊列1", DISPATCH_QUEUE_SERIAL)
        print("串行隊列,同步任務")
        for i in 0 ..< 5 {
            //同步任務,順序執行,同一線程上執行(仍是在主線程)
            dispatch_sync(q, { () -> Void in
                print("\(NSThread.currentThread()) - \(i)")
            })
        }
        print("串行隊列,異步任務")
        for i in 0 ..< 5 {
            //異步任務,開一個子線程併發執行,可是若是在串行隊列中,仍然會依次順序執行
            // num = 1,表示主線程
            // num = 2,表示第2個子線程。。。
            dispatch_async(q, { () -> Void in
                print("\(NSThread.currentThread()) - \(i)")
            })
        }        

控制檯打印

 

並行隊列(並排跑,相似於賽跑)

        // 特色:沒有隊形,執行順序程序員不能控制!
        // 應用場景:併發執行任務,沒有前後順序關係
        // 並行隊列容易出錯!並行隊列不能控制新建線程的數量!
        let q = dispatch_queue_create("並行隊列1", DISPATCH_QUEUE_CONCURRENT)
        
        print("並行隊列,異步任務")
        for i in 0 ..< 5 {
            //異步任務,隨機亂序,而且有N個子線程
            dispatch_async(q, { () -> Void in
                print("\(NSThread.currentThread()) - \(i)")
            })
        }
        
        print("並行隊列,同步任務")
        for i in 0 ..< 5 {
            //同步任務,順序執行,同一線程上執行(仍是在主線程)
            dispatch_sync(q, { () -> Void in
                print("\(NSThread.currentThread()) - \(i)")
            })
        }

控制檯打印

 

全局隊列(蘋果爲了方便多線程的設計,提供一個全局隊列,供全部的APP共同使用)

        // 全局隊列與並行隊列的區別
        // 1> 不須要建立,直接GET就能用
        // 2> 兩個隊列的執行效果相同
        // 3> 全局隊列沒有名稱,調試時,沒法確認準確隊列
        
        // 優先級在開發中通常用DISPATCH_QUEUE_PRIORITY_DEFAULT
        let q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
        
        print("全局隊列,異步任務")
        for i in 0 ..< 5 {
            //異步任務,隨機亂序,而且有N個子線程
            dispatch_async(q, { () -> Void in
                print("\(NSThread.currentThread()) - \(i)")
            })
        }
        
        print("全局隊列,同步任務")
        for i in 0 ..< 5 {
            //同步任務,順序執行,同一線程上執行(仍是在主線程)
            dispatch_sync(q, { () -> Void in
                print("\(NSThread.currentThread()) - \(i)")
            })
        } 

控制檯打印

 

(線程)隊列,保證操做在主線程上執行

 

        // 每個應用程序都只有一個主線程
        // 爲何須要在主線程上工做呢?
        // 在iOS開發中,全部UI的更新工做,都必須在主線程上執行!
        let q = dispatch_get_main_queue()
        
        print("主隊列,異步任務")
        for i in 0 ..< 5 {
            // 異步任務,在主線程上運行,同時是保持隊形的
            dispatch_async(q, { () -> Void in
                print("\(NSThread.currentThread()) - \(i)")
            })
        }
        
//        print("主隊列,同步任務,阻塞")
//        // 主線程是由工做的,並且除非將程序殺掉,不然主線程的工做永遠不會結束!
//        // 阻塞了!!!
//        dispatch_sync(q, { () -> Void in
//            print("come on baby")
//        })        

控制檯打印

 

dispatch_sync的應用場景

  • 阻塞並行隊列的執行,要求某一操做執行後再進行後續操做,如用戶登陸
  • 確保塊代碼以外的局部變量確實被修改
 1 dispatch_queue_t q = dispatch_queue_create(「yu3」, DISPATCH_QUEUE_CONCURRENT);
 2 __block BOOL logon = NO;
 3 dispatch_sync(q, ^{
 4     NSLog(@"模擬耗時操做 %@", [NSThread currentThread]);
 5       [NSThread sleepForTimeInterval:2.0f];//一般在多線程調試中用於模擬耗時操做, 在發佈的應用程序中,不要使用此方法!
 6     NSLog(@"模擬耗時完成 %@", [NSThread currentThread]); 
 7     logon = YES;
 8 });
 9 
10 dispatch_async(q, ^{
11        NSLog(@"登陸完成的處理 %@", [NSThread currentThread]);
12 });

GCD小結

串行隊列,同步任務,不須要新建線程

串行隊列,異步任務,須要一個子線程,線程的建立和回收不須要程序員參與!

「是最安全的一個選擇」串行隊列只能建立一個子線程

 

並行隊列,同步任務,不須要建立線程

並行隊列,異步任務,有多少個任務,就開N個線程執行,

 

不管什麼隊列和什麼任務,線程的建立和回收不須要程序員參與。

線程的建立回收工做是由隊列負責的

 

「併發」編程,爲了讓程序員從負責的線程控制中解脫出來!只須要面對隊列和任務!

  • GCD
    1. 經過GCD,開發者不用再直接跟線程打交道,只須要向隊列中添加代碼塊便可
    2. GCD在後端管理着一個線程池,GCD不只決定着代碼塊將在哪一個線程被執行,它還根據可用的系統資源對這些線程進行管理。從而讓開發者從線程管理的工做中解放出來,經過集中的管理線程,緩解大量線程被建立的問題
    3. 使用GCD,開發者能夠將工做考慮爲一個隊列,而不是一堆線程,這種並行的抽象模型更容易掌握和使用

 

  • GCD的隊列
    1. GCD公開有5個不一樣的隊列:運行在主線程中的主隊列,3 個不一樣優先級的後臺隊列,以及一個優先級更低的後臺隊列(用於 I/O)
    2. 自定義隊列:串行和並行隊列。自定義隊列很是強大,建議在開發中使用。在自定義隊列中被調度的全部Block最終都將被放入到系統的全局隊列中和線程池中
    3. 提示:不建議使用不一樣優先級的隊列,由於若是設計不當,可能會出現優先級反轉,即低優先級的操做阻塞高優先級的操做

GCD隊列示意圖

  

NSOperation & NSOperationQueue

  • 簡介
    • NSOperationQueue(操做隊列)是由GCD提供的隊列模型的Cocoa抽象,是一套Objective-C的API
    • GCD提供了更加底層的控制,而操做隊列則在GCD之上實現了一些方便的功能,這些功能對於開發者而言一般是最好最安全的選擇
  • 隊列及操做
    • NSOperationQueue有兩種不一樣類型的隊列:主隊列和自定義隊列
    • 主隊列運行在主線程上
    • 自定義隊列在後臺執行
    • 隊列處理的任務是NSOperation的子類
      1. NSInvocationOperation
      2. NSBlockOperation

 

NSOperation的基本使用步驟

  1. 定義操做隊列
  2. 定義操做
  3. 將操做添加到隊列

提示:一旦將操做添加到隊列,操做就會當即被調度執行

 

NSInvocationOperation(調度操做)

  •  1 //定義隊列
     2 self.myQueue = [[NSOperationQueue alloc] init];
     3 //操做調用的方法
     4 - (void)operationAction:(id)obj
     5 {
     6     NSLog(@"%@ - obj : %@", [NSThread currentThread], obj);
     7 }
     8 //定義操做並添加到隊列
     9 NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operationAction:) object:@(i)];
    10 [self.myQueue addOperation:op];
    11 //小結:須要準備一個被調度的方法,而且可以接收一個參數,使用起來不方便

NSBlockOperation

1 //定義操做並添加到隊列
2 NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
3     [self operationAction:@"Block Operation"];
4 }];
5 //將操做添加到隊列
6 [self.myQueue addOperation:op];

設置操做的依賴關係(順序執行)

 1 NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
 2     NSLog(@"%@ - 下載圖片", [NSThread currentThread]);
 3 }];
 4 NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
 5     NSLog(@"%@ - 添加圖片濾鏡", [NSThread currentThread]);
 6 }];
 7 NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
 8     NSLog(@"%@ - 更新UI", [NSThread currentThread]);
 9 }];
10 [op2 addDependency:op1];
11 [op3 addDependency:op2];
12 [self.myQueue addOperation:op1];
13 [self.myQueue addOperation:op2];
14 [[NSOperationQueue mainQueue] addOperation:op3];

提示:利用addDependency能夠指定操做之間的彼此依賴關係(執行前後順序)

注意:不要出現循環依賴!

 

NSOperationQueue還能夠設置同時併發的線程數量

[self.myQueue setMaxConcurrentOperationCount:3];

設置同時併發的線程數量可以有效地下降CPU和內存的開銷

這一功能用GCD不容易實現

 

Run Loop

(1) Run Loop提供了一種異步執行代碼的機制,不能並行執行任務

(2) 在主隊列中,Main Run Loop直接配合任務的執行,負責處理UI事件、計時器,以及其它內核相關事件

(3) Run Loop的主要目的是保證程序執行的線程不會被系統終止

Run Loop的工做特色

  1. 當有事件發生時,Run Loop會根據具體的事件類型通知應用程序作出響應
  2. 當沒有事件發生時,Run Loop會進入休眠狀態,從而達到省電的目的
  3. 當事件再次發生時,Run Loop會被從新喚醒,處理事件

主線程和其餘線程中的Run Loop

  • iOS程序的主線程默認已經配置好了Run Loop
  • 其餘線程默認狀況下沒有設置Run Loop
  • 通常在開發中不多會主動建立RunLoop,而一般會把事件添加到RunLoop中

 

多線程中的資源共享

併發編程中許多問題的根源就是在多線程中訪問共享資源。資源能夠是一個屬性、一個對象、網絡設備或者一個文件等

在多線程中任何一個共享的資源均可能是一個潛在的衝突點,必須精心設計以防止這種衝突的發生

 

爲了保證性能,atomic僅針對屬性的setter方法作了保護

而爭搶共享資源時,若是涉及到屬性的getter方法,可使用互斥鎖@synchronized能夠保證屬性在多個線程之間的讀寫都是安全的

不管是atomic仍是@synchronized,使用的代價都是高昂的,不建議使用

 

建議:多線程是併發執行多個任務提升效率的,若是可能,應該在線程中避免爭搶共享資源

 

正是出於性能的考慮,UIKit中的絕大多數的類都不是線程安全的,所以,蘋果公司要求:更新UI相關的操做,應該在主線程中執行

 

單例模式

  • 單例模式是一種經常使用的軟件設計模式
  • 經過單例模式能夠保證系統中一個類只有一個實例並且該實例易於外界訪問,從而方便對實例個數的控制並節約系統資源
  • 若是但願系統中某個類的對象只能存在一個,單例模式是最好的解決方案
  • iOS中最多見的單例就是UIApplication 
  • 應用場景:

音頻播放,背景音樂!

硬件資源:加速器、[UIScreen mainScreen]

下面是實現單例模式的OC代碼

DemoObj.h

1 @interface DemoObj : NSObject
2 
3 + (instancetype)sharedDemoObj;
4 
5 @end

DemoObj.m

 1 @implementation DemoObj
 2 /**
 3  1. 重寫allocWithZone,用dispatch_once實例化一個靜態變量(dispatch_once是線程安全的,可以作到在多線程的環境下Block中的代碼只會被執行一次)
 4  2. 寫一個+sharedXXX方便其餘類調用
 5  */
 6 // 在OC中,全部對象的內存空間的分配,最終都會調用allocWithZone方法
 7 // 若是要作單例,須要重寫此方法
 8 // GCD提供了一個方法,專門用來建立單例的
 9 + (instancetype)allocWithZone:(struct _NSZone *)zone {
10     static DemoObj *result;
11     static dispatch_once_t onceToken;
12     dispatch_once(&onceToken, ^{
13         result = [super allocWithZone:zone];
14     });
15     return result;
16 }
17 
18 + (instancetype)sharedDemoObj {
19     return [[self alloc] init];
20 }
21 @end

Swift代碼

class Singleton: NSObject {
    internal class func sharedInstance() -> Singleton {
        struct Static {
            static var onceToken : dispatch_once_t = 0
            static var instance : Singleton? = nil
        }
        dispatch_once(&Static.onceToken) {
            Static.instance = Singleton()
        }
        return Static.instance!
    }
}

單例小結

優勢: 能夠阻止其餘對象實例化單例對象的副本,從而確保全部對象都訪問惟一實例

缺點: 單例對象一旦創建,對象指針是保存在靜態區的,單例對象在堆中分配的內存空間,會在應用程序終止後纔會被釋放

提示: 只有確實須要惟一使用的對象才須要考慮單例模式,不要濫用單例

 

NSObject的多線程方法

  • 開啓後臺執行任務的方法

- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg

  • 在後臺線程中通知主線程執行任務的方法

- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait

  • 獲取線程信息

[NSThread currentThread]

  • 線程休眠

[NSThread sleepForTimeInterval:2.0f];

  • 特色

使用簡單,量級輕

不能控制線程的數量以及執行順序

注意事項

NSObject的多線程方法使用的是NSThread的多線程技術

而NSThread的多線程技術不會自動使用@autoreleasepool

在使用NSObject或NSThread的多線程技術時,若是涉及到對象分配,須要手動添加@autoreleasepool

 

複習一下autoreleasepool

iOS開發中的內存管理

  • iOS開發中,並無JAVA或C#中的垃圾回收機制
  • 使用ARC開發,只是在編譯時,編譯器會根據代碼結構自動添加了retain、release和autorelease

 

自動釋放池的工做原理

  • 標記爲autorelease的對象在出了做用域範圍後,會被添加到最近一次建立的自動釋放池中
  • 當自動釋放池被銷燬或耗盡時,會向自動釋放池中的全部對象發送release消息

 

每一個線程都須要有@autoreleasepool,不然可能會出現內存泄漏,可是使用NSThread多線程技術,並不會爲後臺線程建立自動釋放池

相關文章
相關標籤/搜索