iOS底層系列24 -- 多線程的實現(xiàn)

  • 本文主要探索NSThread,GCD,NSOperation這三種實現(xiàn)多線程的方式;

NSThread

  • NSthread是蘋果官方提供面向?qū)ο蟮木€程操作技術(shù),是對thread的上層封裝,比較偏向于底層,簡單方便,可以直接操作線程對象,使用頻率較少;
NSThread的初始化創(chuàng)建
【第一種】:alloc方法
- (void)viewDidLoad {
    [super viewDidLoad];
    
    //第一種方式: alloc 需手動開啟
    YYThread *thread = [[YYThread alloc]initWithTarget:self selector:@selector(task:) object:@"thread_name_01"];
    [thread start];
}
- (void)task:(NSObject *)obj{
    NSLog(@"%@ - %@", obj, [NSThread currentThread]);
}
  • LLDB調(diào)試結(jié)果:
Snip20210323_24.png
  • YYThread自定義線程繼承自NSThread;
  • 會創(chuàng)建子線程執(zhí)行任務(wù)(task:方法);
  • 需要手動的啟動,手動調(diào)用start方法;
【第二種】:detachNewThreadSelector類方法
- (void)viewDidLoad {
    [super viewDidLoad];
    
    [NSThread detachNewThreadSelector:@selector(task:) toTarget:self withObject:@"thread_name_02"];
}
- (void)task:(NSObject *)obj{
    NSLog(@"%@ - %@", obj, [NSThread currentThread]);
}
  • LLDB調(diào)試結(jié)果:
Snip20210323_25.png
  • detachNewThreadSelector類方法會生成子線程執(zhí)行任務(wù)(task:方法);
【第三種】:performSelector方法
  • 調(diào)用performSelectorOnMainThread方法,若當(dāng)前線程是主線程,會在主線程中立刻執(zhí)行selector任務(wù)方法;
- (void)viewDidLoad {
    [super viewDidLoad];
    //當(dāng)前線程為主線程
    [self performSelectorOnMainThread:@selector(task_perform:) withObject:@"thread_name_04" waitUntilDone:YES];
}
- (void)task_perform:(NSObject *)obj{
    NSLog(@"%@ - %@", obj, [NSThread currentThread]);
}
Snip20210323_26.png
  • 調(diào)用performSelectorOnMainThread方法,若當(dāng)前線程是子線程,waitUntilDone = YES時,會阻塞當(dāng)前子線程,當(dāng)主線程執(zhí)行完selector任務(wù)方法時,解除子線程的阻塞,繼續(xù)執(zhí)行子線程中的任務(wù);
- (void)viewDidLoad {
    [super viewDidLoad];
    YYThread *thread = [[YYThread alloc]initWithTarget:self selector:@selector(task:) object:@"thread_name_01"];
    [thread start];
}

- (void)task:(NSObject *)obj{
    NSLog(@"%@ - %@", obj, [NSThread currentThread]);
    //當(dāng)前線程為子線程
    [self performSelectorOnMainThread:@selector(task_perform:) withObject:@"thread_name_04" waitUntilDone:YES];
}

- (void)task_perform:(NSObject *)obj{
    NSLog(@"%@ - %@", obj, [NSThread currentThread]);
}
Snip20210323_27.png
  • task方法中的執(zhí)行完打印之后,子線程阻塞,當(dāng)主線程執(zhí)行完task_perform之后,子線程解除阻塞,然后繼續(xù)執(zhí)行,子線程任務(wù)執(zhí)行完,子線程銷毀;
  • 調(diào)用performSelectorOnMainThread方法,若當(dāng)前線程是子線程,waitUntilDone = NO時,不會阻塞當(dāng)前子線程,子線程中任務(wù)繼續(xù)執(zhí)行,selector任務(wù)方法在主線程中執(zhí)行;
  • 將上面的代碼,waitUntilDone入?yún)⒏某蒒O,LLDB結(jié)果如下:
Snip20210323_28.png
  • task方法中的執(zhí)行完打印之后,子線程不會阻塞,繼續(xù)執(zhí)行,子線程任務(wù)執(zhí)行完成后直接銷毀;
  • 主線程執(zhí)行task_perform方法;
  • 總結(jié):
    • 若當(dāng)前線程為子線程,performSelectorOnMainThread:withObject:waitUntilDone方法中的參數(shù)waitUntilDone決定了是否阻塞當(dāng)前子線程,即同步/異步;
    • 調(diào)用performSelectorInBackground方法,開啟新的線程在后臺執(zhí)行test方法任務(wù);
    • 調(diào)用performSelector:onThread方法,在指定的線程執(zhí)行任務(wù);
NSThread常見方法和屬性
  • [NSThread currentThread] 獲取當(dāng)前線程信息
  • NSThread sleepUntilDate: 當(dāng)前線程休眠到指定時間
  • NSThread sleepForTimeInterval: 當(dāng)前線程休眠多長時間
  • [NSThread exit] 當(dāng)前線程退出
  • [NSThread mainThread] 獲取主線程
  • [NSThread isMainThread] 判斷當(dāng)前線程是否時主線程
  • [NSThread mainThread] 獲取主線程
  • thread.isExecuting 線程是否在執(zhí)行
  • thread.isCancelled 線程是否被取消
  • thread.isFinished 線程是否完成
  • thread.isMainThread 線程是否是主線程
  • thread.threadPriority 線程的優(yōu)先級,取值范圍0.0-1.0,默認(rèn)優(yōu)先級0.5,1.0表示最高優(yōu)先級,優(yōu)先級高,CPU調(diào)度的頻率高

GCD

dispatch_after
  • 表示隊列中的block任務(wù)延遲指定的時間執(zhí)行;
- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"aaaaa");
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"3333");
    });
}
  • 主隊列中的block任務(wù)延遲3秒再執(zhí)行;
dispatch_once
  • 保證在App運行期間,block中的代碼只會執(zhí)行一次;
//創(chuàng)建單例
+ (instancetype)sharedInstance{
    static dispatch_once_t onceToken;
    static id instance;
    dispatch_once(&onceToken, ^{
        instance = [[self alloc]init];
    });
    return instance;
}
dispatch_apply
  • 將耗時任務(wù),以指定的次數(shù),追加到隊列中,并等待任務(wù)全部執(zhí)行結(jié)束;
  • 使用場景:for循環(huán)中有大量耗時任務(wù);
【第一種解決方案】:在for循環(huán)中耗時任務(wù),都異步開辟子線程去執(zhí)行
- (void)viewDidLoad {
    [super viewDidLoad];
    //在for循環(huán)中每一次任務(wù)都放到子線程中執(zhí)行
    for (int i = 0; i < 1000; i++) {
        dispatch_async(dispatch_queue_create(nil, DISPATCH_QUEUE_CONCURRENT), ^{
            [NSThread sleepForTimeInterval:1]; // 模擬耗時操作
            NSLog(@"%d -- %@", i, [NSThread currentThread]);
        });
    }
}
  • 從打印結(jié)果來看,1000個耗時任務(wù)能很快執(zhí)行完,但是開辟了將近70個子線程來執(zhí)行耗時任務(wù),這樣會耗費大量的資源,容易導(dǎo)致App的崩潰,所以第一種方案不可?。?/li>
【第二種解決方案】:使用dispatch_apply函數(shù)
- (void)viewDidLoad {
    [super viewDidLoad];
    
     dispatch_async(dispatch_queue_create(nil, DISPATCH_QUEUE_CONCURRENT), ^{
        dispatch_apply(1000, dispatch_queue_create(nil, DISPATCH_QUEUE_CONCURRENT), ^(size_t index) {
            [NSThread sleepForTimeInterval:1];//模擬耗時操作
            NSLog(@"%zu -- %@", index, [NSThread currentThread]);
        });
    });
}
  • 使用dispatch_apply函數(shù)將block任務(wù)循環(huán)1000次,加入并發(fā)隊列中;
  • 從打印結(jié)果來看,1000個耗時任務(wù)執(zhí)行完成需要耗費很長一段時間,但是只開啟了五六個子線程循環(huán)執(zhí)行耗時任務(wù),不會耗費大量資源;
  • 現(xiàn)在定義一個數(shù)組,然后使用dispatch_apply函數(shù)循環(huán)打印數(shù)組中元素,代碼實現(xiàn)如下所示:
- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"-- begin --");
    NSArray *arr = @[@"a", @"b", @"c", @"d", @"e"];
    dispatch_queue_t queue = dispatch_queue_create("com.jarypan.gcdsummary", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_apply(arr.count, queue, ^(size_t index) {
        [NSThread sleepForTimeInterval:2];
        NSLog(@"index = %zu, str = %@ -- %@", index, arr[index], [NSThread currentThread]);
    });
    NSLog(@"-- end --");
}
  • LLDB調(diào)試結(jié)果:
Snip20210324_29.png
  • 使用dispatch_apply函數(shù)往并發(fā)隊列中循環(huán)添加了5個任務(wù);
  • 開啟多個子線程去執(zhí)行任務(wù),在執(zhí)行任務(wù)時會阻塞主線程,只有當(dāng)dispatch_apply添加到并發(fā)隊列中的任務(wù)全部執(zhí)行完,才會解除主線程的阻塞,主線程才能繼續(xù)執(zhí)行;
  • 為了不阻塞主線程的執(zhí)行,我們將dispatch_apply函數(shù)放到異步子線程中去執(zhí)行,代碼改造之后如下:
- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"-- begin --");
    NSArray *arr = @[@"a", @"b", @"c", @"d", @"e"];
    dispatch_queue_t queue = dispatch_queue_create("com.jarypan.gcdsummary", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"current thread -- %@", [NSThread currentThread]);
        dispatch_apply(arr.count, queue, ^(size_t index) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"index = %zu, str = %@ -- %@", index, arr[index], [NSThread currentThread]);
        });
        NSLog(@" current thread - end ");
    });
    NSLog(@"-- end --");
}
  • LLDB調(diào)試結(jié)果:
Snip20210324_30.png
  • 可以看到dispatch_apply函數(shù)會阻塞當(dāng)前子線程的繼續(xù)執(zhí)行,當(dāng)追加到并發(fā)隊列中任務(wù)執(zhí)行完成,才會解除子線程的阻塞
  • 任務(wù)循環(huán)添加到并發(fā)隊列,但是任務(wù)執(zhí)行卻是串行執(zhí)行,并沒有實現(xiàn)我們想要的并發(fā)執(zhí)行,(為什么?),如果將上面的自定義并發(fā)隊列改成全局隊列就可以實現(xiàn)任務(wù)的并發(fā)執(zhí)行;
- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"-- begin --");
    NSArray *arr = @[@"a", @"b", @"c", @"d", @"e"];
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
        NSLog(@"current thread -- %@", [NSThread currentThread]);
        dispatch_apply(arr.count, queue, ^(size_t index) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"index = %zu, str = %@ -- %@", index, arr[index], [NSThread currentThread]);
        });
        NSLog(@" current thread - end ");
    });
    NSLog(@"-- end --");
}
  • LLDB調(diào)試結(jié)果:
Snip20210324_31.png
dispatch_apply會造成死鎖的場景
第一種:在主線程使用dispatch_apply往主隊列中添加任務(wù)
- (void)viewDidLoad {
    [super viewDidLoad];
    
    dispatch_apply(10, dispatch_get_main_queue(), ^(size_t index) {
        NSLog(@"%zu", index);
    });
}
  • dispatch_apply往主隊列中添加block任務(wù),而viewDidLoad已在主線中執(zhí)行,block任務(wù)會等待viewDidLoad執(zhí)行完再執(zhí)行;
  • 由于dispatch_apply會阻塞主線程的繼續(xù)執(zhí)行,viewDidLoad會等待block任務(wù)執(zhí)行完才會繼續(xù)執(zhí)行完成;造成viewDidLoad與block任務(wù)的相互等待形成死鎖;
  • dispatch_apply可以看成dispatch_sync
第二種情況:使用dispatch_apply往異步串行隊列中追加任務(wù)
- (void)viewDidLoad {
    [super viewDidLoad];
    
    dispatch_queue_t queue = dispatch_queue_create(nil, DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{//block1
        dispatch_apply(10, queue, ^(size_t index) {//block2
            NSLog(@"%zu", index);
        });
    });
}
dispatch_group調(diào)度組
  • dispatch_group_asyncdispatch_group_enter,dispatch_group_leave,dispatch_group_notifydispatch_group_wait配合使用;
  • 使用場景:多個異步網(wǎng)絡(luò)請求,回調(diào)統(tǒng)一處理;
- (void)viewDidLoad {
    [super viewDidLoad];

    self.bookId = @"11186718404761703yw";
    
    //創(chuàng)建隊列組
    self.group = dispatch_group_create();
    //創(chuàng)建隊列
    self.queue = dispatch_get_global_queue(0, 0);
    
    dispatch_group_async(self.group, self.queue, ^{
        [self loadBookView];
    });
    
    dispatch_group_async(self.group, self.queue, ^{
        [self loadBookEvalute];
    });
    
    dispatch_group_async(self.group, self.queue, ^{
        [self loadBookRecommandList];
    });
    
    long timeout = dispatch_group_wait(self.group, dispatch_time(DISPATCH_TIME_NOW, 1 *NSEC_PER_SEC));
    NSLog(@"timeout = %ld", timeout);
    if (timeout == 0) {
        NSLog(@"按時完成任務(wù)");
    }else{
        NSLog(@"超時");
    }
    
    dispatch_group_notify(self.group, self.queue, ^{
        NSLog(@"同步所有異步請求");
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"刷新UI");
        });
    });
}
- (void)loadBookView{
    ///請求一
    dispatch_group_enter(self.group);
    [YYRequestBookApi requestBookViewWithBookId:self.bookId success:^(id  _Nonnull response) {
        dispatch_group_leave(self.group);
    } fail:^(NSString * _Nonnull error) {
        dispatch_group_leave(self.group);
    }];
}

- (void)loadBookEvalute{
    ///請求二
    dispatch_group_enter(self.group);
    [YYRequestBookApi requestBookEvaluteWithBookId:self.bookId success:^(id  _Nonnull response) {
        dispatch_group_leave(self.group);
    } fail:^(NSString * _Nonnull error) {
        dispatch_group_leave(self.group);
    }];
}

- (void)loadBookRecommandList{
    ///請求三
    dispatch_group_enter(self.group);
    [YYRequestBookApi requestBookRecommentWithBookId:self.bookId success:^(id  _Nonnull response) {
        dispatch_group_leave(self.group);
    } fail:^(NSString * _Nonnull error) {
        dispatch_group_leave(self.group);
    }];
}
  • dispatch_group_enterdispatch_group_leave需要配對使用;
  • dispatch_group_wait設(shè)置調(diào)度組等待的超時時間(即等多久);
  • 當(dāng)三個網(wǎng)絡(luò)請求的回調(diào)數(shù)據(jù)都回來之后,在dispatch_group_notify函數(shù)中做統(tǒng)一處理;
dispatch_barrier柵欄函數(shù)
  • 可實現(xiàn)并發(fā)隊列中的任務(wù),按照順序執(zhí)行;
  • dispatch_barrier分為兩種分別為dispatch_barrier_syncdispatch_barrier_async
- (void)viewDidLoad {
    [super viewDidLoad];
    dispatch_queue_t queue = dispatch_queue_create("com.lyy.queue", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@" start ");
    dispatch_async(queue, ^{
        sleep(5);
        NSLog(@"---1--- %@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"---2--- %@",[NSThread currentThread]);
    });
    
    dispatch_barrier_sync(queue, ^{
        NSLog(@" 柵欄函數(shù) -- %@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"---3--- %@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"---4--- %@",[NSThread currentThread]);
    });
    NSLog(@" end ");
}
  • 并發(fā)隊列中追加四個任務(wù),任務(wù)1是耗時任務(wù),子線程休眠5秒;dispatch_barrier_sync柵欄同步添加在四個任務(wù)的正中間;
Snip20210324_32.png
  • 可以看出dispatch_barrier_sync會阻塞主線程的繼續(xù)執(zhí)行,當(dāng)其block執(zhí)行完成時,主線程解除阻塞繼續(xù)執(zhí)行;
  • dispatch_barrier_sync柵欄同步將并發(fā)隊列的任務(wù)執(zhí)行分割開,必須等任務(wù)1(耗時任務(wù))與任務(wù)2都執(zhí)行完畢時才會執(zhí)行柵欄同步函數(shù),柵欄同步函數(shù)執(zhí)行完成之后再執(zhí)行任務(wù)3,任務(wù)4。
  • 將上面的dispatch_barrier_sync柵欄同步改成dispatch_barrier_async柵欄異步,調(diào)試結(jié)果如下:
Snip20210324_34.png
  • 可以看出dispatch_barrier_async柵欄異步不會阻塞主線程,end會立即執(zhí)行且會開辟子線程,在子線程中執(zhí)行;
  • dispatch_barrier_async柵欄異步將并發(fā)隊列的任務(wù)執(zhí)行分割開,必須等任務(wù)1(耗時任務(wù))與任務(wù)2都執(zhí)行完畢時才會執(zhí)行柵欄異步函數(shù),柵欄異步函數(shù)執(zhí)行完成之后再執(zhí)行任務(wù)3,任務(wù)4。
dispatch_semaphore_t信號量
  • 主要涉及三個函數(shù)如下:
    • dispatch_semaphore_create()創(chuàng)建信號量,并設(shè)定一個初始信號量值;
    • dispatch_semaphore_signal()發(fā)送信號量,信號量值+1;
    • dispatch_semaphore_wait() 等待信號量,信號量值-1;
  • 當(dāng)信號量值 < 0時,會阻塞當(dāng)前線程的繼續(xù)執(zhí)行,當(dāng)信號量>=0時,當(dāng)前線程解除阻塞,會繼續(xù)執(zhí)行接下來的邏輯;
應(yīng)用場景一:配合dispatch_group實現(xiàn)多個異步網(wǎng)絡(luò)請求,回調(diào)統(tǒng)一處理,實現(xiàn)如下:
- (void)viewDidLoad {
    [super viewDidLoad];
    self.bookId = @"11186718404761703yw";
    
    self.semphore = dispatch_semaphore_create(0);
    
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_group_async(group, queue, ^{
        [self loadBookView];
    });
    
    dispatch_group_async(group, queue, ^{
        [self loadBookEvalute];
    });
    
    dispatch_group_async(group, queue, ^{
        [self loadBookRecommandList];
    });
    
    dispatch_group_notify(group, queue, ^{
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"刷新UI");
        });
    });
}
- (void)loadBookView{
    ///請求一
    [YYRequestBookApi requestBookViewWithBookId:self.bookId success:^(id  _Nonnull response) {
        dispatch_semaphore_signal(self.semphore);
    } fail:^(NSString * _Nonnull error) {
        dispatch_semaphore_signal(self.semphore);
    }];
    dispatch_semaphore_wait(self.semphore, DISPATCH_TIME_FOREVER);
}

- (void)loadBookEvalute{
    ///請求二
    [YYRequestBookApi requestBookEvaluteWithBookId:self.bookId success:^(id  _Nonnull response) {
        dispatch_semaphore_signal(self.semphore);
    } fail:^(NSString * _Nonnull error) {
        dispatch_semaphore_signal(self.semphore);
    }];
     dispatch_semaphore_wait(self.semphore, DISPATCH_TIME_FOREVER);
}

- (void)loadBookRecommandList{
    ///請求三
    [YYRequestBookApi requestBookRecommentWithBookId:self.bookId success:^(id  _Nonnull response) {
        dispatch_semaphore_signal(self.semphore);
    } fail:^(NSString * _Nonnull error) {
        dispatch_semaphore_signal(self.semphore);
    }];
     dispatch_semaphore_wait(self.semphore, DISPATCH_TIME_FOREVER);
}
應(yīng)用場景二:實現(xiàn)異步并發(fā)(串行)隊列中的任務(wù),依次順序執(zhí)行,如果是隊列中的任務(wù)是網(wǎng)絡(luò)請求,也是可以控制網(wǎng)絡(luò)請求回調(diào)的順序執(zhí)行
- (void)viewDidLoad {
    [super viewDidLoad];
    
    dispatch_semaphore_t sem = dispatch_semaphore_create(0);
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        sleep(1);
        NSLog(@"任務(wù)1:%@",[NSThread currentThread]);
        dispatch_semaphore_signal(sem);//2
    });
    
    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);//1
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        sleep(1);
        NSLog(@"任務(wù)2:%@",[NSThread currentThread]);
        dispatch_semaphore_signal(sem);//4
    });
    
    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);//3
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{//5
        sleep(1);
        NSLog(@"任務(wù)3:%@",[NSThread currentThread]);
    });
}
Snip20210324_35.png
  • 信號量的初始值為0,首先執(zhí)行到1,等待信號量信號量減1,變成-1會阻塞當(dāng)前主線程的繼續(xù)執(zhí)行,所以dispatch_semaphore_wait后面的代碼執(zhí)行不了;
  • 第一個dispatch_async函數(shù)開始執(zhí)行,當(dāng)執(zhí)行到2時,發(fā)送信號量,信號量+1變成0,那么主線程解除阻塞繼續(xù)執(zhí)行,來到3等待信號量信號量減1,又變成-1,又會阻塞當(dāng)前主線程,后面的代碼不能執(zhí)行;
  • 第二個dispatch_async函數(shù)開始執(zhí)行,當(dāng)執(zhí)行到4時,發(fā)送信號量,信號量+1變成0,那么主線程解除阻塞繼續(xù)執(zhí)行,執(zhí)行第三個dispatch_async函數(shù);
應(yīng)用場景三:控制異步并發(fā)的數(shù)量
- (void)viewDidLoad {
    [super viewDidLoad];
    
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(5);
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    for (int i = 0; i < 20; i++) {
        dispatch_async(queue, ^{
            //等待降低信號量
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            NSLog(@"run task %d -- %@",i,[NSThread currentThread]);
            sleep(2);
            NSLog(@"complete task %d -- %@",i,[NSThread currentThread]);
            //提高信號量
            dispatch_semaphore_signal(semaphore);
        });
    }
}
  • 當(dāng)信號量初始值設(shè)置為3時,LLDB調(diào)試如下:
Snip20210324_36.png
  • 當(dāng)信號量初始值設(shè)置為5時,LLDB調(diào)試如下:
Snip20210324_37.png
  • 可以看到信號量的初始值,控制了異步任務(wù)的并發(fā)數(shù),即同一時刻任務(wù)執(zhí)行的數(shù)量;
dispatch_source_t
  • dispatch_source_t主要用于計時操作,計時精準(zhǔn)度比NSTimer高,其原因是因為它創(chuàng)建的timer不依賴于RunLoop;
  • 常見API方法如下:
    • dispatch_source_create: 創(chuàng)建事件源
    • dispatch_source_set_event_handler: 設(shè)置數(shù)據(jù)源回調(diào)
    • dispatch_source_merge_data: 設(shè)置事件源數(shù)據(jù)
    • dispatch_source_get_data: 獲取事件源數(shù)據(jù)
    • dispatch_resume: 繼續(xù)
    • dispatch_suspend: 掛起
    • dispatch_cancle: 取消
- (void)viewDidLoad {
    [super viewDidLoad];
    
    //1.創(chuàng)建隊列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    //2.創(chuàng)建timer
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    self.timer = timer;
    //3.設(shè)置timer首次執(zhí)行時間,間隔,精確度
    dispatch_source_set_timer(self.timer, DISPATCH_TIME_NOW, 2.0*NSEC_PER_SEC, 0);
    //4.設(shè)置timer事件回調(diào)
    dispatch_source_set_event_handler(self.timer, ^{
        NSLog(@"GCDTimer");
    });
    //5.默認(rèn)是掛起狀態(tài),需要手動激活
    dispatch_resume(self.timer);
}

NSOperation

  • NSOperation是基于GCD之上的更高一層封裝,NSOperation需要配合NSOperationQueue來實現(xiàn)多線程;
NSInvocationOperation與NSBlockOperation
  • NSOperation是抽象類,在實際使用的中需要使用它的兩個子類NSInvocationOperationNSBlockOperation
- (void)viewDidLoad {
    [super viewDidLoad];
    
    //創(chuàng)建NSInvocationOperation實例對象 并綁定操作方法
    NSInvocationOperation *operation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task) object:nil];
    //start開始執(zhí)行操作
    [operation start];
    
    //創(chuàng)建NSBlockOperation實例對象 在Block中添加任務(wù)操作
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:1];//模擬耗時操作
        NSLog(@"NSBlockOperation excute - %@", [NSThread currentThread]);
    }];
    //start開始執(zhí)行操作
    [operation1 start];
}
- (void)task{
    NSLog(@"NSInvocationOperation excute - %@",[NSThread currentThread]);
}
  • LLDB調(diào)試結(jié)果:
Snip20210324_38.png
  • 單獨使用NSInvocationOperation與NSBlockOperation時,在主線程中執(zhí)行任務(wù)操作;
  • NSBlockOperation還可以調(diào)用addExecutionBlock函數(shù),給NSBlockOperation添加其他block任務(wù),這些任務(wù)在子線程中并發(fā)執(zhí)行
- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"1---%@", [NSThread currentThread]);
    }];

    [operation addExecutionBlock:^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"2---%@", [NSThread currentThread]);
    }];

    [operation addExecutionBlock:^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"3---%@", [NSThread currentThread]);
    }];

    [operation start];
}
  • LLDB調(diào)試結(jié)果:
Snip20210324_39.png
自定義NSOperation
#import <Foundation/Foundation.h>

@interface YYOperation : NSOperation

@end
#import "YYOperation.h"

@implementation YYOperation

//重寫main方法
- (void)main{
    if (!self.isCancelled) {
        [NSThread sleepForTimeInterval:2];
        NSLog(@"main --- %@", [NSThread currentThread]);
    }
}

@end
- (void)viewDidLoad {
    [super viewDidLoad];
    
    YYOperation *operation = [[YYOperation alloc]init];
    [operation start];
}
  • main函數(shù)中的任務(wù)操作在主線程中執(zhí)行;
NSOperationQueue的使用
  • 其使用步驟如下:
    • 1、創(chuàng)建任務(wù):先將需要執(zhí)行的任務(wù)封裝到NSOperation對象中;
    • 2、創(chuàng)建隊列:創(chuàng)建NSOperationQueue;
    • 3、將任務(wù)加入到隊列中:將NSOperation操作對象添加到NSOperationQueue中;
- (void)viewDidLoad {
    [super viewDidLoad];
    
    //1.獲取主隊列
    NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
    //2.創(chuàng)建操作
    //使用 NSInvocationOperation 創(chuàng)建操作1
    NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil];
    
    //使用 NSInvocationOperation 創(chuàng)建操作2
    NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task2) object:nil];
    
    //使用 NSBlockOperation 創(chuàng)建操作3
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
            [NSThread sleepForTimeInterval:2]; 
            NSLog(@"3---%@", [NSThread currentThread]); 
    }];
    
    [op3 addExecutionBlock:^{
            [NSThread sleepForTimeInterval:2]; 
            NSLog(@"4---%@", [NSThread currentThread]); 
    }];
    
    //3.調(diào)用addOperation: 添加所有操作到主隊列中
    [mainQueue addOperation:op1]; //[op1 start]
    [mainQueue addOperation:op2]; //[op2 start]
    [mainQueue addOperation:op3]; //[op3 start]
}
- (void)task1{
     [NSThread sleepForTimeInterval:2]; 
      NSLog(@"1---%@", [NSThread currentThread]); 
}
- (void)task2{
      [NSThread sleepForTimeInterval:2]; // 模擬耗時操作
      NSLog(@"2---%@", [NSThread currentThread]); // 打印當(dāng)前線程
}
  • LLDB調(diào)試結(jié)果:
Snip20210324_42.png
  • 創(chuàng)建的操作添加到NSOperationQueue中,不用再調(diào)用start方法來啟動執(zhí)行,是由系統(tǒng)去啟動執(zhí)行操作
  • 添加到主隊列中的操作都是在主線程中,依次執(zhí)行,由于op3調(diào)用addExecutionBlock添加的操作會并發(fā)執(zhí)行;
  • 將上面的隊列改成自定義隊列 NSOperationQueue *queue = [[NSOperationQueue alloc]init],LLDB調(diào)試如下:
Snip20210324_43.png
  • 可以看到操作任務(wù)添加到自定義隊列中,是在子線程中并發(fā)執(zhí)行;
往NSOperationQueue中直接添加block任務(wù)
- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    for (int i = 0; i < 5; i++) {
        [queue addOperationWithBlock:^{
            NSLog(@"%@---%d", [NSThread currentThread], i);
        }];
    }
}
  • LLDB調(diào)試結(jié)果如下:
Snip20210324_44.png
  • NSOperationQueue調(diào)用addOperationWithBlock直接往操作隊列中追加block任務(wù);
  • 由于添加到自定義操作隊列,所以所有任務(wù)并發(fā)執(zhí)行;
往NSOperationQueue設(shè)置并發(fā)數(shù)
  • 在GCD中我們通過信號量dispatch_semaphore_t,控制異步并發(fā)隊列的并發(fā)數(shù);
  • 在NSOperationQueue中我們直接通過其屬性maxConcurrentOperationCount,控制自定義NSOperationQueue操作隊列的并發(fā)數(shù);
- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    queue.name = @"lyy.queue";
    queue.maxConcurrentOperationCount = 2;
    
    for (int i = 0; i < 10; i++) {
        [queue addOperationWithBlock:^{ // 一個任務(wù)
            [NSThread sleepForTimeInterval:2];
            NSLog(@"%d-%@",i,[NSThread currentThread]);
        }];
    }
}
  • LLDB調(diào)試結(jié)果如下:
Snip20210324_45.png
  • 當(dāng)自定義操作隊列的最大并發(fā)數(shù)設(shè)置為2時,表明同一時刻只開辟兩個子線程執(zhí)行任務(wù);當(dāng)最大并發(fā)數(shù)設(shè)置為3時,LLDB調(diào)試結(jié)果如下:
Snip20210324_46.png
NSOperation設(shè)置依賴
  • 任務(wù)操作之間添加依賴,代碼實現(xiàn)如下:
- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"請求token");
    }];
    
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"拿著token,請求數(shù)據(jù)1");
    }];
    
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"拿著數(shù)據(jù)1,請求數(shù)據(jù)2");
    }];
    
    [op2 addDependency:op1];
    [op3 addDependency:op2];
    
    [queue addOperations:@[op1,op2,op3] waitUntilFinished:YES];
    
    NSLog(@"執(zhí)行完了 -- 我要干其他事");
}
  • LLDB調(diào)試結(jié)果如下:
Snip20210324_47.png
  • op2依賴于op1,表明只有先執(zhí)行op1,才能執(zhí)行op2;
  • op3依賴于op2,表明只有先執(zhí)行op2,才能執(zhí)行op3;
NSOperation設(shè)置優(yōu)先級
  • NSOperation設(shè)置優(yōu)先級只會讓CPU有更高的幾率調(diào)用,不是說設(shè)置高就一定全部先執(zhí)行;
  • 常見優(yōu)先級有如下:從高到低
    • NSQualityOfServiceUserInteractive:用戶交互級別:最高級別,通常用于響應(yīng)用戶操作的UI處理,如將圖像繪制到屏幕上;
    • NSQualityOfServiceUserInitiated:用戶發(fā)起級別:由用戶發(fā)起的僅次于UI的任務(wù),用戶希望立即響應(yīng)并在此任務(wù)完成后進(jìn)行下一步操作。如遠(yuǎn)程內(nèi)容載入;
    • NSQualityOfServiceUtility:工具級別:由用戶或自動發(fā)起,不必要立即響應(yīng),不阻止用戶交互。通常以一個可見的進(jìn)度條來標(biāo)示進(jìn)度,例如預(yù)加載內(nèi)容、上傳或大量文件操作(如媒體導(dǎo)入);
    • NSQualityOfServiceBackground:后臺級別:通常不是由用戶發(fā)起、用戶不可見的。例如備份、索引、數(shù)據(jù)同步等;
    • NSQualityOfServiceDefault:將從其他來源推測QoS,如果無法推斷,將使用 UserInitiated 到 Utility中的一個;
- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 3; i++) {
//            sleep(1);
            NSLog(@"第一個操作 %d --- %@", i, [NSThread currentThread]);
        }
    }];
    
    //設(shè)置最低優(yōu)先級
    op1.qualityOfService = NSQualityOfServiceBackground;
    
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"第二個操作 %d --- %@", i, [NSThread currentThread]);
        }
    }];
    //設(shè)置最高優(yōu)先級
    op2.qualityOfService = NSQualityOfServiceUserInteractive;
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperation:op1];
    [queue addOperation:op2];
}
  • LLDB調(diào)試結(jié)果如下:
Snip20210324_50.png
  • 由于op2的優(yōu)先級比op1優(yōu)先級高,所以先執(zhí)行op2;
  • 若在op1中加入睡眠一秒的操作,且設(shè)置op1的優(yōu)先級最高,op2優(yōu)先級最低,代碼如下所示:
- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 3; i++) {
            sleep(1);
            NSLog(@"第一個操作 %d --- %@", i, [NSThread currentThread]);
        }
    }];
    
    //設(shè)置最高優(yōu)先級
    op1.qualityOfService = NSQualityOfServiceUserInteractive;
    
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"第二個操作 %d --- %@", i, [NSThread currentThread]);
        }
    }];
    //設(shè)置最低優(yōu)先級
    op2.qualityOfService = NSQualityOfServiceBackground;
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperation:op1];
    [queue addOperation:op2];
}
  • LLDB調(diào)試結(jié)果如下:
Snip20210324_51.png
  • 看到即使op1的優(yōu)先級最高,op2優(yōu)先級最低,由于op1有阻塞操作,最終op2首先執(zhí)行,表明NSOperation設(shè)置優(yōu)先級只會讓CPU有更高的幾率調(diào)用,不是說設(shè)置高就一定先執(zhí)行,還要考慮其他因素;
NSOperationQueue實現(xiàn)線程間通信
  • 代碼實現(xiàn)如下:
- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    queue.name = @"lyy.queue";
    [queue addOperationWithBlock:^{
        //子線程執(zhí)行耗時操作
        NSLog(@"耗時操作 -- %@", [NSThread currentThread]);
        //切換到主線程刷新UI
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            NSLog(@"刷新UI -- %@", [NSThread currentThread]);
        }];
    }];
}
  • LLDB調(diào)試結(jié)果如下:
Snip20210324_49.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容