你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發綜合 >> iOS筆記_12_多線程

iOS筆記_12_多線程

編輯:IOS開發綜合

主線程

一個iOS程序運行後,默認會開啟1條線程,稱為“主線程”或“UI線程”(刷新UI界面最好在主線程中做,在子線程中可能會出現莫名其妙的BUG) 主線程的作用顯示\刷新UI界面 處理UI事件(比如點擊事件、滾動事件、拖拽事件等) 注意點別將比較耗時的操作放到主線程中 耗時操作會卡住主線程,嚴重影響UI的流暢度,給用戶一種“卡”的壞體驗 iOS中多線程的實現方案
pthread(c語言,程序員管理)
一套通用的多線程API 適用於Unix\Linux\Windows等系統 跨平台\可移植 使用難度大 NSThread(oc語言,程序員管理)
使用更加面向對象 簡單易用,可直接操作線程對象 GCD(c語言,自動管理)
旨在替代NSThread等線程技術 充分利用設備的多核 NSOperation(oc語言,自動管理)
基於GCD(底層是GCD) 比GCD多了一些更簡單實用的功能 使用更加面向對象

NSThread

一個NSThread對象就代表一條線程 創建、啟動線程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
[thread start];
// 線程一啟動,就會在線程thread中執行self的run方法
常見相關用法
+ (NSThread *)mainThread; // 獲得主線程
- (BOOL)isMainThread; // 是否為主線程
+ (BOOL)isMainThread; // 是否為主線程

// 獲取當前線程
NSThread *current = [NSThread currentThread];

// 線程的名字
- (void)setName:(NSString *)n;
- (NSString *)name;
其他創建線程方式
優點:簡單快捷 缺點:無法對線程進行更詳細的設置
// 創建線程後自動啟動線程
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];

// 隱式創建並啟動線程
[self performSelectorInBackground:@selector(run) withObject:nil];
控制線程的狀態
// 啟動線程
- (void)start; 
// 進入就緒狀態 -> 運行狀態。當線程任務執行完畢,自動進入死亡狀態

// 阻塞(暫停)線程
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
// 進入阻塞狀態

// 強制停止線程
+ (void)exit;
// 進入死亡狀態
// 注意:一旦線程停止(死亡)了,就不能再次開啟任務

互斥鎖

互斥鎖使用格式
@synchronized(鎖對象) { // 需要鎖定的代碼  }
// 注意:鎖定1份代碼只用1把鎖,用多把鎖是無效的

原子和非原子屬性

OC在定義屬性時有nonatomic和atomic兩種選擇
atomic:原子屬性,為setter方法加鎖(默認就是atomic) nonatomic:非原子屬性,不會為setter方法加鎖 nonatomic和atomic對比
atomic:線程安全,需要消耗大量的資源 nonatomic:非線程安全,適合內存小的移動設備 iOS開發的建議
所有屬性都聲明為nonatomic 盡量避免多線程搶奪同一塊資源 盡量將加鎖、資源搶奪的業務邏輯交給服務器端處理,減小移動客戶端的壓力

線程間的通信

什麼叫做線程間通信
在1個進程中,線程往往不是孤立存在的,多個線程之間需要經常進行通信 線程間通信的體現
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;

GCD

本質及優點
全稱是Grand Central Dispatch,可譯為“牛逼的中樞調度器” GCD會自動管理線程的生命周期(創建線程、調度任務、銷毀線程) 程序員只需要告訴GCD想要執行什麼任務,不需要編寫任何線程管理代碼 使用GCD的步驟
定制任務
確定想做的事情 將任務添加到隊列中
GCD會自動將隊列中的任務取出,放到對應的線程中執行 任務的取出遵循隊列的FIFO原則:先進先出,後進後出 執行任務常用的函數
// 用同步的方式執行任務
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
// queue:隊列
// block:任務
// 注意:使用sync函數往當前串行隊列中添加任務,會卡住當前的串行隊列。同步方式會阻塞當前隊列。


// 用異步的方式執行任務
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
// 在前面的任務執行結束後它才執行,而且它後面的任務等它執行完成之後才會執行
// 注意:這個queue不能是全局的並發隊列
同步和異步的區別
同步:只能在當前線程中執行任務,不具備開啟新線程的能力 異步:可以在新的線程中執行任務,具備開啟新線程的能力 隊列的類型
並發隊列(Concurrent Dispatch Queue)
可以讓多個任務並發(同時)執行(自動開啟多個線程同時執行任務) 並發功能只有在異步(dispatch_async)函數下才有效 串行隊列(Serial Dispatch Queue)
讓任務一個接著一個地執行(一個任務執行完畢後,再執行下一個任務) 並發隊列
// 創建並發隊列
dispatch_queue_t queue = dispatch_queue_create("隊列名稱", DISPATCH_QUEUE_CONCURRENT)
// 第一個參數 const char *label 隊列名稱 
// 第二個參數 dispatch_queue_attr_t attr 隊列的類型 DISPATCH_QUEUE_CONCURRENT 表示並發隊列

// 獲得全局的並發隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 
// 第一個參數 dispatch_queue_priority_t priority 隊列的優先級
// 第二個參數 unsigned long flags 此參數暫時無用,用0即可
#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 // 後台
串行隊列
// 創建串行隊列(隊列類型傳遞NULL或者DISPATCH_QUEUE_SERIAL)
dispatch_queue_t queue = dispatch_queue_create("隊列名稱", NULL); 
// 獲得主隊列,主隊列也是串行隊列
dispatch_queue_t queue = dispatch_get_main_queue();
線程間的通信
// 從子線程回到主線程
dispatch_async(
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 執行耗時的異步操作...
      dispatch_async(dispatch_get_main_queue(), ^{
        // 回到主線程,執行UI刷新操作
        });
});
延遲操作
iOS中延遲操作有3種
調用NSObject的方法 使用GCD函數 使用NSTimer
[self performSelector:@selector(run) withObject:nil afterDelay:2.0];
// 2秒後再調用self的run方法

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    // 2秒後執行這裡的代碼...
});

[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(test) userInfo:nil repeats:NO];
一次性代碼
// 使用dispatch_once函數能保證某段代碼在程序運行過程中只被執行1次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    // 只執行1次的代碼(這裡面默認是線程安全的)
});
快速迭代
// 使用dispatch_apply函數能進行快速迭代遍歷
dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t index){
    // 執行10次代碼,index順序不確定
});
隊列組
可以滿足的一種需求(當然也可以用依賴線程依賴實現,下文會提到)
首先:分別異步執行2個耗時的操作 其次:等2個異步操作都執行完畢後,再回到主線程執行操作
dispatch_group_t group =  dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 執行1個耗時的異步操作
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 執行1個耗時的異步操作
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    // 等前面的異步操作都執行完畢後,回到主線程...
});

單例模式

單例模式的作用
可以保證在程序運行過程,一個類只有一個實例,而且該實例易於供外界訪問 從而方便地控制了實例個數,並節約系統資源 單例模式的使用場合
在整個應用程序中,共享一份資源(這份資源只需要創建初始化1次) 實現步驟:
// 在.m中保留一個全局的static的實例
static id _instance;

// 重寫allocWithZone:方法,在這裡創建唯一的實例(注意線程安全),alloc方法會調用allocWithZone:方法
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _instance = [super allocWithZone:zone];
    });
    return _instance;
}
// 提供1個類方法讓外界訪問唯一的實例
+ (instancetype)sharedInstance
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _instance = [[self alloc] init];
    });
    return _instance;
}
// 實現copyWithZone:方法
- (id)copyWithZone:(struct _NSZone *)zone
{
    return _instance;
}

NSOperation

NSOperation和NSOperationQueue實現多線程的具體步驟
先將需要執行的操作封裝到一個NSOperation對象中 然後將NSOperation對象添加到NSOperationQueue中 系統會自動將NSOperationQueue中的NSOperation取出來 將取出的NSOperation封裝的操作放到一條新線程中執行 NSOperation是抽象類,使用NSOperation子類的方式有3種
NSInvocationOperation NSBlockOperation 自定義子類繼承NSOperation,實現內部相應的方法 NSInvocationOperation
// 創建NSInvocationOperation對象
- (id)initWithTarget:(id)target selector:(SEL)sel object:(id)arg;

// 調用start方法開始執行操作
- (void)start;
// 一旦執行操作,就會調用target的sel方法
// 注意:默認情況下,調用了start方法後並不會開一條新線程去執行操作,而是在當前線程同步執行操作。只有將NSOperation放到一個NSOperationQueue中,才會異步執行操作
NSBlockOperation
// 創建NSBlockOperation對象
+ (id)blockOperationWithBlock:(void (^)(void))block;

// 通過addExecutionBlock:方法添加更多的操作
- (void)addExecutionBlock:(void (^)(void))block;

// 注意:只要NSBlockOperation封裝的操作數 > 1,就會異步執行操作
NSOperationQueue
// 添加操作到NSOperationQueue中
- (void)addOperation:(NSOperation *)op;
- (void)addOperationWithBlock:(void (^)(void))block;
相關操作
// 設置最大並發數
- (NSInteger)maxConcurrentOperationCount;
- (void)setMaxConcurrentOperationCount:(NSInteger)cnt;

// 取消隊列的所有操作
- (void)cancelAllOperations;
// 提示:也可以調用NSOperation的- (void)cancel方法取消單個操作

// 暫停和恢復隊列
- (void)setSuspended:(BOOL)b; // YES代表暫停隊列,NO代表恢復隊列
- (BOOL)isSuspended;
操作依賴
NSOperation之間可以設置依賴來保證執行順序 可以在不同queue的NSOperation之間創建依賴關系
[operationB addDependency:operationA]; // 操作B依賴於操作A
操作的監聽
// 可以監聽一個操作的執行完畢
- (void (^)(void))completionBlock;
- (void)setCompletionBlock:(void (^)(void))block;
自定義NSOperation
只需要重寫- (void)main方法,在裡面實現想執行的任務 注意點
自己創建自動釋放池(因為如果是異步操作,無法訪問主線程的自動釋放池) 經常通過- (BOOL)isCancelled方法檢測操作是否被取消,對取消做出響應

計算時間差

NSDate *start = [NSDate date];
// 上下兩句代碼間放需要計算時間的程序
NSDate *end = [NSDate date];
NSLog(@"%f", [end timeIntervalSinceDate:start]);
CFTimeInterval begin = CFAbsoluteTimeGetCurrent();
// 上下兩句代碼間放需要計算時間的程序
CFTimeInterval end = CFAbsoluteTimeGetCurrent();
NSLog(@"%f", end - begin);

小結:

1、在GCD中,方法如果用異步函數可以開啟子線程做事情,該方法中的程序會順序執行到底,然後再返回去開啟子線程執行內部的操作。如果是同步函數,則不能開啟子線程,裡面的同步函數只能一個一個執行下去。
2、如果在主隊列中調用同步函數,容易造成死鎖。

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved