iOS-底層原理 27:GCD 之 NSThread & GCD & NSOperation

iOS 底層原理 文章匯總

本文的主要目的是介紹 NSThread、GCDNSOperation常見的使用方式

NSthread

NSthread是蘋果官方提供面向對象的線程操作技術,是對thread的上層封裝,比較偏向于底層。簡單方便,可以直接操作線程對象,使用頻率較少。

創建線程
線程的創建方式主要以下三種方式

  • 通過init初始化方式創建

  • 通過detachNewThreadSelector構造器方式創建

  • 通過performSelector...方法創建,主要是用于獲取主線程,以及后臺線程

//1、創建
- (void)cjl_createNSThread{
    NSString *threadName1 = @"NSThread1";
    NSString *threadName2 = @"NSThread2";
    NSString *threadName3 = @"NSThread3";
    NSString *threadNameMain = @"NSThreadMain";
    
    //方式一:初始化方式,需要手動啟動
    NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(doSomething:) object:threadName1];
    [thread1 start];
    
    //方式二:構造器方式,自動啟動
    [NSThread detachNewThreadSelector:@selector(doSomething:) toTarget:self withObject:threadName2];
    
    //方式三:performSelector...方法創建
    [self performSelectorInBackground:@selector(doSomething:) withObject:threadName3];
    
    //方式四
    [self performSelectorOnMainThread:@selector(doSomething:) withObject:threadNameMain waitUntilDone:YES];
    
}
- (void)doSomething:(NSObject *)objc{
    NSLog(@"%@ - %@", objc, [NSThread currentThread]);
}

屬性

- thread.isExecuting    //線程是否在執行
- thread.isCancelled    //線程是否被取消
- thread.isFinished     //是否完成
- thread.isMainThread   //是否是主線程
- thread.threadPriority //線程的優先級,取值范圍0.0-1.0,默認優先級0.5,1.0表示最高優先級,優先級高,CPU調度的頻率高

類方法
常用的類方法有以下幾個:

  • currentThread:獲取當前線程

  • sleep...:阻塞線程

  • exit:退出線程

  • mainThread:獲取主線程

- (void)cjl_NSThreadClassMethod{
    //當前線程
    [NSThread currentThread];
    // 如果number=1,則表示在主線程,否則是子線程
    NSLog(@"%@", [NSThread currentThread]);
    
    //阻塞休眠
    [NSThread sleepForTimeInterval:2];//休眠多久
    [NSThread sleepUntilDate:[NSDate date]];//休眠到指定時間
    
    //其他
    [NSThread exit];//退出線程
    [NSThread isMainThread];//判斷當前線程是否為主線程
    [NSThread isMultiThreaded];//判斷當前線程是否是多線程
    NSThread *mainThread = [NSThread mainThread];//主線程的對象
    NSLog(@"%@", mainThread);

GCD

dispatch_after

- (void)cjl_testAfter{
    /*
     dispatch_after表示在某隊列中的block延遲執行
     應用場景:在主隊列上延遲執行一項任務,如viewDidload之后延遲1s,提示一個alertview(是延遲加入到隊列,而不是延遲執行)
     */
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"2s后輸出");
    });
   
}

dispatch_once

- (void)cjl_testOnce{
    /*
     dispatch_once保證在App運行期間,block中的代碼只執行一次
     應用場景:單例、method-Swizzling
     */
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        //創建單例、method swizzled或其他任務
        NSLog(@"創建單例");
    });
}

dispatch_apply

- (void)cjl_testApply{
    /*
     dispatch_apply將指定的Block追加到指定的隊列中重復執行,并等到全部的處理執行結束——相當于線程安全的for循環

     應用場景:用來拉取網絡數據后提前算出各個控件的大小,防止繪制時計算,提高表單滑動流暢性
     - 添加到串行隊列中——按序執行
     - 添加到主隊列中——死鎖
     - 添加到并發隊列中——亂序執行
     - 添加到全局隊列中——亂序執行
     */
    
    dispatch_queue_t queue = dispatch_queue_create("CJL", DISPATCH_QUEUE_SERIAL);
    NSLog(@"dispatch_apply前");
    /**
         param1:重復次數
         param2:追加的隊列
         param3:執行任務
         */
    dispatch_apply(10, queue, ^(size_t index) {
        NSLog(@"dispatch_apply 的線程 %zu - %@", index, [NSThread currentThread]);
    });
    NSLog(@"dispatch_apply后");
}

dispatch_group_t

有以下兩種使用方式

  • 【方式一】使用dispatch_group_async + dispatch_group_notify
- (void)cjl_testGroup1{
    /*
     dispatch_group_t:調度組將任務分組執行,能監聽任務組完成,并設置等待時間

     應用場景:多個接口請求之后刷新頁面
     */
    
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"請求一完成");
    });
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"請求二完成");
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"刷新頁面");
    });
}
  • 【方式二】使用dispatch_group_enter + dispatch_group_leave + dispatch_group_notify
- (void)cjl_testGroup2{
    /*
     dispatch_group_enter和dispatch_group_leave成對出現,使進出組的邏輯更加清晰
     */
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"請求一完成");
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"請求二完成");
        dispatch_group_leave(group);
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"刷新界面");
    });
}
  • 在方式二的基礎上增加超時dispatch_group_wait
- (void)cjl_testGroup3{
    /*
     long dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout)

     group:需要等待的調度組
     timeout:等待的超時時間(即等多久)
        - 設置為DISPATCH_TIME_NOW意味著不等待直接判定調度組是否執行完畢
        - 設置為DISPATCH_TIME_FOREVER則會阻塞當前調度組,直到調度組執行完畢


     返回值:為long類型
        - 返回值為0——在指定時間內調度組完成了任務
        - 返回值不為0——在指定時間內調度組沒有按時完成任務

     */
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"請求一完成");
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"請求二完成");
        dispatch_group_leave(group);
    });
    
//    long timeout = dispatch_group_wait(group, DISPATCH_TIME_NOW);
//    long timeout = dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    long timeout = dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 1 *NSEC_PER_SEC));
    NSLog(@"timeout = %ld", timeout);
    if (timeout == 0) {
        NSLog(@"按時完成任務");
    }else{
        NSLog(@"超時");
    }
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"刷新界面");
    });
}

dispatch_barrier_sync & dispatch_barrier_async

柵欄函數,主要有兩種使用場景:串行隊列、并發隊列

- (void)cjl_testBarrier{
    /*
     dispatch_barrier_sync & dispatch_barrier_async
     
     應用場景:同步鎖
     
     等柵欄前追加到隊列中的任務執行完畢后,再將柵欄后的任務追加到隊列中。
     簡而言之,就是先執行柵欄前任務,再執行柵欄任務,最后執行柵欄后任務
     
     - dispatch_barrier_async:前面的任務執行完畢才會來到這里
     - dispatch_barrier_sync:作用相同,但是這個會堵塞線程,影響后面的任務執行
    
     - dispatch_barrier_async可以控制隊列中任務的執行順序,
     - 而dispatch_barrier_sync不僅阻塞了隊列的執行,也阻塞了線程的執行(盡量少用)
     */
    
    [self cjl_testBarrier1];
    [self cjl_testBarrier2];
}
- (void)cjl_testBarrier1{
    //串行隊列使用柵欄函數
    
    dispatch_queue_t queue = dispatch_queue_create("CJL", DISPATCH_QUEUE_SERIAL);
    
    NSLog(@"開始 - %@", [NSThread currentThread]);
    dispatch_async(queue, ^{
        sleep(2);
        NSLog(@"延遲2s的任務1 - %@", [NSThread currentThread]);
    });
    NSLog(@"第一次結束 - %@", [NSThread currentThread]);
    
    //柵欄函數的作用是將隊列中的任務進行分組,所以我們只要關注任務1、任務2
    dispatch_barrier_async(queue, ^{
        NSLog(@"------------柵欄任務------------%@", [NSThread currentThread]);
    });
    NSLog(@"柵欄結束 - %@", [NSThread currentThread]);
    
    dispatch_async(queue, ^{
        sleep(2);
        NSLog(@"延遲2s的任務2 - %@", [NSThread currentThread]);
    });
    NSLog(@"第二次結束 - %@", [NSThread currentThread]);
}
- (void)cjl_testBarrier2{
    //并發隊列使用柵欄函數
    
    dispatch_queue_t queue = dispatch_queue_create("CJL", DISPATCH_QUEUE_CONCURRENT);
    
    NSLog(@"開始 - %@", [NSThread currentThread]);
    dispatch_async(queue, ^{
        sleep(2);
        NSLog(@"延遲2s的任務1 - %@", [NSThread currentThread]);
    });
    NSLog(@"第一次結束 - %@", [NSThread currentThread]);
    
    //由于并發隊列異步執行任務是亂序執行完畢的,所以使用柵欄函數可以很好的控制隊列內任務執行的順序
    dispatch_barrier_async(queue, ^{
        NSLog(@"------------柵欄任務------------%@", [NSThread currentThread]);
    });
    NSLog(@"柵欄結束 - %@", [NSThread currentThread]);
    
    dispatch_async(queue, ^{
        sleep(2);
        NSLog(@"延遲2s的任務2 - %@", [NSThread currentThread]);
    });
    NSLog(@"第二次結束 - %@", [NSThread currentThread]);
}

dispatch_semaphore_t

信號量主要用作同步鎖,用于控制GCD最大并發數

- (void)cjl_testSemaphore{
    /*
     應用場景:同步當鎖, 控制GCD最大并發數

     - dispatch_semaphore_create():創建信號量
     - dispatch_semaphore_wait():等待信號量,信號量減1。當信號量< 0時會阻塞當前線程,根據傳入的等待時間決定接下來的操作——如果永久等待將等到信號(signal)才執行下去
     - dispatch_semaphore_signal():釋放信號量,信號量加1。當信號量>= 0 會執行wait之后的代碼

     */
    dispatch_queue_t queue = dispatch_queue_create("CJL", DISPATCH_QUEUE_CONCURRENT);
    
    for (int i = 0; i < 10; i++) {
        dispatch_async(queue, ^{
            NSLog(@"當前 - %d, 線程 - %@", i, [NSThread currentThread]);
        });
    }
    
    //利用信號量來改寫
    dispatch_semaphore_t sem = dispatch_semaphore_create(0);
    
    for (int i = 0; i < 10; i++) {
        dispatch_async(queue, ^{
            NSLog(@"當前 - %d, 線程 - %@", i, [NSThread currentThread]);
            
            dispatch_semaphore_signal(sem);
        });
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
    }
}

dispatch_source_t

dispatch_source_t主要用于計時操作,其原因是因為它創建的timer不依賴于RunLoop,且計時精準度比NSTimer

- (void)cjl_testSource{
    /*
     dispatch_source
     
     應用場景:GCDTimer
     在iOS開發中一般使用NSTimer來處理定時邏輯,但NSTimer是依賴Runloop的,而Runloop可以運行在不同的模式下。如果NSTimer添加在一種模式下,當Runloop運行在其他模式下的時候,定時器就掛機了;又如果Runloop在阻塞狀態,NSTimer觸發時間就會推遲到下一個Runloop周期。因此NSTimer在計時上會有誤差,并不是特別精確,而GCD定時器不依賴Runloop,計時精度要高很多
     
     dispatch_source是一種基本的數據類型,可以用來監聽一些底層的系統事件
        - Timer Dispatch Source:定時器事件源,用來生成周期性的通知或回調
        - Signal Dispatch Source:監聽信號事件源,當有UNIX信號發生時會通知
        - Descriptor Dispatch Source:監聽文件或socket事件源,當文件或socket數據發生變化時會通知
        - Process Dispatch Source:監聽進程事件源,與進程相關的事件通知
        - Mach port Dispatch Source:監聽Mach端口事件源
        - Custom Dispatch Source:監聽自定義事件源

     主要使用的API:
        - dispatch_source_create: 創建事件源
        - dispatch_source_set_event_handler: 設置數據源回調
        - dispatch_source_merge_data: 設置事件源數據
        - dispatch_source_get_data: 獲取事件源數據
        - dispatch_resume: 繼續
        - dispatch_suspend: 掛起
        - dispatch_cancle: 取消
     */
    
    //1.創建隊列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    //2.創建timer
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    //3.設置timer首次執行時間,間隔,精確度
    dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 2.0*NSEC_PER_SEC, 0.1*NSEC_PER_SEC);
    //4.設置timer事件回調
    dispatch_source_set_event_handler(timer, ^{
        NSLog(@"GCDTimer");
    });
    //5.默認是掛起狀態,需要手動激活
    dispatch_resume(timer);
    
}

NSOperation

NSOperation是基于GCD之上的更高一層封裝,NSOperation需要配合NSOperationQueue來實現多線程

NSOperatino實現多線程的步驟如下:

  • 1、創建任務:先將需要執行的操作封裝到NSOperation對象中。

  • 2、創建隊列:創建NSOperationQueue。

  • 3、將任務加入到隊列中:將NSOperation對象添加到NSOperationQueue中。

//基本使用
- (void)cjl_testBaseNSOperation{
    //處理事務
    NSInvocationOperation *op =  [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(handleInvocation::) object:@"CJL"];
    //創建隊列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    //操作加入隊列
    [queue addOperation:op];
    
}
- (void)handleInvocation:(id)operation{
    NSLog(@"%@ - %@", operation, [NSThread currentThread]);
}

需要注意的是,NSOperation是個抽象類,實際運用時中需要使用它的子類,有三種方式:

  • 1、使用子類NSInvocationOperation

//直接處理事務,不添加隱性隊列
- (void)cjl_createNSOperation{
    //創建NSInvocationOperation對象并關聯方法,之后start。
    NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(doSomething:) object:@"CJL"];
    
    [invocationOperation start];
}
  • 2、使用子類NSBlockOperation
- (void)cjl_testNSBlockOperationExecution{
    //通過addExecutionBlock這個方法可以讓NSBlockOperation實現多線程。
    //NSBlockOperation創建時block中的任務是在主線程執行,而運用addExecutionBlock加入的任務是在子線程執行的。
    NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"main task = >currentThread: %@", [NSThread currentThread]);
    }];
    
    [blockOperation addExecutionBlock:^{
            NSLog(@"task1 = >currentThread: %@", [NSThread currentThread]);
    }];
    
    [blockOperation addExecutionBlock:^{
            NSLog(@"task2 = >currentThread: %@", [NSThread currentThread]);
    }];
    
    [blockOperation addExecutionBlock:^{
            NSLog(@"task3 = >currentThread: %@", [NSThread currentThread]);
    }];
    
    [blockOperation start];
}
  • 3、定義繼承自NSOperation的子類,通過實現內部相應的方法來封裝任務。
//*********自定義繼承自NSOperation的子類*********
@interface CJLOperation : NSOperation
@end

@implementation CJLOperation
- (void)main{
    for (int i = 0; i < 3; i++) {
        NSLog(@"NSOperation的子類:%@",[NSThread currentThread]);
    }
}
@end

//*********使用*********
- (void)cjl_testCJLOperation{
    //運用繼承自NSOperation的子類 首先我們定義一個繼承自NSOperation的類,然后重寫它的main方法。
    CJLOperation *operation = [[CJLOperation alloc] init];
    [operation start];
}

NSOperationQueue

NSOperationQueue添加事務

NSOperationQueue有兩種隊列:主隊列其他隊列。其他隊列包含了 串行和并發。

  • 主隊列:主隊列上的任務是在主線程執行的。

  • 其他隊列(非主隊列):加入到'非隊列'中的任務默認就是并發,開啟多線程。

- (void)cjl_testNSOperationQueue{
    /*
     NSInvocationOperation和NSBlockOperation兩者的區別在于:
     - 前者類似target形式
     - 后者類似block形式——函數式編程,業務邏輯代碼可讀性更高
     
     NSOperationQueue是異步執行的,所以任務一、任務二的完成順序不確定
     */
    // 初始化添加事務
    NSBlockOperation *bo = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任務1————%@",[NSThread currentThread]);
    }];
    // 添加事務
    [bo addExecutionBlock:^{
        NSLog(@"任務2————%@",[NSThread currentThread]);
    }];
    // 回調監聽
    bo.completionBlock = ^{
        NSLog(@"完成了!!!");
    };
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperation:bo];
    NSLog(@"事務添加進了NSOperationQueue");
}

設置執行順序

//執行順序
- (void)cjl_testQueueSequence{
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        for (int i = 0; i < 5; i++) {
            [queue addOperationWithBlock:^{
                NSLog(@"%@---%d", [NSThread currentThread], i);
            }];
        }
}

設置優先級

- (void)cjl_testOperationQuality{
    /*
     NSOperation設置優先級只會讓CPU有更高的幾率調用,不是說設置高就一定全部先完成
     - 不使用sleep——高優先級的任務一先于低優先級的任務二
     - 使用sleep進行延時——高優先級的任務一慢于低優先級的任務二
     */
    NSBlockOperation *bo1 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 5; i++) {
            //sleep(1);
            NSLog(@"第一個操作 %d --- %@", i, [NSThread currentThread]);
        }
    }];
    // 設置最高優先級
    bo1.qualityOfService = NSQualityOfServiceUserInteractive;
    
    NSBlockOperation *bo2 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"第二個操作 %d --- %@", i, [NSThread currentThread]);
        }
    }];
    // 設置最低優先級
    bo2.qualityOfService = NSQualityOfServiceBackground;
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperation:bo1];
    [queue addOperation:bo2];

}

設置并發數

//設置并發數
- (void)cjl_testOperationMaxCount{
    /*
     在GCD中只能使用信號量來設置并發數
     而NSOperation輕易就能設置并發數
     通過設置maxConcurrentOperationCount來控制單次出隊列去執行的任務數
     */
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    queue.name = @"Felix";
    queue.maxConcurrentOperationCount = 2;
    
    for (int i = 0; i < 5; i++) {
        [queue addOperationWithBlock:^{ // 一個任務
            [NSThread sleepForTimeInterval:2];
            NSLog(@"%d-%@",i,[NSThread currentThread]);
        }];
    }
}

添加依賴

//添加依賴
- (void)cjl_testOperationDependency{
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    NSBlockOperation *bo1 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"請求token");
    }];
    
    NSBlockOperation *bo2 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"拿著token,請求數據1");
    }];
    
    NSBlockOperation *bo3 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"拿著數據1,請求數據2");
    }];
    
    [bo2 addDependency:bo1];
    [bo3 addDependency:bo2];
    
    [queue addOperations:@[bo1,bo2,bo3] waitUntilFinished:YES];
    
    NSLog(@"執行完了?我要干其他事");
}

線程間通訊

//線程間通訊
- (void)cjl_testOperationNoti{
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    queue.name = @"Felix";
    [queue addOperationWithBlock:^{
        NSLog(@"請求網絡%@--%@", [NSOperationQueue currentQueue], [NSThread currentThread]);
        
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            NSLog(@"刷新UI%@--%@", [NSOperationQueue currentQueue], [NSThread currentThread]);
        }];
    }];

}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容