GCD

  • 什么是 GCD

首先 GCD (Grand Central Dispatch)是apple 開發的一個多核編程的解決方法.GCD 和 block 的配合使用,可以方便的進行多線程編程.

  • 應用舉例
    例如在 iPhone 上做一個下載網頁的功能,該功能非常簡單,就是 viewcontroller 上防止一個按鈕,點擊該按鈕時,顯示一個轉動的圓圈,表示正在進行下載,當下載完成后,將內容加載到界面上的一個文本控件中.
  • 不使用 GCD 前

功能雖簡單,但是我們必須將下載的過程放到后臺的線程中,否則的話會阻塞 UI線程
.所以如果不用 GCD, 我們需要些3個方法

  • someClick 方法是點擊按鈕后的代碼,可以看到我們用 NSInvocationOperation 建了一個后臺線程,并且放到 NSOperationQueue 中。后臺線程執行 download 方法
  • download 方法處理下載網頁的邏輯。下載完成后用 performSelectorOnMainThread 執行 download_completed 方法。
  • download_completed 進行 clear up 的工作,并把下載的內容顯示到文本控件中。
static NSOperationQueue * queue;
//點擊按鈕
- (IBAction)someClick:(id)sender { 
self.indicator.hidden = NO; 
[self.indicator startAnimating]; 
queue = [[NSOperationQueue alloc] init]; 
NSInvocationOperation * op = [[[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download) object:nil] autorelease]; 
[queue addOperation:op];
}
//下載任務
- (void)download { 
NSURL * url = [NSURL URLWithString:@"http://www.youdao.com"]; NSError * error; 
NSString * data = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&error];
 if (data != nil) { 
[self performSelectorOnMainThread:@selector(download_completed:) withObject:data waitUntilDone:NO]; 
} else { 
NSLog(@"error when download:%@", error); 
[queue release]; 
}
}
//顯示文本
- (void) download_completed:(NSString *) data { 
NSLog(@"call back"); 
[self.indicator stopAnimating]; 
self.indicator.hidden = YES; 
self.content.text = data; [queue release];
}
  • 使用 GCD

如果使用 GCD, 以上的三個方法就可以放到一起

//原始代碼片段一
self.indicator.hidden = NO;
[self.indicator startAnimating];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
 // 原始代碼片段二
NSURL * url = [NSURL URLWithString:@"http://www.youdao.com"];
NSErroe *error;
NSString *data = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&error];
if (data != nil) {
//原始代碼片段三
dispatch_async(dispatch_get_main_queue(), ^{ //回主線程
[self.indicator stopAnimating]; 
self.indicator.hidden = YES; 
self.content.text = data;
 });
} else{
NSLog(@"error when download:%@", error);
}
});

代碼塊變短,這是因為少了原來3個方法的定義,也少了相互之間需要傳遞的變量的封裝.
此外呢,代碼變清楚了.雖然是異步的代碼.但是他們被 GCD 合理的整合在一起.邏輯非常清晰.如果應用上 MVC 模式.我們也可以將 ViewController 層的回調函數用 GCD 的方式傳遞給 Model 層,這相比之前用@ selector 的方式,代碼的邏輯關系會更加清楚.

block 的定義

block 的定義有點像函數指針,差別是 block 用^替代了函數指針的*號.

//申明變量
- (void)(^loggerBlock)(void){
//定義

loggerBlock = ^{
NSLog("hello world");
};
//調用
loggerBlock();
}

但是在大多數時候,我們通常使用內聯的方式來定義 Block, 即將它的程序塊寫在調用的函數里面,例如這樣:

dispatch_async(dispatch_get_global_queue(0,0),^{
//something;
});

所以可以看出, block 有如下特點:
1.程序塊可以在代碼總以內聯的方式來定義
2.程序塊可以訪問在創建它的范圍內的可用的變量.

系統提供的 dispatch 的方法

在我們使用的時候,為了更加方便的使用 GCD, 蘋果提供了一些方法方便我們將 block 放在主線程或后臺線程執行,貨值延后執行.

//后臺執行
dispatch_async(dispatch_get_global_queue(0,0),^{
//something
});
//主線程執行
dispatch_async(dispatch_get_main_queue(),^{
//something;
});
//一次執行;
static dispatch_once_t onceToken;
dispatch_once(&onceToken,^{
//code to be executed once
});

//延遲2秒執行;
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW ,delayInSeconds *NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ 
// code to be executed on the main queue after delay
});

另外dispatch_queue_t也可以自己定義,如果要自定義 queue, 可以用dispatch_queue_create方法來進行定義.

dispatch_queue_t urls_queue = dispatch_queue_create("blog.devtang.com", NULL);
dispatch_async(urls_queue, ^{
//your code
});
dispatch_release(urls_queue);

另外, GCD 還有一些高級用法,例如讓后臺2個線程并行執行,然后等2個線程都結束后,在匯總執行結果.這個可以用dispatch_group\dispatch_group_async 和 dispatch_group_notify來實現.

dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(0,0), ^{ 
// 并行執行的線程一
});
dispatch_group_async(group, dispatch_get_global_queue(0,0), ^{ 
// 并行執行的線程二
});
dispatch_group_notify(group, dispatch_get_global_queue(0,0), ^{ 
// 匯總結果
});
  • 加入要修改 block 之外的變量
    一般在默認情況下,在程序塊訪問的外部變量是復制過去的,即寫操作不對原變量生效,但是你可以加上__block來使得其寫操作生效.
__block int a =  0;
void(^foo)(void) = ^{
a =1;
}
foo();
//在這里,a 的值被修改為1;

-此外使用 block 的另一個用處是可以讓程序在后臺較長就的運行.在以前,當 app被按 home 鍵推出后, app僅有最多5秒鐘的時候做一些保存或者清理資源的工作.但是應用可以調用 UIApplication 的beginBackgroundTaskWithExpirationHandler方法,讓APP 最多有10分鐘的時間在后臺長久運行,這個時間可以用來做清理本地緩存,發送統計數據等工作.

讓程序在后臺長久運行的實例代碼如下;

// AppDelegate.h 文件
@property (assign, nonatomic) UIBackgroundTaskIdentifier backgroundUpdateTask;
// AppDelegate.m 文件
- (void)applicationDidEnterBackground:(UIApplication *)application
{ 
[self beingBackgroundUpdateTask]; 
// 在這里加上你需要長久運行的代碼 [self endBackgroundUpdateTask];
}

- (void)beingBackgroundUpdateTask
{ 
self.backgroundUpdateTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ 
[self endBackgroundUpdateTask]; 
}];
}

- (void)endBackgroundUpdateTask{ 
[[UIApplication sharedApplication] endBackgroundTask: self.backgroundUpdateTask];
 self.backgroundUpdateTask = UIBackgroundTaskInvalid;
}

例如代碼;

#pragma mark-----|GCD的串行隊列,有序的執行一組任務.
- (void)serialQueue{
    //創建一個串行隊列,執行任務的時候,在一個子線程里面.
    /*第一個參數:當前隊列的標簽(也就是名字).第二個參數是隊列類型*/
    dispatch_queue_t serialQueue = dispatch_queue_create("串行", DISPATCH_QUEUE_SERIAL);
    //創建任務
     /*第一個參數:當前任務所在的隊列.第二個參數是任務的 block 回調,,邏輯代碼就在 block 中*/
    dispatch_async(serialQueue, ^{
        NSLog(@"當前的線程%@",[NSThread currentThread]);
        NSLog(@"小蝌蚪");
    });
    dispatch_async(serialQueue, ^{
        NSLog(@"找媽媽");
        NSLog(@"當前的線程%@",[NSThread currentThread]);
    });
    dispatch_async(serialQueue, ^{
        NSLog(@"十個月的變態發育");
        NSLog(@"當前的線程%@",[NSThread currentThread]);
    });
    dispatch_async(serialQueue, ^{
        NSLog(@"出生");
        NSLog(@"當前的線程%@",[NSThread currentThread]);
    });
    dispatch_async(serialQueue, ^{
        NSLog(@"掛了");
        NSLog(@"當前的線程%@",[NSThread currentThread]);
    });
}
#pragma mark---并行隊列
- (void)concurrentQueue{
    dispatch_queue_t concurrentQueue = dispatch_queue_create("并行", DISPATCH_QUEUE_CONCURRENT);
    /*通過回調函數的方式來實現任務*/
    dispatch_async_f(concurrentQueue, @"天堂",  function);
    
    dispatch_async(concurrentQueue, ^{
        NSLog(@"當前的線程%@",[NSThread currentThread]);
        NSLog(@"睡覺");
    });
    dispatch_async(concurrentQueue, ^{
        NSLog(@"當前的線程%@",[NSThread currentThread]);
        NSLog(@"抽煙");
    });
    dispatch_async(concurrentQueue, ^{
        NSLog(@"當前的線程%@",[NSThread currentThread]);
        NSLog(@"泰國");
    });
    
}
#pragma mark---回調函數
//聲明一個函數
void function(void *contenct){
    NSLog(@"%@",contenct);
    
}
#pragma mark-----/*在實際使用中,我們常用的 GCD 方式*/
-(void)usefulMethod{
    //系統提供的全局隊列
    //全局隊列優先級
    //預留參數:當前并沒有什么用.
    dispatch_queue_t oneQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(oneQueue, ^{
        //在此處執行耗時的操作
        //當操作完成之后,我們需要回到主線程刷新 UI.GCD回主線程方式
        dispatch_async(dispatch_get_main_queue(), ^{
            //已經回到主線程,在此處刷新 UI.
        });
    });
    //系統提供的
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        dispatch_async(dispatch_get_main_queue(), ^{
            
        });
    });
    
}
#pragma mark------GCD延時調用
- (void)afterDelay{
    //DISPATCH_TIME_NOW:從什么時候開始計時
    //第二個參數:從計時開始.多長時間之后執行.
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"那安安");
    });
    
    //聲明一個 oncetoken 對象
    //使用oncetoken
    //
    
//    static dispatch_once_t onceToken;
//    dispatch_once(&onceToken, ^{
//        <#code to be executed once#>
//    });
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容