iOS開(kāi)發(fā)之 - 多線程

iOS開(kāi)發(fā)之 - 多線程

最近利用晚上的空閑時(shí)間,整理了一下多線程相關(guān)的知識(shí),幾經(jīng)更改,就變成你現(xiàn)在看到的模樣了(如有失誤之處,還請(qǐng)不吝賜教)。把多線程相關(guān)的知識(shí)總結(jié)在這里,只希望對(duì)看到的朋友們有所幫助!文章有些長(zhǎng),涵蓋的知識(shí)點(diǎn)也比較多,希望朋友們能夠耐心讀完哈!我們這篇文章主要包括以下幾個(gè)模塊:

  • (一)iOS開(kāi)發(fā)之多線程 基礎(chǔ)知識(shí)
  • (二)iOS開(kāi)發(fā)之多線程 NSThread
  • (三)iOS開(kāi)發(fā)之多線程 NSOperation
  • (四)iOS開(kāi)發(fā)之多線程 GCD
  • (五)iOS開(kāi)發(fā)之多線程 線程間的通信

下面我們開(kāi)始了解 iOS 多線程的基礎(chǔ)知識(shí),如果在此之前對(duì)多線程已經(jīng)有所了解的朋友,大可跳過(guò)這一部分,直接看后邊三種多線程編程的 demo 即可!

(一)iOS 開(kāi)發(fā)之多線程 基礎(chǔ)知識(shí)

1.多線程簡(jiǎn)介:

一個(gè)進(jìn)程要想執(zhí)行任務(wù), 必須得有線程(每個(gè)進(jìn)程至少要有一條線程), 線程是進(jìn)程的基本執(zhí)行單元, 一個(gè)進(jìn)程(程序)的所有任務(wù)都在線程中執(zhí)行.

2.多線程的原理:

同一時(shí)間, CPU 只能處理一條線程,只有一條線程在工作.
多線程并發(fā)(同時(shí))執(zhí)行, 其實(shí)是 CPU 快速地在多條線程之間調(diào)度.
如果 CPU 調(diào)度線程的時(shí)間足夠快,就造成了多線程并發(fā)執(zhí)行的假象.

3.多線程的優(yōu)缺點(diǎn):

多線程的優(yōu)點(diǎn):

能適當(dāng)提高程序的執(zhí)行效率.
能適當(dāng)提高資源利用率(CPU、內(nèi)存利用率).

多線程的缺點(diǎn):

開(kāi)啟線程需要占用一定的內(nèi)存空間(默認(rèn)情況下,主線程占用1M,子線程占用512KB),如果開(kāi)啟大量的線程,會(huì)占用大量的內(nèi)存空間,降低程序的性能.

4.多線程在 iOS 開(kāi)發(fā)中的應(yīng)用:

主線程:一個(gè) iOS 程序運(yùn)行后,默認(rèn)會(huì)開(kāi)啟1條線程,稱(chēng)為“主線程”或“ UI 線程”.
主線程的主要作用:刷新 UI 界面、處理 UI 事件(比如點(diǎn)擊事件、滾動(dòng)事件、拖拽事件等).

5.iOS 中三種多線程編程的技術(shù),分別是:

(一)NSThread

(二)Cocoa NSOperation

(三)GCD(全稱(chēng):Grand Central Dispatch)

這三種編程方式從上到下,抽象度層次是從低到高的,抽象度越高的使用越簡(jiǎn)單,也是Apple最推薦使用的。

三種方式的優(yōu)缺點(diǎn)介紹:

NSThread:

優(yōu)點(diǎn):NSThread 比其他兩個(gè)輕量級(jí)
缺點(diǎn):需要自己管理線程的生命周期,線程同步。線程同步對(duì)數(shù)據(jù)的加鎖會(huì)有一定的系統(tǒng)開(kāi)銷(xiāo)。

Cocoa NSOperation

優(yōu)點(diǎn):不需要關(guān)心線程管理,數(shù)據(jù)同步的事情,可以把精力放在自己需要執(zhí)行的操作上。
Cocoa operation 相關(guān)的類(lèi)是 NSOperation ,NSOperationQueue。
NSOperation是個(gè)抽象類(lèi),使用它必須用它的子類(lèi),可以實(shí)現(xiàn)它或者使用它定義好的兩個(gè)子類(lèi):NSInvocationOperation 和 NSBlockOperation。
創(chuàng)建NSOperation子類(lèi)的對(duì)象,把對(duì)象添加到NSOperationQueue隊(duì)列里執(zhí)行。

GCD

Grand Central Dispatch (GCD)是Apple開(kāi)發(fā)的一個(gè)多核編程的解決方法。在iOS4.0開(kāi)始之后才能使用。GCD是一個(gè)替代諸如NSThread, NSOperationQueue, NSInvocationOperation等技術(shù)的很高效和強(qiáng)大的技術(shù)。


對(duì) iOS 基礎(chǔ)知識(shí)有了一定了解之后,下邊我們開(kāi)始看多線程相關(guān)的一些 demo, 建議朋友們?cè)囋囅逻叺?demo,這樣更方便理解!如有疑問(wèn),或發(fā)現(xiàn)某些 demo 寫(xiě)的有誤,還請(qǐng)朋友們告知!

下邊我們開(kāi)始了解NSThread,NSThread 其實(shí)用的真不多,主要還是用另外兩種,不過(guò)作為 iOS 開(kāi)發(fā)者,還是應(yīng)該對(duì)它有些了解的,下邊是 NSThread 相關(guān)的 demo !

(二)iOS 開(kāi)發(fā)之多線程 NSThread

一、NSThread 初始化

1.動(dòng)態(tài)方法

- (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument 
// 初始化線程 
NSThread* thread = [[NSThread alloc] initWithTarget:self selector:@selector(doSomething:)object:nil];
// 線程名字 
thread.name = @"MYThread";
 // 啟動(dòng)線程 
[thread start];

2.靜態(tài)方法

+ (void)detachNewThreadSelector:(SEL)aSelector toTarget:(id)aTarget withObject:(id)anArgument
[NSThread detachNewThreadSelector:@selector(doSomething:) toTarget:self withObject:nil];   
// 調(diào)用完畢后,會(huì)馬上創(chuàng)建并開(kāi)啟新線程 

3.隱式創(chuàng)建線程的方法:

- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg;
[self performSelectorInBackground:@selector(run) withObject:nil];  

提示朋友們一下:第一種方式會(huì)直接創(chuàng)建線程并且開(kāi)始運(yùn)行線程,第二種方式是先創(chuàng)建線程對(duì)象,然后再運(yùn)行線程操作,在運(yùn)行線程操作前可以設(shè)置線程的優(yōu)先級(jí)等線程信息

二、線程間的通信

//在主線程上執(zhí)行操作
[self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:YES];  
//在當(dāng)前線程執(zhí)行操作
[self performSelector:@selector(run) withObject:nil];  
//在指定線程上執(zhí)行操作
[self performSelector:@selector(run) onThread:thread withObject:nil waitUntilDone:YES]; 

三、一些簡(jiǎn)單方法

//獲取當(dāng)前線程
NSThread *thread = [NSThread currentThread];
//獲取主線程
NSThread *main = [NSThread mainThread]; 
//睡眠,相當(dāng)于暫停
[NSThread sleepForTimeInterval:3];

NSThread 就說(shuō)到這,一起再看下NSOperation, NSOperation 在開(kāi)發(fā)中用的還是挺多的,雖然蘋(píng)果建議使用 GCD ,但依然有開(kāi)發(fā)者覺(jué)得 NSOperation 比 GCD 更好用!仁者見(jiàn)仁智者見(jiàn)智吧!本小白覺(jué)得如果能掌握還是都掌握的好!

(三)iOS開(kāi)發(fā)之多線程 NSOperation

一、創(chuàng)建線程的方式

使用NSOperation創(chuàng)建線程的方式有3種:

(1)NSInvocationOperation 方式
(2)NSBlockOperation 方式
(3)自定義子類(lèi)繼承NSOperation,實(shí)現(xiàn)內(nèi)部相應(yīng)的?法

下面是這三種方式創(chuàng)建線程的具體方法:

1.NSInvocationOperation

- (void)invocationOperation
{
//創(chuàng)建NSInvocationOperation對(duì)象
    NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];

//開(kāi)始執(zhí)行操作
    [operation start];
}
//一旦執(zhí)行操作,就會(huì)調(diào)用target的run方法

提示:默認(rèn)情況下,調(diào)用了start方法后并不會(huì)開(kāi)一條新線程去執(zhí)行操作,只有將NSOperation放到一個(gè)NSOperationQueue中,才會(huì)異步執(zhí)行操作.

2.NSBlockOperation

- (void)blockOperation
{
//創(chuàng)建NSBlockOperation對(duì)象
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        // 主線程
        NSLog(@"1---%@", [NSThread currentThread]);
    }];
    // 添加任務(wù)(在子線程執(zhí)行)
    [operation addExecutionBlock:^{
        NSLog(@"2---%@", [NSThread currentThread]);
    }];
    [operation addExecutionBlock:^{
        NSLog(@"3---%@", [NSThread currentThread]);
    }];
    //開(kāi)啟執(zhí)行操作
    [operation start];
}

提示:只要NSBlockOperation封裝的操作數(shù) > 1,就會(huì)異步執(zhí)行操作

3.自定義子類(lèi)繼承自NSOperation

自定義NSOperation子類(lèi)需要重寫(xiě) - (void)main方法

- (void)main
{
    // 新建一個(gè)自動(dòng)釋放池,如果是異步執(zhí)行操作,那么將無(wú)法訪問(wèn)到主線程的自動(dòng)釋放池
    @autoreleasepool {
    for (NSInteger i = 0; i<100; i++) {
        NSLog(@"1 - %ld - %@", i, [NSThread currentThread]);
    }
    //檢測(cè)操作是否被取消,對(duì)取消做出響應(yīng)
    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的應(yīng)用

一個(gè)NSOperation對(duì)象可以通過(guò)調(diào)用start方法來(lái)執(zhí)行任務(wù),默認(rèn)是同步執(zhí)行的.也可以將NSOperation添加到一個(gè)NSOperationQueue中去執(zhí)行,而且是異步執(zhí)行的.

1.NSOperationQueue簡(jiǎn)單創(chuàng)建

//第一種方式
- (void)operationQueue
{
    // 創(chuàng)建NSOperationQueue隊(duì)列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    // 創(chuàng)建NSInvocationOperation對(duì)象
    NSInvocationOperation *operation1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run1) object:nil];
    NSInvocationOperation *operation2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run2) object:nil];
    
  // 創(chuàng)建NSBlockOperation對(duì)象
    NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"1 --- %@", [NSThread currentThread]);
    }];
    [operation3 addExecutionBlock:^{
        NSLog(@"2 --- %@", [NSThread currentThread]);
    }];
    NSBlockOperation *operation4 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"3 --- %@", [NSThread currentThread]);
    }];
    // 添加任務(wù)到隊(duì)列中
    [queue addOperation:operation1]; 
    [queue addOperation:operation2]; 
    [queue addOperation:operation3]; 
    [queue addOperation:operation4]; 
}


//第二種方式
- (void)operationQueue2
{
    // 創(chuàng)建NSOperationQueue隊(duì)列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperationWithBlock:^{
        NSLog(@"1 --- %@", [NSThread currentThread]);
    }];
    [queue addOperationWithBlock:^{
        NSLog(@"2 --- %@", [NSThread currentThread]);
    }];
}

2.NSOperation中的操作依賴(lài)

NSOperation之間可以設(shè)置依賴(lài)來(lái)保證執(zhí)行順序,比如一定要讓操作A執(zhí)行完后,才能執(zhí)行操作B,可以這么寫(xiě)[operationB addDependency:operationA]; // 操作B依賴(lài)于操作operationA,另外,通過(guò)removeDependency方法可以刪除依賴(lài)對(duì)象.

#pragma mark - 依賴(lài)操作調(diào)整執(zhí)行順序
- (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]);
    };
      // 設(shè)置依賴(lài)
    [op1 addDependency:op2];
    [op1 addDependency:op3];
    [queue addOperation:op1];
    [queue addOperation:op2];
    [queue addOperation:op3];
}

3.線程間的通信

#pragma mark - NSOperation實(shí)現(xiàn)線程間的通信
- (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;
        }];
    }];
}

三、一些簡(jiǎn)單方法

// 取消單個(gè)操作  
[operation cancel];  
// 取消queue中所有的操作  
[queue cancelAllOperations]; 
//設(shè)置隊(duì)列的最大并發(fā)操作數(shù)量
queue.maxConcurrentOperationCount = 1; 
// 會(huì)阻塞當(dāng)前線程,等到某個(gè)operation執(zhí)行完畢
[operation waitUntilFinished];  
// 阻塞當(dāng)前線程,等待queue的所有操作執(zhí)行完畢
[queue waitUntilAllOperationsAreFinished];
// 暫停queue YES代表暫停隊(duì)列,NO代表恢復(fù)隊(duì)列
[queue setSuspended:YES];

NSOperation 常用的基本上也就上面那些了。。。GCD 就不用多說(shuō)了,老東家蘋(píng)果推薦的,不過(guò)我卻覺(jué)得名字太長(zhǎng)~~~,一開(kāi)始總是記不住,多敲就好了!所有編程語(yǔ)言大概都有這么一個(gè)絕招O(∩_∩)O!

(四)iOS開(kāi)發(fā)之多線程 GCD

一、GCD 術(shù)語(yǔ)

要理解GCD ,我們先來(lái)熟悉與線程和并發(fā)相關(guān)的幾個(gè)概念。

1.Serial vs. Concurrent 串行 vs. 并發(fā)

任務(wù)串行執(zhí)行就是每次只有一個(gè)任務(wù)被執(zhí)行,任務(wù)并發(fā)執(zhí)行就是在同一時(shí)間可以有多個(gè)任務(wù)被執(zhí)行。

2.同步(Synchronous)與異步(Asynchronous)

同步,意味著在當(dāng)前線程中執(zhí)行任務(wù),不具備開(kāi)啟新的線程的能力。
異步,在新的線程中執(zhí)行任務(wù),具備開(kāi)啟新的線程的能力。

3.臨界區(qū)(Critical Section)

就是一段代碼不能被并發(fā)執(zhí)行,即兩個(gè)線程不能同時(shí)執(zhí)行這段代碼。

4.死鎖(Deadlock)

停止等待事情的線程會(huì)導(dǎo)致多個(gè)線程相互維持等待,即死鎖。

5.Thread Safe 線程安全

線程安全的代碼能在多線程或并發(fā)任務(wù)中被安全的調(diào)用,而不會(huì)導(dǎo)致任何問(wèn)題(數(shù)據(jù)損壞,崩潰等)。

6.Queues 隊(duì)列

GCD 提供有 dispatch queues 來(lái)處理代碼塊,這些隊(duì)列管理你提供給 GCD 的任務(wù)并用 FIFO 順序執(zhí)行這些任務(wù)。

二、代碼演示

1、dispatch_async(異步函數(shù))

//異步函數(shù) + 并發(fā)隊(duì)列:可以同時(shí)開(kāi)啟多條線程,任務(wù)是并行的
// 1.創(chuàng)建并發(fā)隊(duì)列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
      //2.將任務(wù)加入隊(duì)列
    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]);
        }
    });
//異步函數(shù) + 串行隊(duì)列:會(huì)開(kāi)啟新的線程,但是任務(wù)是串行的,執(zhí)行完一個(gè)任務(wù),再執(zhí)行下一個(gè)任務(wù)
// 1.創(chuàng)建串行隊(duì)列
    dispatch_queue_t queue = dispatch_queue_create("YMWM", DISPATCH_QUEUE_SERIAL);
// 2.將任務(wù)加入隊(duì)列
    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(同步函數(shù))

   //同步函數(shù) + 并發(fā)隊(duì)列:不會(huì)開(kāi)啟新的線程
    // 1.獲得全局的并發(fā)隊(duì)列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    // 2.將任務(wù)加入隊(duì)列
    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);
        }
});
//同步函數(shù) + 串行隊(duì)列:不會(huì)開(kāi)啟新的線程
    // 1.創(chuàng)建串行隊(duì)列
    dispatch_queue_t queue = dispatch_queue_create("YMWM", DISPATCH_QUEUE_SERIAL);
    // 2.將任務(wù)加入隊(duì)列
    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.兩者在主隊(duì)列中的應(yīng)用

    //異步函數(shù) + 主隊(duì)列:只在主線程中執(zhí)行任務(wù)
    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);
        }
    });
    //同步函數(shù) + 主隊(duì)列:死循環(huán)
    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);
// 創(chuàng)建一個(gè)隊(duì)列組
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的使用

  //執(zhí)行某個(gè)代碼片段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的使用

第一個(gè)參數(shù)predicate,該參數(shù)是檢查后面第二個(gè)參數(shù)所代表的代碼塊是否被調(diào)用的謂詞,第二個(gè)參數(shù)則是在整個(gè)應(yīng)用程序中只會(huì)被調(diào)用一次的代碼塊

static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSLog(@"%@", [NSThread currentThread]);
    });

8.dispatch_after的使用

  - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"-------");
    //延遲執(zhí)行,較為精確
   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實(shí)現(xiàn)單粒設(shè)計(jì)模式

1.GCD實(shí)現(xiàn)設(shè)計(jì)模式

類(lèi)的.h文件

@interface PKPerson : NSObject
+ (instancetype)sharedCar;
@end

類(lèi)的.m文件

@interface PKPerson() <NSCopying>

@end

@implementation PKPerson

// 實(shí)例變量,當(dāng)前類(lèi)
static id _instance;

// 重寫(xiě)allocWithZone方法
+ (instancetype)allocWithZone:(struct _NSZone *)zone{

    static dispatch_once_t onceToken;

    // 該方法整個(gè)應(yīng)用程序只調(diào)用一次
    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;
}

// 保證每復(fù)制一個(gè)對(duì)象也是同一內(nèi)存空間
- (id)copyWithZone:(NSZone *)zone{
    return _instance;
}
@end

2.宏定義封裝GCD單粒設(shè)計(jì)模式

通常當(dāng)多個(gè)類(lèi)之間有相同的屬性和方法時(shí),我們會(huì)考慮將相同的部分抽取出來(lái),封裝到父類(lèi)中,但單粒模式不可以這樣做,因?yàn)閱瘟DJ讲捎美^承方式的話,所有的類(lèi)會(huì)共享一份內(nèi)存。所以一般采取宏定義封裝GCD單粒設(shè)計(jì)模式。注意,每行的結(jié)尾需要添加 ""。

// .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實(shí)現(xiàn)設(shè)計(jì)模式

類(lèi)的.h文件

@interface PKPerson : NSObject

+ (instancetype)sharedInstance;

@end

類(lèi)的.m文件

#import "PKPerson.h"

@interface PKPerson()

@end

@implementation PKPerson

static id _instance;

+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
    // 同步鎖,防止多線程同時(shí)進(jìn)入
    @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定時(shí)器

@interface ViewController ()
/** 定時(shí)器(這里不用帶*,因?yàn)閐ispatch_source_t就是個(gè)類(lèi),內(nèi)部已經(jīng)包含了*) */
@property (nonatomic, strong) dispatch_source_t timer;
@end

@implementation ViewController

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    // 獲得隊(duì)列
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    // 創(chuàng)建一個(gè)定時(shí)器(dispatch_source_t本質(zhì)還是個(gè)OC對(duì)象)
    self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    
    // GCD的時(shí)間參數(shù)一般是納秒(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);
    
    // 設(shè)置回調(diào)
    dispatch_source_set_event_handler(self.timer, ^{
        NSLog(@"------%@-------", [NSThread currentThread]);
    });
    
    // 啟動(dòng)定時(shí)器
    dispatch_resume(self.timer);
}

三種多線程編程都說(shuō)完了,代碼有些多,不過(guò)希望朋友們有所收獲,另外再補(bǔ)充一些關(guān)于線程間通信的。。。

(五)iOS開(kāi)發(fā)之多線程 線程間的通信

一、簡(jiǎn)單介紹

在一個(gè)進(jìn)程中,線程往往不是孤立存在的,多個(gè)線程之間需要經(jīng)常進(jìn)行通信。以下是線程之間進(jìn)行通信的方法:

二、常用方法:

代碼1:performSelector

performSelector常用方法的常用方法主要有以下幾種:

//在主線程上執(zhí)行操作
[self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:YES];  

//在當(dāng)前線程執(zhí)行操作
[self performSelector:@selector(run) withObject:nil];  

//在指定線程上執(zhí)行操作
[self performSelector:@selector(run) onThread:thread withObject:nil waitUntilDone:YES];

具體代碼:

//點(diǎn)擊屏幕開(kāi)始執(zhí)行以下方法
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [self performSelectorInBackground:@selector(download) withObject:nil];
}
- (void)download
{
    // 圖片的網(wǎng)絡(luò)路徑
    NSURL *url = [NSURL URLWithString:@"http://img.lanrentuku.com/img/allimg/1310/13822295641903.jpg"];
    
    // 根據(jù)地址加載圖片
    NSData *data = [NSData dataWithContentsOfURL:url]; //耗時(shí)操作
    
    // 生成圖片
    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調(diào)用的是setImage:方法,會(huì)直接調(diào)用系統(tǒng)的方法)
//- (void)showImage:(UIImage *)image
//{
//    self.imageView.image = image;
//}

代碼2:NSOperationQueue方式

一個(gè)NSOperation對(duì)象可以通過(guò)調(diào)用start方法來(lái)執(zhí)行任務(wù),默認(rèn)是同步執(zhí)行的.也可以將NSOperation添加到一個(gè)NSOperationQueue中去執(zhí)行,而且是異步執(zhí)行的.

#pragma mark - NSOperation實(shí)現(xiàn)線程間的通信
- (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,全稱(chēng)Grand Central Dispath,是蘋(píng)果開(kāi)發(fā)的一種支持并行操作的機(jī)制。它的主要部件是一個(gè)FIFO隊(duì)列和一個(gè)線程池,前者用來(lái)添加任務(wù),后者用來(lái)執(zhí)行任務(wù)。

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

 // 圖片的網(wǎng)絡(luò)路徑
        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個(gè)子類(lèi),NSSocketPort、NSMessagePort、NSMachPort,但在iOS下只有NSMachPort可用。代碼如下:

UIViewController中的代碼:

#import "ViewController.h"
#import "MyPort.h"

@interface ViewController () <NSPortDelegate>
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    //創(chuàng)建主線程的port
    NSPort *myPort = [NSPort port];
    
    //設(shè)置代理
    myPort.delegate = self;
    
    //把port加入runloop
    [[NSRunLoop currentRunLoop] addPort:myPort forMode:NSDefaultRunLoopMode];
    
    //啟動(dòng)子線程,并傳入主線程的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

        //設(shè)置子線程名字
        [[NSThread currentThread] setName:@"MyThread"];
        
        //開(kāi)啟runloop
        [[NSRunLoop currentRunLoop] run];
        
        //創(chuàng)建自己port
        secondPort = [NSPort port];
        secondPort.delegate = self;
        
        //將自己的port添加到runloop,防止runloop執(zhí)行完畢之后退出,接收主線程發(fā)送過(guò)來(lái)的port消息
        [[NSRunLoop currentRunLoop] addPort:secondPort forMode:NSDefaultRunLoopMode];
        
        //完成向主線程發(fā)送消息
        [self sendPortMessage];
    }
}
/**
 *   完成向主線程發(fā)送消息
 */
- (void)sendPortMessage {
    //發(fā)送消息到主線程
    [firstPort sendBeforeDate:[NSDate date]
                         msgid:PKMessage
                    components:nil
                          from:secondPort
                      reserved:0];
}

提示:通常來(lái)說(shuō), NSPort可以做的事情通過(guò)performSelector也同樣可以搞定,因此線程間通信完全可以通過(guò)performSelector來(lái)實(shí)現(xiàn)。
<end>


結(jié)束語(yǔ):以上是多線程相關(guān)的知識(shí)總結(jié),本人也是小白級(jí)別,如果有寫(xiě)錯(cuò)的地方,還請(qǐng)朋友們能夠指出來(lái),謝謝!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,501評(píng)論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,673評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事?!?“怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 178,610評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,939評(píng)論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,668評(píng)論 6 412
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 56,004評(píng)論 1 329
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,001評(píng)論 3 449
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 43,173評(píng)論 0 290
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,705評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,426評(píng)論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,656評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,139評(píng)論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,833評(píng)論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 35,247評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 36,580評(píng)論 1 295
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,371評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,621評(píng)論 2 380

推薦閱讀更多精彩內(nèi)容