文章分享至我的個人技術部落格: https://cainluo.github.io/15062229631553.html
上一篇, 我們簡單的講了一些使用GCD
的小技巧, 如果沒有看的朋友, 可以去玩轉iOS開發:實戰開發中的GCD Tips小技巧 (一)看.
這次, 我們繼續講解小技巧.
轉載宣告:如需要轉載該文章, 請聯絡作者, 並且註明出處, 以及不能擅自修改本文.
佇列組的靈活使用
通常我們使用佇列組執行任務的時候是醬紫的:
- (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
, 不然就會掛了.
如果我們要新增結束任務的話, 可以有兩種方式:
- dispatch_group_notify
- 當前的佇列組任務執行完畢之後, 就會呼叫
dispatch_group_notify
來通知, 任務已經結束.
- 當前的佇列組任務執行完畢之後, 就會呼叫
- dispatch_group_wait
- 和
dispatch_group_notify
類似, 只不過是在可以新增延遲結束的時間, 但這裡需要注意一點,dispatch_group_wait
會阻塞當前執行緒, 所以不要在主執行緒
中呼叫, 不然會阻塞主執行緒.
- 和
dispatch_barrier_(a)sync使用的注意
我們都知道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
和dispatch_set_finalizer_f
.
- 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
進行操作, 那麼該怎麼做呢?
NSObject與dispatch_set_context
這裡我們要建立一個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
關鍵字:
- __bridge: 只做型別轉換, 不做記憶體管理許可權修改.
- __bridge_retained: 記憶體轉換
(CFBridgingRetain)
, 並且把記憶體管理許可權從ARC
裡拿到自己手裡, 最後釋放時要用CFRelease
來釋放物件. - __bridge_transfer: 將
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