iOS開發多線程--技術方案

來源:互聯網
上載者:User

標籤:

pthread 實現多線程操作

代碼實現:

void * run(void *param){    for (NSInteger i = 0; i < 1000; i++) {        NSLog(@"---buttonclick---%zd---%@", i, [NSThread currentThread]);    }    return NULL;}@implementation ViewController- (IBAction)clickButton:(id)sender {    // 定義一個線程    pthread_t thread;    // 建立一個線程  (參1)pthread_t *restrict:建立線程的指標,(參2)const pthread_attr_t *restrict:線程屬性  (參3)void *(*)(void *):線程執行的函數的指標,(參4)void *restrict:null    pthread_create(&thread, NULL, run, NULL);    // 何時回收線程不需要你考慮    pthread_t thread2;    pthread_create(&thread2, NULL, run, NULL);}
NSThread實現多線程

一個 NSThread 對象就代表一條線程

建立線程的多種方式
  • 第一種方式:先建立再啟動線程

      // 建立線程  NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"jack"];  // 線程啟動了,事情做完了才會死, 一個NSThread對象就代表一條線程  [thread start];
  • 第二種:直接建立並啟動線程

      // 直接建立並啟動線程  [NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"jack"];
  • 第三種:

      // 直接建立並啟動線程  [self performSelectorInBackground:@selector(run:) withObject:@"jack"];  // 使線程進入阻塞狀態  [NSThread sleepForTimeInterval:2.0];  #pragma mark - 執行run方法  - (void)run:(NSString *)param  {      // 當前線程是否是主線程      for (NSInteger i = 0; i < 100; i++) {          NSLog(@"---%@---%zd---%d", [NSThread currentThread], i,  [NSThread isMainThread]);      }  }
  • 方法2和方法3的優點:快捷 方法1的優點:可以輕鬆拿到線程

線程間通訊
  • 線程間通訊的體現

1個線程傳遞資料給另1個線程

在1個線程中執行完特定任務後,轉到另1個線程繼續執行任務

線程間通訊的常用方法:小程式圖片下載

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {    // 擷取圖片的url    NSURL *url = [NSURL URLWithString:@"http://7xjanq.com1.z0.glb.clouddn.com/6478.jpg"];// 另開1條線程 object用於資料的傳遞    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(downLoadWithURL:) object:url];    // 由於下面下載圖片的耗時太長,應領開啟線程來完成    [thread start];}// 下載圖片- (void)downLoadWithURL:(NSURL *)url{    NSLog(@"%@", [NSThread currentThread]);    // 下載圖片    NSData *data = [NSData dataWithContentsOfURL:url];    // 產生圖片    UIImage *image = [UIImage imageWithData:data];    // 返回主線程顯示圖片    [self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:YES];}

以上兩種方式使用線程已經過時了,開發中我們操作線程大多都使用 GCD 和 NSOperation 來實現多線程操作。

下面我就給大家系統的介紹一下 GCD 是如何?多線程的

GCD 實現多線程GCD 簡介

GCD 全稱是Grand Central Dispatch,可譯為“超級厲害的中樞調度器”,GCD 是蘋果公司為多核的並行運算提出的解決方案, GCD會自動利用更多的 CPU 核心(比如雙核、四核)來開啟線程執行任務,GCD 會自動管理線程的生命週期(建立線程、調度任務、銷毀線程),不需要我們程式員手動管理記憶體。

任務和隊列

任務:在同步函數和非同步函數中執行

隊列:用來存放任務(並發 串列)

GCD會自動將隊列中的任務取出,放到對應的線程,任務的取出遵循FIFO,即先入先出隊列,First Input First Output 的縮寫。先進入的任務先完成並結束,再執行後面的任務。

同步函數和非同步函數,並發隊列和串列隊列
  • 用同步的方式執行任務:在當前線程中可立即執行任務,不具備開啟線程的能力

  • 用非同步方式執行任務:在當前線程結束時執行任務,具備開啟新的線程的能力

  • 並發隊列:允許多個任務同時執行

  • 串列隊列:一個任務執行完畢後,再執行下一個任務

建立並發/串列隊列代碼:

// 建立並發隊列 // 參1:const char *label 隊列名稱 // 參2:dispatch_queue_attr_t attr 隊列類型dispatch_queue_t queueConcurrent = dispatch_queue_create("520it.com", DISPATCH_QUEUE_CONCURRENT);// 建立串列隊列  serial 串列  concurrent並發dispatch_queue_t queueSerial = dispatch_queue_create("520it.com", DISPATCH_QUEUE_SERIAL);// 擷取全域隊列 全域隊列是並發隊列 // 參1:隊列的優先順序 // 參2:0(以後可能用到的參數)dispatch_queue_t queueGlobal = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 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 // 後台// 擷取主隊列  在主隊列中的任務都會在主線程中執行。dispatch_queue_t queueMain = dispatch_get_main_queue();

同步/非同步函數代碼錶示:

// GCD同步函數串列隊列(立即執行,當前線程) // 參1: dispatch_queue_t queue 隊列 // 參2: 任務dispatch_sync(queueSerial, ^{    for (NSInteger i = 0; i < 10; i++) {        NSLog(@"~~~%@", [NSThread currentThread]);   }});// 同步函數並行隊列(立即執行,當前線程)dispatch_sync(queueConcurrent, ^{    for (NSInteger i = 0; i < 10; i++) {        NSLog(@"~~~%@", [NSThread currentThread]);    }});// 非同步函數串列隊列 (另開線程,多個任務按順序執行)dispatch_async(queueSerial, ^{    dispatch_async(queueSerial, ^{        for (NSInteger i = 0; i < 10; i++) {            NSLog(@"~~~%@", [NSThread currentThread]);        }    });    dispatch_async(queueSerial, ^{        for (NSInteger i = 0; i < 10; i++) {            NSLog(@"~~~%@", [NSThread currentThread]);        }    });    dispatch_async(queueSerial, ^{        for (NSInteger i = 0; i < 10; i++) {            NSLog(@"~~~%@", [NSThread currentThread]);        }    });});// 非同步函數並行隊列 (另開線程,多個任務一起執行)dispatch_async(queueConcurrent, ^{    dispatch_async(queueSerial, ^{            for (NSInteger i = 0; i < 10; i++) {                NSLog(@"~~~%@", [NSThread currentThread]);            }    });    dispatch_async(queueSerial, ^{        for (NSInteger i = 0; i < 10; i++) {            NSLog(@"~~~%@", [NSThread currentThread]);        }    });    dispatch_async(queueSerial, ^{        for (NSInteger i = 0; i < 10; i++) {            NSLog(@"~~~%@", [NSThread currentThread]);        }    });});// 主隊列:(任何一個任務只要在主隊列中,都會加入到主線程的隊列中執行)

注意:使用sync函數(同步函數)往當前串列隊列中新增工作,會卡住當前的串列隊列

解釋:使用同步函數新增工作 A 到串列隊列,說明要在當前串列隊列立即執行任務 A ,任務 A 執行完後,才會執行任務 A 後面的代碼。但當前隊列是串列隊列,也就是說任務 A 必須等到當前串列隊列中正在執行的任務 B 完成之後才能執行,因此又必須先執行任務 A 中立即執行任務,又要必須等到任務 B 執行完以後才能執行下一個任務,所以就會卡死。你等我,我等你,誰也無法執行。

GCD實現線程通訊

小項目:下載圖片

代碼如下:

// 擷取圖片的urlNSURL *url = [NSURL URLWithString:@"http://7xjanq.com1.z0.glb.clouddn.com/6478.jpg"];// 開啟線程下載圖片dispatch_queue_t queue = dispatch_queue_create("111", DISPATCH_QUEUE_CONCURRENT);dispatch_async(queue, ^{    NSData *data = [NSData dataWithContentsOfURL:url];    UIImage *image = [UIImage imageWithData:data];    // 下載完成後返回主線程顯示圖片    dispatch_async(dispatch_get_main_queue(), ^{        self.imageView.image = image;    });});
GCD其他常用函數dispatch_barrier 柵欄
// 1.barrier : 在barrier前面的先執行,然後再執行barrier,然後再執行barrier後面的 barrier的queue不能是全域的並發隊列dispatch_queue_t queue = dispatch_queue_create("11", DISPATCH_QUEUE_CONCURRENT);dispatch_async(queue, ^{    for (int i = 0;  i < 100; i++) {        NSLog(@"%@--1", [NSThread currentThread]);    }});dispatch_async(queue, ^{    for (int i = 0;  i < 100; i++) {        NSLog(@"%@--2", [NSThread currentThread]);    }});dispatch_barrier_async(queue, ^{    for (int i = 0;  i < 100; i++) {        NSLog(@"%@--3", [NSThread currentThread]);    }});dispatch_async(queue, ^{    for (int i = 0;  i < 100; i++) {        NSLog(@"%@--4", [NSThread currentThread]);    }});

dispatch_after 順延強制

// 順延強制// 方法1[self performSelector:@selector(run:) withObject:@"參數" afterDelay:2.0];// 方法2dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{    for (NSInteger i = 0; i < 100; i++) {        NSLog(@"%@", [NSThread currentThread]);    }});// 方法3[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:NO];

dispatch_once 整個程式運行中執行一次

// 整個程式中只執行一次static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{    // 一次性代碼});

作用:實現某個類的單粒對象

單例模式:在整個應用程式中,共用一份資源(這份資源只需要建立初始化1次)

static id _person;+ (instancetype)sharePerson{    static dispatch_once_t onceToken;    dispatch_once(&onceToken, ^{        _person = [[super alloc] init];    });    return _person;}+ (instancetype)allocWithZone:(struct _NSZone *)zone{    static dispatch_once_t onceToken;    dispatch_once(&onceToken, ^{        _person = [super allocWithZone:zone];    });    return _person;}- (id)copy{    return _person;}

開發中一般自訂成宏,比較方便,一行代碼搞定。

dispatch_apply 快速迭代

樣本小程式:將一個檔案夾中的圖片剪下到另一個檔案夾

// 將圖片剪下到另一個檔案夾裡NSString *from = @"/Users/Ammar/Pictures/壁紙";NSString *to = @"/Users/Ammar/Pictures/to";NSFileManager *manager = [NSFileManager defaultManager];NSArray *subPaths = [manager subpathsAtPath:from];// 快速迭代dispatch_apply(subPaths.count, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t index) {   NSLog(@"%@ - %zd", [NSThread currentThread], index);    NSString *subPath = subPaths[index];    NSString *fromPath = [from stringByAppendingPathComponent:subPath];    NSString *toPath = [to stringByAppendingPathComponent:subPath];    // 剪下    [manager moveItemAtPath:fromPath toPath:toPath error:nil];    NSLog(@"%@---%zd", [NSThread currentThread], index);});

dispatch_group 隊列組

樣本小程式:需求下載圖片1 下載圖片2 將圖片1和圖片2合成新的圖片

// 建立隊列
   dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

// 建立組dispatch_group_t group = dispatch_group_create();// 用組隊列下載圖片1dispatch_group_async(group, queue, ^{    NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://7xjanq.com1.z0.glb.clouddn.com/6478.jpg"]];    self.image1 = [UIImage imageWithData:data];    NSLog(@"1%@", [NSThread currentThread]);});// 用組隊列下載圖片2dispatch_group_async(group, queue, ^{    NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://7xjanq.com1.z0.glb.clouddn.com/6478.jpg"]];    self.image2 = [UIImage imageWithData:data];    NSLog(@"2%@", [NSThread currentThread]);});// 將圖片1和圖片2合成一張圖片dispatch_group_notify(group, queue, ^{    CGFloat imageW = self.imageView.bounds.size.width;    CGFloat imageH = self.imageView.bounds.size.height;    // 開啟位元影像上下文    UIGraphicsBeginImageContext(self.imageView.bounds.size);    // 畫圖    [self.image1 drawInRect:CGRectMake(0, 0, imageW * 0.5, imageH)];    [self.image2 drawInRect:CGRectMake(imageW * 0.5, 0, imageW * 0.5, imageH)];    // 將圖片取出    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();    // 關閉圖形上下文    UIGraphicsEndImageContext();    // 在主線程上顯示圖片    dispatch_async(dispatch_get_main_queue(), ^{        self.imageView.image = image;    });    NSLog(@"3%@", [NSThread currentThread]);});
GCD定時器

GCD定時器不受Mode影響因此比NSTimer要準確

static int count = 0;dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{    // 這句話的意思現在很好懂了});// GCD定時器dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);// 1.建立一個定時器源// 參1:類型定時器// 參2:控制代碼 // 參3:mask傳0 // 參4:隊列  (注意:dispatch_source_t本質是OC對象,表示源)self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);// 嚴謹起見,時間間隔需要用單位int64_t,做乘法以後單位就變了// 下面這句代碼錶示回呼函數時間間隔是多少int64_t interval = (int64_t)(2.0 * NSEC_PER_SEC); // 如何設定開始時間 CGD給我們了一個設定時間的方法  // 參1:dispatch_time_t when 傳一個時間, delta是增量dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)); // 從現在起3秒後開始// 2.設定定時器的各種屬性// 參1:timer // 參2:開始時間 // 參3:時間間隔 // 參4:傳0 不需要   DISPATCH_TIME_NOW 表示現在 GCD 時間用 NS 表示dispatch_source_set_timer(self.timer, start, interval, 0);// 3.設定回調(即每次間隔要做什麼事情)dispatch_source_set_event_handler(self.timer, ^{    NSLog(@"----------------%@", [NSThread currentThread]);    // 如果希望做5次就停掉    count++;    if (count == 5) {        dispatch_cancel(self.timer);        self.timer = nil;    }});// 4.啟動定時器  (恢複)dispatch_resume(self.timer);

講完 GCD 就該講講 NSOperation,它是 GCD 的物件導向的封裝,使用起來也更方便,

NSOperation實現多線程

NSOperation是個抽象類別,並不具備封裝操作的能力,必須使用它的子類

NSInvocationOperationNSBlockOperation自訂子類繼承NSOperation,實現內部相應的方法

使用 NSOperation 實現多線程的步驟:

建立任務 NSOperation 對象建立 NSOperationQueue 隊列將任務 NSOperation 對象 add 到 NSOperationQueue 隊列中去

NSInvocationOperation

代碼如下:

NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];[op start];

注意:預設情況下,調用了start方法後並不會開一條新的線程去執行,而是在當前線程同步執行操作,只有將 NSOperation 放到一個 NSOperationQueue 中,才會非同步執行操作

NSBlockOperation
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{    // 在主線程      NSLog(@"下載1------%@", [NSThread currentThread]);}];// 添加額外的任務(在子線程執行),封裝數大於1才會非同步執行[op addExecutionBlock:^{    NSLog(@"下載2------%@", [NSThread currentThread]);}];

自訂Operation:需要實現- (void)main方法,需要做的事情放在mian方法中

NSOperationQueue

使用NSOperationQueue建立隊列:主隊列和全域隊列

// 建立一個其他隊列(包括串列隊列和並發隊列) 放到這個隊列中的NSOperation對象會自動放到子線程中執行NSOperationQueue *queue = [[NSOperationQueue alloc] init];// 建立一個主隊列,放到這個隊列中的NSOperation對象會自動放到子線程中執行NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];// 表示並發數量:即同時執行任務的最大數。queue.maxConcurrentOperationCount = 1;
隊列的取消、暫停、恢複:
// NSOpertion的 - cancel 方法也可以停止單個操作- (void)cancelAllOperations; // YES代表暫停隊列,NO代表恢複隊列- (void)setSuspended:(BOOL)b;
添加依賴
NSOperationQueue *queue = [[NSOperationQueue alloc] init];NSBlockOperation *block1 = [NSBlockOperation blockOperationWithBlock:^{    NSLog(@"download1 -------------- %@", [NSThread currentThread]);}];NSBlockOperation *block2 = [NSBlockOperation blockOperationWithBlock:^{    NSLog(@"download2 -------------- %@", [NSThread currentThread]);}];NSBlockOperation *block3 = [NSBlockOperation blockOperationWithBlock:^{    NSLog(@"download3 -------------- %@", [NSThread currentThread]);}];// 添加依賴: block1 和 block2執行完後 再執行 block3  block3依賴於block1和block2// 給block3添加依賴 讓block3在block1和block2之後執行[block3 addDependency:block1];[block3 addDependency:block2];[queue addOperation:block1];[queue addOperation:block2];[queue addOperation:block3];

注意:不能循環相依性,但可以跨隊列依賴,不管NSOperation對象在哪個隊列。只要是兩個NSOperation對象就可以依賴

線程間通訊

樣本:下載圖片

// 下載圖片 operation實現線程間通訊[[[NSOperationQueue alloc] init] addOperation:[NSBlockOperation blockOperationWithBlock:^{    UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://7xjanq.com1.z0.glb.clouddn.com/6478.jpg"]]];    // 返回主線程    [[NSOperationQueue mainQueue] addOperation:[NSBlockOperation blockOperationWithBlock:^{        self.imageView.image = image;    }]];}]];

樣本:下載圖片1和圖片2 併合成圖片

NSOperationQueue *queue = [[NSOperationQueue alloc] init];// 下載圖片1__block UIImage *image1 = nil;NSBlockOperation *block1 = [NSBlockOperation blockOperationWithBlock:^{    image1 = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://7xjanq.com1.z0.glb.clouddn.com/6478.jpg"]]];}];// 下載圖片2__block UIImage *image2 = nil;NSBlockOperation *block2 = [NSBlockOperation blockOperationWithBlock:^{    image2 = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://7xjanq.com1.z0.glb.clouddn.com/6478.jpg"]]];}];CGFloat imageW = self.imageView.bounds.size.width;CGFloat imageH = self.imageView.bounds.size.height;// 合成圖片NSBlockOperation *block3 = [NSBlockOperation blockOperationWithBlock:^{    UIGraphicsBeginImageContext(CGSizeMake(imageW, imageH));    [image1 drawInRect:CGRectMake(0, 0, imageW * 0.5, imageH)];    [image2 drawInRect:CGRectMake(0.5 * imageW, 0, 0.5 * imageW, imageH)];    UIImage *image3 = UIGraphicsGetImageFromCurrentImageContext();    UIGraphicsEndImageContext();    // 切換回主線程顯示圖片    [[NSOperationQueue mainQueue] addOperation:[NSBlockOperation blockOperationWithBlock:^{        self.imageView.image = image3;    }]];}];// 設定依賴[block3 addDependency:block1];[block3 addDependency:block2];// 新增工作到隊列中[queue addOperation:block1];[queue addOperation:block2];[queue addOperation:block3];
應用

應用:SDWebImage 架構的底層主要功能實現就是基於多線程,使用多線程,我們可以實現小圖片的多圖片下載。這裡的邏輯其實是比較複雜的

實現小圖片的多圖片下載思路:

代碼實現見本文代碼。

本文代碼見:Multithreading

https://github.com/lizhaoLoveIT/Multithreading

iOS開發多線程--技術方案

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.