最近利用晚上的空閑時間,整理了一下多線程相關的知識,幾經更改,就變成你現在看到的模樣了(如有失誤之處,還請不吝賜教)。把多線程相關的知識總結在這里,只希望對看到的朋友們有所幫助!文章有些長,涵蓋的知識點也比較多,希望朋友們能夠耐心讀完哈!我們這篇文章主要包括以下幾個模塊:
- (一)iOS開發之多線程 基礎知識
- (二)iOS開發之多線程 NSThread
- (三)iOS開發之多線程 NSOperation
- (四)iOS開發之多線程 GCD
- (五)iOS開發之多線程 線程間的通信
下面我們開始了解 iOS 多線程的基礎知識,如果在此之前對多線程已經有所了解的朋友,大可跳過這一部分,直接看后邊三種多線程編程的 demo 即可!
(一)iOS 開發之多線程 基礎知識
1.多線程簡介:
一個進程要想執行任務, 必須得有線程(每個進程至少要有一條線程), 線程是進程的基本執行單元, 一個進程(程序)的所有任務都在線程中執行.
2.多線程的原理:
同一時間, CPU 只能處理一條線程,只有一條線程在工作.
多線程并發(同時)執行, 其實是 CPU 快速地在多條線程之間調度.
如果 CPU 調度線程的時間足夠快,就造成了多線程并發執行的假象.
3.多線程的優缺點:
多線程的優點:
能適當提高程序的執行效率.
能適當提高資源利用率(CPU、內存利用率).
多線程的缺點:
開啟線程需要占用一定的內存空間(默認情況下,主線程占用1M,子線程占用512KB),如果開啟大量的線程,會占用大量的內存空間,降低程序的性能.
4.多線程在 iOS 開發中的應用:
主線程:一個 iOS 程序運行后,默認會開啟1條線程,稱為“主線程”或“ UI 線程”.
主線程的主要作用:刷新 UI 界面、處理 UI 事件(比如點擊事件、滾動事件、拖拽事件等).
5.iOS 中三種多線程編程的技術,分別是:
(一)NSThread
(二)Cocoa NSOperation
(三)GCD(全稱:Grand Central Dispatch)
這三種編程方式從上到下,抽象度層次是從低到高的,抽象度越高的使用越簡單,也是Apple最推薦使用的。
三種方式的優缺點介紹:
NSThread:
優點:NSThread 比其他兩個輕量級
缺點:需要自己管理線程的生命周期,線程同步。線程同步對數據的加鎖會有一定的系統開銷。
Cocoa NSOperation
優點:不需要關心線程管理,數據同步的事情,可以把精力放在自己需要執行的操作上。
Cocoa operation 相關的類是 NSOperation ,NSOperationQueue。
NSOperation是個抽象類,使用它必須用它的子類,可以實現它或者使用它定義好的兩個子類:NSInvocationOperation 和 NSBlockOperation。
創建NSOperation子類的對象,把對象添加到NSOperationQueue隊列里執行。
GCD
Grand Central Dispatch (GCD)是Apple開發的一個多核編程的解決方法。在iOS4.0開始之后才能使用。GCD是一個替代諸如NSThread, NSOperationQueue, NSInvocationOperation等技術的很高效和強大的技術。
對 iOS 基礎知識有了一定了解之后,下邊我們開始看多線程相關的一些 demo, 建議朋友們試試下邊的 demo,這樣更方便理解!如有疑問,或發現某些 demo 寫的有誤,還請朋友們告知!
下邊我們開始了解NSThread,NSThread 其實用的真不多,主要還是用另外兩種,不過作為 iOS 開發者,還是應該對它有些了解的,下邊是 NSThread 相關的 demo !
(二)iOS 開發之多線程 NSThread
一、NSThread 初始化
1.動態方法
- (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument
// 初始化線程
NSThread* thread = [[NSThread alloc] initWithTarget:self selector:@selector(doSomething:)object:nil];
// 線程名字
thread.name = @"MYThread";
// 啟動線程
[thread start];
2.靜態方法
+ (void)detachNewThreadSelector:(SEL)aSelector toTarget:(id)aTarget withObject:(id)anArgument
[NSThread detachNewThreadSelector:@selector(doSomething:) toTarget:self withObject:nil];
// 調用完畢后,會馬上創建并開啟新線程
3.隱式創建線程的方法:
- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg;
[self performSelectorInBackground:@selector(run) withObject:nil];
提示朋友們一下:第一種方式會直接創建線程并且開始運行線程,第二種方式是先創建線程對象,然后再運行線程操作,在運行線程操作前可以設置線程的優先級等線程信息
二、線程間的通信
//在主線程上執行操作
[self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:YES];
//在當前線程執行操作
[self performSelector:@selector(run) withObject:nil];
//在指定線程上執行操作
[self performSelector:@selector(run) onThread:thread withObject:nil waitUntilDone:YES];
三、一些簡單方法
//獲取當前線程
NSThread *thread = [NSThread currentThread];
//獲取主線程
NSThread *main = [NSThread mainThread];
//睡眠,相當于暫停
[NSThread sleepForTimeInterval:3];
NSThread 就說到這,一起再看下NSOperation, NSOperation 在開發中用的還是挺多的,雖然蘋果建議使用 GCD ,但依然有開發者覺得 NSOperation 比 GCD 更好用!仁者見仁智者見智吧!本小白覺得如果能掌握還是都掌握的好!
(三)iOS開發之多線程 NSOperation
一、創建線程的方式
使用NSOperation創建線程的方式有3種:
(1)NSInvocationOperation 方式
(2)NSBlockOperation 方式
(3)自定義子類繼承NSOperation,實現內部相應的?法
下面是這三種方式創建線程的具體方法:
1.NSInvocationOperation
- (void)invocationOperation
{
//創建NSInvocationOperation對象
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
//開始執行操作
[operation start];
}
//一旦執行操作,就會調用target的run方法
提示:默認情況下,調用了start方法后并不會開一條新線程去執行操作,只有將NSOperation放到一個NSOperationQueue中,才會異步執行操作.
2.NSBlockOperation
- (void)blockOperation
{
//創建NSBlockOperation對象
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
// 主線程
NSLog(@"1---%@", [NSThread currentThread]);
}];
// 添加任務(在子線程執行)
[operation addExecutionBlock:^{
NSLog(@"2---%@", [NSThread currentThread]);
}];
[operation addExecutionBlock:^{
NSLog(@"3---%@", [NSThread currentThread]);
}];
//開啟執行操作
[operation start];
}
提示:只要NSBlockOperation封裝的操作數 > 1,就會異步執行操作
3.自定義子類繼承自NSOperation
自定義NSOperation子類需要重寫 - (void)main方法
- (void)main
{
// 新建一個自動釋放池,如果是異步執行操作,那么將無法訪問到主線程的自動釋放池
@autoreleasepool {
for (NSInteger i = 0; i<100; i++) {
NSLog(@"1 - %ld - %@", i, [NSThread currentThread]);
}
//檢測操作是否被取消,對取消做出響應
if (self.isCancelled) return;
for (NSInteger i = 0; i<100; i++) {
NSLog(@"2 - %ld - %@", i, [NSThread currentThread]);
}
if (self.isCancelled) return;
for (NSInteger i = 0; i<100; i++) {
NSLog(@"3 - %ld - %@", i, [NSThread currentThread]);
}
if (self.isCancelled) return;
}
}
二、NSOperationQueue的應用
一個NSOperation對象可以通過調用start方法來執行任務,默認是同步執行的.也可以將NSOperation添加到一個NSOperationQueue中去執行,而且是異步執行的.
1.NSOperationQueue簡單創建
//第一種方式
- (void)operationQueue
{
// 創建NSOperationQueue隊列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 創建NSInvocationOperation對象
NSInvocationOperation *operation1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run1) object:nil];
NSInvocationOperation *operation2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run2) object:nil];
// 創建NSBlockOperation對象
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"1 --- %@", [NSThread currentThread]);
}];
[operation3 addExecutionBlock:^{
NSLog(@"2 --- %@", [NSThread currentThread]);
}];
NSBlockOperation *operation4 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"3 --- %@", [NSThread currentThread]);
}];
// 添加任務到隊列中
[queue addOperation:operation1];
[queue addOperation:operation2];
[queue addOperation:operation3];
[queue addOperation:operation4];
}
//第二種方式
- (void)operationQueue2
{
// 創建NSOperationQueue隊列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperationWithBlock:^{
NSLog(@"1 --- %@", [NSThread currentThread]);
}];
[queue addOperationWithBlock:^{
NSLog(@"2 --- %@", [NSThread currentThread]);
}];
}
2.NSOperation中的操作依賴
NSOperation之間可以設置依賴來保證執行順序,比如一定要讓操作A執行完后,才能執行操作B,可以這么寫[operationB addDependency:operationA]; // 操作B依賴于操作operationA,另外,通過removeDependency方法可以刪除依賴對象.
#pragma mark - 依賴操作調整執行順序
- (void)NSOperationTest1 {
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"~~1~~%@", [NSThread currentThread]);
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 5; i ++) {
NSLog(@"~~2~~%@~~%d~~", [NSThread currentThread], i);
}
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"~~3~~%@", [NSThread currentThread]);
}];
[op2 addExecutionBlock:^{
NSLog(@"~~4~~%@", [NSThread currentThread]);
}];
op3.completionBlock = ^{
NSLog(@"~~5~~%@", [NSThread currentThread]);
};
// 設置依賴
[op1 addDependency:op2];
[op1 addDependency:op3];
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:op3];
}
3.線程間的通信
#pragma mark - NSOperation實現線程間的通信
- (void)NSOperationTest2 {
[[[NSOperationQueue alloc] init] addOperationWithBlock:^{
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://img.lanrentuku.com/img/allimg/1310/13822295641903.jpg"]];
UIImage *image = [UIImage imageWithData:data];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
self.imageview.image = image;
}];
}];
}
三、一些簡單方法
// 取消單個操作
[operation cancel];
// 取消queue中所有的操作
[queue cancelAllOperations];
//設置隊列的最大并發操作數量
queue.maxConcurrentOperationCount = 1;
// 會阻塞當前線程,等到某個operation執行完畢
[operation waitUntilFinished];
// 阻塞當前線程,等待queue的所有操作執行完畢
[queue waitUntilAllOperationsAreFinished];
// 暫停queue YES代表暫停隊列,NO代表恢復隊列
[queue setSuspended:YES];
NSOperation 常用的基本上也就上面那些了。。。GCD 就不用多說了,老東家蘋果推薦的,不過我卻覺得名字太長~~~,一開始總是記不住,多敲就好了!所有編程語言大概都有這么一個絕招O(∩_∩)O!
(四)iOS開發之多線程 GCD
一、GCD 術語
要理解GCD ,我們先來熟悉與線程和并發相關的幾個概念。
1.Serial vs. Concurrent 串行 vs. 并發
任務串行執行就是每次只有一個任務被執行,任務并發執行就是在同一時間可以有多個任務被執行。
2.同步(Synchronous)與異步(Asynchronous)
同步,意味著在當前線程中執行任務,不具備開啟新的線程的能力。
異步,在新的線程中執行任務,具備開啟新的線程的能力。
3.臨界區(Critical Section)
就是一段代碼不能被并發執行,即兩個線程不能同時執行這段代碼。
4.死鎖(Deadlock)
停止等待事情的線程會導致多個線程相互維持等待,即死鎖。
5.Thread Safe 線程安全
線程安全的代碼能在多線程或并發任務中被安全的調用,而不會導致任何問題(數據損壞,崩潰等)。
6.Queues 隊列
GCD 提供有 dispatch queues 來處理代碼塊,這些隊列管理你提供給 GCD 的任務并用 FIFO 順序執行這些任務。
二、代碼演示
1、dispatch_async(異步函數)
//異步函數 + 并發隊列:可以同時開啟多條線程,任務是并行的
// 1.創建并發隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//2.將任務加入隊列
dispatch_async(queue, ^{
for (NSInteger i = 0 ; i < 5; i ++) {
NSLog(@"%ld~~~~1~~~~%@", i, [NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (NSInteger i = 0; i < 5; i ++) {
NSLog(@"%ld~~~2~~~%@", i, [NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (NSInteger i = 0; i < 5; i ++) {
NSLog(@"%ld~~~~3~~~~%@", i, [NSThread currentThread]);
}
});
//異步函數 + 串行隊列:會開啟新的線程,但是任務是串行的,執行完一個任務,再執行下一個任務
// 1.創建串行隊列
dispatch_queue_t queue = dispatch_queue_create("YMWM", DISPATCH_QUEUE_SERIAL);
// 2.將任務加入隊列
dispatch_async(queue, ^{
for (NSInteger i = 0; i < 5 ; i ++) {
NSLog(@"1~~~%@~~~~~%ld", [NSThread currentThread], i);
}
});
dispatch_async(queue, ^{
for (NSInteger i = 0; i < 5; i ++) {
NSLog(@"2~~~%@~~~~~%ld", [NSThread currentThread], i);
}
});
dispatch_async(queue, ^{
for (NSInteger i = 0; i < 5; i ++) {
NSLog(@"2~~~%@~~~~~%ld", [NSThread currentThread], i);
}
});
2、dispatch_sync(同步函數)
//同步函數 + 并發隊列:不會開啟新的線程
// 1.獲得全局的并發隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 2.將任務加入隊列
dispatch_sync(queue, ^{
for (NSInteger i = 0; i < 5; i ++) {
NSLog(@"1~~~%@~~~~~%ld", [NSThread currentThread], i);
}
});
dispatch_sync(queue, ^{
for (NSInteger i = 0; i < 5; i ++) {
NSLog(@"2~~~%@~~~~~%ld", [NSThread currentThread], i);
}
});
dispatch_sync(queue, ^{
for (NSInteger i = 0; i < 5; i ++) {
NSLog(@"3~~~%@~~~~~%ld", [NSThread currentThread], i);
}
});
//同步函數 + 串行隊列:不會開啟新的線程
// 1.創建串行隊列
dispatch_queue_t queue = dispatch_queue_create("YMWM", DISPATCH_QUEUE_SERIAL);
// 2.將任務加入隊列
dispatch_sync(queue, ^{
for (NSInteger i = 0; i < 5; i ++) {
NSLog(@"1~~~%@~~~~~%ld", [NSThread currentThread], i);
}
});
dispatch_sync(queue, ^{
for (NSInteger i = 0; i < 5; i ++) {
NSLog(@"2~~~%@~~~~~%ld", [NSThread currentThread], i);
}
});
dispatch_sync(queue, ^{
for (NSInteger i = 0; i < 5; i ++) {
NSLog(@"3~~~%@~~~~~%ld", [NSThread currentThread], i);
}
});
3.兩者在主隊列中的應用
//異步函數 + 主隊列:只在主線程中執行任務
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
for (NSInteger i = 0; i < 5; i ++) {
NSLog(@"1~~~%@~~~~~%ld", [NSThread currentThread], i);
}
});
dispatch_async(queue, ^{
for (NSInteger i = 0; i < 5; i ++) {
NSLog(@"2~~~%@~~~~~%ld", [NSThread currentThread], i);
}
});
dispatch_async(queue, ^{
for (NSInteger i = 0; i < 5; i ++) {
NSLog(@"3~~~%@~~~~~%ld", [NSThread currentThread], i);
}
});
//同步函數 + 主隊列:死循環
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
for (NSInteger i = 0; i < 5; i ++) {
NSLog(@"1~~~%@~~~~~%ld", [NSThread currentThread], i);
}
});
dispatch_sync(queue, ^{
for (NSInteger i = 0; i < 5; i ++) {
NSLog(@"1~~~%@~~~~~%ld", [NSThread currentThread], i);
}
});
dispatch_sync(queue, ^{
for (NSInteger i = 0; i < 5; i ++) {
NSLog(@"1~~~%@~~~~~%ld", [NSThread currentThread], i);
}
});
4、dispatch_group_async的使用
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 創建一個隊列組
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
//添加操作...
[NSThread sleepForTimeInterval:1];
NSLog(@"1%@", [NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
//添加操作...
[NSThread sleepForTimeInterval:1];
NSLog(@"2%@", [NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
//添加操作...
[NSThread sleepForTimeInterval:1];
NSLog(@"3%@", [NSThread currentThread]);
});
// 回到主線程
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"回到主線程刷新UI");
});
5、dispatch_barrier_async的使用
dispatch_queue_t queue = dispatch_queue_create("YMWM", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:.5];
NSLog(@"1%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:.5];
NSLog(@"2%@", [NSThread currentThread]);
});
dispatch_barrier_async(queue, ^{
NSLog(@"barrier --- %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:.5];
});
dispatch_async(queue, ^{
NSLog(@"3%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"4%@", [NSThread currentThread]);
});
6、dispatch_apply的使用
//執行某個代碼片段10次
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(10, queue, ^(size_t index) {
NSLog(@"%@", [NSThread currentThread]);
});
7、dispatch_once_t的使用
第一個參數predicate,該參數是檢查后面第二個參數所代表的代碼塊是否被調用的謂詞,第二個參數則是在整個應用程序中只會被調用一次的代碼塊
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"%@", [NSThread currentThread]);
});
8.dispatch_after的使用
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"-------");
//延遲執行,較為精確
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"~~~~~~");
});
}
9.線程間的通信
#pragma mark - GCD-線程間的通信
- (void)downloadImage {
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://img.lanrentuku.com/img/allimg/1310/13822295641903.jpg"]];
UIImage *image = [UIImage imageWithData:data];
dispatch_async(dispatch_get_main_queue(), ^{
self.imageview.image = image;
});
}
10.文件剪切
//第一種方式
//將文件從from剪切至to
NSString *from = @"/Users/iOS/Desktop/Test";
NSString *to = @"/Users/iOS/Desktop/test1";
NSFileManager *mgr = [NSFileManager defaultManager];
NSArray *subpaths = [mgr subpathsAtPath:from];
for (NSString *subpath in subpaths) {
NSString *fromFullpath = [from stringByAppendingPathComponent:subpath];
NSString *toFullpath = [to stringByAppendingPathComponent:subpath];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//剪切
[mgr moveItemAtPath:fromFullpath toPath:toFullpath error:nil];
});
}
//第二種方式
//將文件從from剪切至to
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSString *from = @"/Users/iOS/Desktop/Test";
NSString *to = @"/Users/iOS/Desktop/test1";
NSFileManager *mag = [NSFileManager defaultManager];
NSArray *subpaths = [mag subpathsAtPath:from];
dispatch_apply(subpaths.count, queue, ^(size_t index) {
NSString *subpath = subpaths[index];
NSString *fromFullpath = [from stringByAppendingPathComponent:subpath];
NSString *toFullpath = [to stringByAppendingPathComponent:subpath];
// 剪切
[mag moveItemAtPath:fromFullpath toPath:toFullpath error:nil];
NSLog(@"%@~~~~~~%@", [NSThread currentThread], subpath);
});
三、GCD與非GCD實現單粒設計模式
1.GCD實現設計模式
類的.h文件
@interface PKPerson : NSObject
+ (instancetype)sharedCar;
@end
類的.m文件
@interface PKPerson() <NSCopying>
@end
@implementation PKPerson
// 實例變量,當前類
static id _instance;
// 重寫allocWithZone方法
+ (instancetype)allocWithZone:(struct _NSZone *)zone{
static dispatch_once_t onceToken;
// 該方法整個應用程序只調用一次
dispatch_once(&onceToken, ^{
_instance = [super allocWithZone:zone];
});
return _instance;
}
+ (instancetype)sharedInstance{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [[self alloc] init];
});
return _instance;
}
// 保證每復制一個對象也是同一內存空間
- (id)copyWithZone:(NSZone *)zone{
return _instance;
}
@end
2.宏定義封裝GCD單粒設計模式
通常當多個類之間有相同的屬性和方法時,我們會考慮將相同的部分抽取出來,封裝到父類中,但單粒模式不可以這樣做,因為單粒模式采用繼承方式的話,所有的類會共享一份內存。所以一般采取宏定義封裝GCD單粒設計模式。注意,每行的結尾需要添加 ""。
// .h文件
#define PKSingletonH + (instancetype)sharedInstance;
// .m文件
#define PKSingletonH \
static id _instace; \
\
+ (instancetype)allocWithZone:(struct _NSZone *)zone \
{ \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
_instace = [super allocWithZone:zone]; \
}); \
return _instace; \
} \
\
+ (instancetype)sharedInstance \
{ \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
_instace = [[self alloc] init]; \
}); \
return _instace; \
} \
\
- (id)copyWithZone:(NSZone *)zone \
{ \
return _instace; \
}
3.非GCD實現設計模式
類的.h文件
@interface PKPerson : NSObject
+ (instancetype)sharedInstance;
@end
類的.m文件
#import "PKPerson.h"
@interface PKPerson()
@end
@implementation PKPerson
static id _instance;
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
// 同步鎖,防止多線程同時進入
@synchronized(self) {
if (_instance == nil) {
_instance = [super allocWithZone:zone];
}
}
return _instance;
}
+ (instancetype)sharedInstance
{
@synchronized(self) {
if (_instance == nil) {
_instance = [[self alloc] init];
}
}
return _instance;
}
- (id)copyWithZone:(NSZone *)zone
{
return _instance;
}
@end
四、GCD定時器
@interface ViewController ()
/** 定時器(這里不用帶*,因為dispatch_source_t就是個類,內部已經包含了*) */
@property (nonatomic, strong) dispatch_source_t timer;
@end
@implementation ViewController
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
// 獲得隊列
dispatch_queue_t queue = dispatch_get_main_queue();
// 創建一個定時器(dispatch_source_t本質還是個OC對象)
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// GCD的時間參數一般是納秒(1秒 == 10的9次方納秒)
dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC));
uint64_t interval = (uint64_t)(1.0 * NSEC_PER_SEC);
dispatch_source_set_timer(self.timer, start, interval, 0);
// 設置回調
dispatch_source_set_event_handler(self.timer, ^{
NSLog(@"------%@-------", [NSThread currentThread]);
});
// 啟動定時器
dispatch_resume(self.timer);
}
三種多線程編程都說完了,代碼有些多,不過希望朋友們有所收獲,另外再補充一些關于線程間通信的。。。
(五)iOS開發之多線程 線程間的通信
一、簡單介紹
在一個進程中,線程往往不是孤立存在的,多個線程之間需要經常進行通信。以下是線程之間進行通信的方法:
二、常用方法:
代碼1:performSelector
performSelector常用方法的常用方法主要有以下幾種:
//在主線程上執行操作
[self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:YES];
//在當前線程執行操作
[self performSelector:@selector(run) withObject:nil];
//在指定線程上執行操作
[self performSelector:@selector(run) onThread:thread withObject:nil waitUntilDone:YES];
具體代碼:
//點擊屏幕開始執行以下方法
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self performSelectorInBackground:@selector(download) withObject:nil];
}
- (void)download
{
// 圖片的網絡路徑
NSURL *url = [NSURL URLWithString:@"http://img.lanrentuku.com/img/allimg/1310/13822295641903.jpg"];
// 根據地址加載圖片
NSData *data = [NSData dataWithContentsOfURL:url]; //耗時操作
// 生成圖片
UIImage *image = [UIImage imageWithData:data];
// 回到主線程,刷新UI界面
//方式一
[self.imageView performSelector:@selector(setImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:NO];
//方式二
// [self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:NO];
//方式三
// [self performSelectorOnMainThread:@selector(showImage:) withObject:image waitUntilDone:NO];
}
//顯示圖片(如果performSelector調用的是setImage:方法,會直接調用系統的方法)
//- (void)showImage:(UIImage *)image
//{
// self.imageView.image = image;
//}
代碼2:NSOperationQueue方式
一個NSOperation對象可以通過調用start方法來執行任務,默認是同步執行的.也可以將NSOperation添加到一個NSOperationQueue中去執行,而且是異步執行的.
#pragma mark - NSOperation實現線程間的通信
- (void)NSOperationTest {
[[[NSOperationQueue alloc] init] addOperationWithBlock:^{
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://img.lanrentuku.com/img/allimg/1310/13822295641903.jpg"]];
UIImage *image = [UIImage imageWithData:data];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
self.imageView.image = image;
}];
}];
}
代碼3:GCD方式
GCD,全稱Grand Central Dispath,是蘋果開發的一種支持并行操作的機制。它的主要部件是一個FIFO隊列和一個線程池,前者用來添加任務,后者用來執行任務。
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 圖片的網絡路徑
NSURL *url = [NSURL URLWithString:@"http://img.lanrentuku.com/img/allimg/1310/13822295641903.jpg"];
// 加載圖片
NSData *data = [NSData dataWithContentsOfURL:url];
// 生成圖片
UIImage *image = [UIImage imageWithData:data];
// 回到主線程顯示圖片
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = image;
});
});
三、NSPort方法:
NSPort有3個子類,NSSocketPort、NSMessagePort、NSMachPort,但在iOS下只有NSMachPort可用。代碼如下:
UIViewController中的代碼:
#import "ViewController.h"
#import "MyPort.h"
@interface ViewController () <NSPortDelegate>
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//創建主線程的port
NSPort *myPort = [NSPort port];
//設置代理
myPort.delegate = self;
//把port加入runloop
[[NSRunLoop currentRunLoop] addPort:myPort forMode:NSDefaultRunLoopMode];
//啟動子線程,并傳入主線程的port
MyPort *work = [[MyPort alloc] init];
[NSThread detachNewThreadSelector:@selector(launchThreadWithPort:)
toTarget:work
withObject:myPort];
}
- (void)handlePortMessage:(NSMessagePort*)message{
NSLog(@"已接到子線程的消息 --- %@", message);
}
@end
MyPort中的代碼:
.h文件:
#import <Foundation/Foundation.h>
@interface MyPort : NSPort
@end
.m文件:
#import "MyPort.h"
#define PKMessage 10
@interface MyPort ()<NSMachPortDelegate> {
NSPort *firstPort;
NSPort *secondPort;
}
@end
@implementation MyPort
- (void)launchThreadWithPort:(NSPort *)port {
@autoreleasepool {
//接收主線程傳入的port
//設置子線程名字
[[NSThread currentThread] setName:@"MyThread"];
//開啟runloop
[[NSRunLoop currentRunLoop] run];
//創建自己port
secondPort = [NSPort port];
secondPort.delegate = self;
//將自己的port添加到runloop,防止runloop執行完畢之后退出,接收主線程發送過來的port消息
[[NSRunLoop currentRunLoop] addPort:secondPort forMode:NSDefaultRunLoopMode];
//完成向主線程發送消息
[self sendPortMessage];
}
}
/**
* 完成向主線程發送消息
*/
- (void)sendPortMessage {
//發送消息到主線程
[firstPort sendBeforeDate:[NSDate date]
msgid:PKMessage
components:nil
from:secondPort
reserved:0];
}
提示:通常來說, NSPort可以做的事情通過performSelector也同樣可以搞定,因此線程間通信完全可以通過performSelector來實現。
<end>
結束語:以上是多線程相關的知識總結,本人也是小白級別,如果有寫錯的地方,還請朋友們能夠指出來,謝謝!