文章分享至個人我的技術博客: https://cainluo.github.io/15062229631553.htmlhtml
上一篇, 咱們簡單的講了一些使用GCD
的小技巧, 若是沒有看的朋友, 能夠去玩轉iOS開發:實戰開發中的GCD Tips小技巧 (一)看.git
此次, 咱們繼續講解小技巧.github
轉載聲明:如須要轉載該文章, 請聯繫做者, 而且註明出處, 以及不能擅自修改本文.vim
一般咱們使用隊列組執行任務的時候是醬紫的:bash
- (void)queueGroup {
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
NSLog(@"執行任務, 當前線程爲:%@", [NSThread currentThread]);
});
}
複製代碼
打印的結果:微信
2017-09-24 11:34:44.766052+0800 GCD-Tips[59653:3481972] 開始執行
2017-09-24 11:34:44.766606+0800 GCD-Tips[59653:3482075] 執行任務, 當前線程爲:<NSThread: 0x604000464980>{number = 3, name = (null)}
複製代碼
但有時候, 咱們會遇到一種狀況, 就是沒有辦法直接使用隊列組變量, 這個時候, 還有另一種方式, 就是dispatch_group_enter
和dispatch_group_leave
, 注意, 這兩個方法是同時出現的:網絡
- (void)gourpEnterAndLeave {
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
[self urlRequestSuccess:^{
NSLog(@"網絡請求成功");
dispatch_group_leave(group);
} failure:^{
NSLog(@"網絡請求失敗");
dispatch_group_leave(group);
}];
}
- (void)urlRequestSuccess:(void(^)())success
failure:(void(^)())failure {
success();
// failure();
}
複製代碼
打印的結果:併發
2017-09-24 11:46:16.054410+0800 GCD-Tips[60002:3501228] 開始執行
2017-09-24 11:46:16.054721+0800 GCD-Tips[60002:3501228] 網絡請求成功
複製代碼
這樣子, 咱們就能夠把這個網絡請求給打包起來, 但這裏要注意一下, 不能同時調用兩個dispatch_group_leave
, 否則就會掛了.app
若是咱們要添加結束任務的話, 能夠有兩種方式:async
dispatch_group_notify
來通知, 任務已經結束.dispatch_group_notify
相似, 只不過是在能夠添加延遲結束的時間, 但這裏須要注意一點, dispatch_group_wait
會阻塞當前線程, 因此不要在主線程
中調用, 否則會阻塞主線程.咱們都知道dispatch_barrier_(a)sync
實際上是一個柵欄方法, 它的做用就是在向某個隊列插入一個block
, 等到該block
執行完以後, 纔會繼續執行其餘隊列, 有點老大的味道.
- (void)queueBarrier {
dispatch_queue_t queue = dispatch_queue_create("queueBarrier", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"執行一, 當前線程:%@", [NSThread currentThread]);
});
dispatch_barrier_async(queue, ^{
NSLog(@"大佬來了, 當前線程:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"執行二, 當前線程:%@", [NSThread currentThread]);
});
}
複製代碼
打印的結果:
2017-09-24 12:44:36.151126+0800 GCD-Tips[61121:3585236] 開始執行
2017-09-24 12:44:36.151579+0800 GCD-Tips[61121:3585334] 執行一, 當前線程:<NSThread: 0x600000462e40>{number = 3, name = (null)}
2017-09-24 12:44:36.152335+0800 GCD-Tips[61121:3585334] 大佬來了, 當前線程:<NSThread: 0x600000462e40>{number = 3, name = (null)}
2017-09-24 12:44:36.154241+0800 GCD-Tips[61121:3585334] 執行二, 當前線程:<NSThread: 0x600000462e40>{number = 3, name = (null)}
複製代碼
PS: dispatch_barrier_(a)sync
只在本身建立的併發隊列的纔會有效, 若是是在全局併發隊列, 串行隊列, dispatch_(a)sync
效果是同樣的, 這樣子的話, 就會容易形成線程死鎖, 因此這裏要注意.
這裏要補充兩個東西, dispatch_set_context
和dispatch_set_finalizer_f
.
這裏要注意一點dispatch_set_context
接受的context
參數是爲C
語言參數, 因此這裏寫的時候, 要注意一下:
typedef struct _Info {
int age;
} Info;
void cleanStaff(void *context) {
NSLog(@"In clean, context age: %d", ((Info *)context)->age);
//釋放,若是是new出來的對象,就要用delete
free(context);
}
- (void)setContext {
dispatch_queue_t queue = dispatch_queue_create("contextQueue", DISPATCH_QUEUE_SERIAL);
// 初始化Data對象, 而且設置初始化值
Info *myData = malloc(sizeof(Info));
myData->age = 100;
// 綁定Context
dispatch_set_context(queue, myData);
// 設置finalizer函數,用於在隊列執行完成後釋放對應context內存
dispatch_set_finalizer_f(queue, cleanStaff);
dispatch_async(queue, ^{
//獲取隊列的context數據
Info *data = dispatch_get_context(queue);
//打印
NSLog(@"1: context age: %d", data->age);
//修改context保存的數據
data->age = 20;
});
}
複製代碼
打印一下結果:
2017-09-24 14:24:10.394129+0800 GCD-Tips[61881:3652088] 開始執行
2017-09-24 14:24:10.394547+0800 GCD-Tips[61881:3652274] 1: context age: 100
2017-09-24 14:24:10.394738+0800 GCD-Tips[61881:3652274] In clean, context age: 20
複製代碼
PS: 咱們設置了dispatch_set_context
記得必定要釋放掉, 否則就會形成內存泄漏.
除了這個以外, 咱們還能夠對Core Foundation
進行操做, 那麼該怎麼作呢?
這裏咱們要建立一個Model
類, 繼承與NSObject
:
@interface GCDModel : NSObject
@property (nonatomic, assign) NSInteger age;
@end
@implementation GCDModel
- (void)dealloc {
NSLog(@"%@ 釋放了", NSStringFromClass([self class]));
}
@end
複製代碼
在這個類裏面, 咱們就簡單操做, 只有一個屬性和一個dealloc
方法, 具體操做:
void cleanObjectStaff(void *context) {
GCDModel *model = (__bridge GCDModel *)context;
NSLog(@"In clean, context age: %ld", model.age);
// 釋放內存
CFRelease(context);
}
- (void)objectAndContext {
dispatch_queue_t queue = dispatch_queue_create("objectQueue", DISPATCH_QUEUE_SERIAL);
// 初始化Data對象, 而且設置初始化值
GCDModel *model = [[GCDModel alloc] init];
model.age = 20;
// 綁定Context, 這裏使用__bridge關鍵
dispatch_set_context(queue, (__bridge_retained void *)(model));
// 設置finalizer函數,用於在隊列執行完成後釋放對應context內存
dispatch_set_finalizer_f(queue, cleanObjectStaff);
dispatch_async(queue, ^{
//獲取隊列的context數據
GCDModel *model = (__bridge GCDModel *)(dispatch_get_context(queue));
//打印
NSLog(@"1: context age: %ld", model.age);
//修改context保存的數據
model.age = 120;
});
}
複製代碼
打印一下結果:
2017-09-24 14:40:34.024509+0800 GCD-Tips[62448:3676807] 開始執行
2017-09-24 14:40:34.024915+0800 GCD-Tips[62448:3676887] 1: context age: 20
2017-09-24 14:40:34.025236+0800 GCD-Tips[62448:3676887] In clean, context age: 120
2017-09-24 14:40:34.025706+0800 GCD-Tips[62448:3676887] GCDModel 釋放了
複製代碼
這裏咱們要解釋一下__bridge
關鍵字:
(CFBridgingRetain)
, 而且把內存管理權限從ARC
裏拿到本身手裏, 最後釋放時要用CFRelease
來釋放對象.Core Foundation
轉換成Objective-C
對象(CFBridgingRelease)
, 而且將內存管理的權限交給ARC
.看到這裏應該會有人問, 爲何要把內存管理拿到本身的手裏, 而不是交給ARC
?
其實道理很簡單, 若是是ARC
管理的話, 一旦它檢測到做用於完了以後, 你的對象就會釋放了.
那麼你就沒法將這個Context
添加到隊列當中, 一旦添加就會給你報一個野指針
錯誤, 因此咱們爲了確保不會被ARC
給釋放掉, 咱們就須要本身去操做了.
而上面那段代碼的解釋也很簡單:
dispatch_set_context
時候用__bridge_retained
進行轉換, 將Context
的內存管理權限拿到咱們本身手上進行管理.dispatch_get_context
來獲取context
的時候用關鍵字__bridge
進行轉換, 這樣子能夠維持context
的內存管理權不變, 防止出了做用域Context
就會被釋放掉.CFRelease
來釋放掉context
, 這樣子就能夠保證內存獲得釋放, 不會形成內存泄漏的問題.好了, 額外補充的GCD
小技巧到這裏就差很少了, 若是之後還有更多的小技巧也會繼續更新, 歡迎各位小夥伴們和我分享其餘的使用技巧~~
這裏推薦幾篇文章:
Grand Central Dispatch (GCD) Reference Concurrency Programming Guide Toll-Free Bridged Types
項目地址: https://github.com/CainRun/iOS-Project-Example/tree/master/GCD-Tips/GCD-Tips-Two