Grand Central Dispatch回顧

原創(chuàng)文章轉(zhuǎn)載請注明出處柱徙,謝謝


重溫了一遍關(guān)于GCD方面的一些知識,于是重新整理了一下奇昙。

關(guān)于Dispatch Queue

Dispatch Queue可以分為兩種隊列护侮,一種是等待現(xiàn)在執(zhí)行中處理的Serial Dispatch Queue,即串行執(zhí)行储耐;另一種是不等待現(xiàn)在執(zhí)行中處理的Concurrent Dispatch Queue圣蝎,即并行執(zhí)行酸役;

Concurrent Dispatch Queue并行執(zhí)行的處理數(shù)是由CPU核數(shù)重贺,CPU負(fù)荷以及Dispatch Queue中的處理所決定几晤。

關(guān)于dispatch-queue-create

dispatch-queue-create用于創(chuàng)建Dispatch Queue,可以創(chuàng)建Serial Dispatch Queue和Concurrent Dispatch Queue兩種禽炬。

在《Objective-C高級編程iOS與OSX多線程和內(nèi)存管理》一書中說到dispatch-queue-create不受ARC控制,需要我們自己手動disaptch-retain和dispatch-release勤家,這個其實是不對的腹尖。在官方的文檔中已經(jīng)說明在OSX10.8和iOS10.6以后,ARC已經(jīng)支持自動管理Dispatch Queue的創(chuàng)建了伐脖,不要我們手動release和retain了热幔;但是如果你需要在開啟ARC的情況下同時手動retian/release,那么就需要在compiler flags設(shè)置-DOS-OBJECT-USE-OBJC = 0讼庇。

關(guān)于Main Disaptch Queue和Global Disaptch Queue

Main Disaptch Queue和Global Disaptch Queue是兩個系統(tǒng)的標(biāo)準(zhǔn)Dispatch Queue绎巨。

Main Disaptch Queue是在主線程中執(zhí)行的Dispatch Queue,因為主線程只有一個蠕啄,所以Main Dispatch Queue就是Serial Dispatch Queue场勤。

Global Disaptch Queue是所有線程都可以使用的Concurrent Dispatch Queue,Global Disaptch Queue有四個執(zhí)行優(yōu)先級:

  • High Priority(最高優(yōu)先級)
  • Default Priority(默認(rèn)優(yōu)先級)
  • Low Priority(低優(yōu)先級)
  • Background Priority(后臺優(yōu)先級)

但是通過XNU內(nèi)核用于Global Disaptch Queue的線程并不能保持實時性歼跟,因此執(zhí)行優(yōu)先級只是大致的判斷和媳。Global Disaptch Queue的默認(rèn)執(zhí)行優(yōu)先級是Default Priority。

/*
 * Main Dispatch Queue
 */
 dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();
 
 /*
  * Global Dispatch Queue(High Priority)
  */
 dispatch_queue_t globalDispatchQueueHigh = 
    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
 
 /*
  * Global Dispatch Queue(Default Priority)
  */
 dispatch_queue_t globalDispatchQueueDefault = 
    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 
  /*
  * Global Dispatch Queue(Low Priority)
  */
 dispatch_queue_t globalDispatchQueueLow = 
    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
 
  /*
  * Global Dispatch Queue(Background Priority)
  */
 dispatch_queue_t globalDispatchQueueBackground = 
    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
 

關(guān)于dispatch-set-target-queue

dispatch-set-target-queue一共有兩個作用哈街。

作用一:修改Dispatch Queue的執(zhí)行優(yōu)先級留瞳;通過dispatch-queue-create函數(shù)生成的Dispatch Queue默認(rèn)優(yōu)先級都是Default Priority。

/*
 *修改serialQueue優(yōu)先級至Background Priority
 */
dispatch_queue_t serialQueue = dispatch_queue_create("com.example.gcd.serialQueue", NULL);  
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);  
dispatch_set_target_queue(serialQueue, globalQueue); 

注意不要修改Main Disaptch Queue和Global Disaptch Queue的優(yōu)先級骚秦,因為這種情況是不可預(yù)知的她倘。

作用二:修改用戶隊列的目標(biāo)隊列璧微,使多個Serial Dispatch Queue在目標(biāo)Queue只能同時執(zhí)行一個處理,防止并行執(zhí)行硬梁。

dispatch_queue_t serialQueue = dispatch_queue_create("com.example.gcd.serialQueue", DISPATCH_QUEUE_SERIAL);  
      
dispatch_queue_t queue1 = dispatch_queue_create("com.example.gcd.queue1", DISPATCH_QUEUE_SERIAL); 
dispatch_queue_t queue2 = dispatch_queue_create("com.example.gcd.queue2", DISPATCH_QUEUE_SERIAL);  
dispatch_queue_t queue3 = dispatch_queue_create("com.example.gcd.queue3", DISPATCH_QUEUE_SERIAL);  
      
dispatch_set_target_queue(queue1, serialQueue);  
dispatch_set_target_queue(queue2, serialQueue);  
dispatch_set_target_queue(queue3, serialQueue);  
      
      
dispatch_async(queue1, ^{  
    NSLog(@"queue1-start");
    sleep(1.0f);
    NSLog(@"queue1-end");
});  
  
dispatch_async(queue2, ^{  
    NSLog(@"queue2-start");
    sleep(1.0f);
    NSLog(@"queue2-end");
});  
dispatch_async(queue3, ^{  
    NSLog(@"queue3-start");
    sleep(1.0f);
    NSLog(@"queue3-end");
});  

//out put  多個Serial Queue并發(fā)執(zhí)行前硫,每次只能執(zhí)行一個serial Queue的內(nèi)容
queue1-start
queue1-end
queue2-start
queue2-end
queue3-start
queue3-end

關(guān)于dispatch-after和dispatch-once

dispatch-after函數(shù)并不是在指定時間后執(zhí)行處理,而是在指定時間追加處理時間到Dispatch queue后再進(jìn)行執(zhí)行靶溜。

關(guān)于dispatch-time-t的類型可以由dispatch-time和dispatch-walltime兩個函數(shù)來生成开瞭。

dispatch-time函數(shù)能夠獲取從第一個參數(shù)dispatch-time-t類型值中指定時間開始,到第二個參數(shù)指定的毫微秒單位時間后的時間罩息。此時間是指相對時間嗤详。

// 延時一秒以后
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC);

dispatch-walltime函數(shù)能夠獲取從第一個參數(shù)struct timespec結(jié)構(gòu)體時間開始,此時間是指絕對時間瓷炮。

struct timespec類型的時間可以通過NSDate類對象轉(zhuǎn)換而成葱色。

NSDate *date = [NSDate date];
NSTimeInterval interval;
double second, subsecond;
struct timespec time;
    
interval = [date timeIntervalSince1970];
subsecond = modf(interval, &second);
time.tv_sec = second;
time.tv_nsec = subsecond * NSEC_PER_SEC;
dispatch_time_t milestone milestone = dispatch_walltime(&time, 0);

dispatch-once函數(shù)的目的是保證在應(yīng)用程序中執(zhí)行中只執(zhí)行指定處理,經(jīng)常出現(xiàn)在單例的初始化里面娘香,通過disptach-once函數(shù)苍狰,即使在多線程環(huán)境下執(zhí)行也是安全的。

static dispatch_once_t once;

dispatch_once(&once, ^{
    // init class
});

關(guān)于Dispatch Group的作用

Dispatch Group的作用是當(dāng)隊列中的所有任務(wù)都執(zhí)行完畢后在去做一些操作烘绽,主要針對Concurrent Dispatch Queue中多個處理結(jié)束后追加的操作淋昭。

Dispatch Group分為兩種方式可以實現(xiàn)上面需求。

第一種是使用dispatch-group-async函數(shù)安接,將隊列與任務(wù)組進(jìn)行關(guān)聯(lián)并自動執(zhí)行隊列中的任務(wù)翔忽。

    dispatch_queue_t queue =
    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"blk1");
    });
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"blk2");
    });
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"blk3");
    });
    
    dispatch_group_notify(group, queue, ^{
        NSLog(@"done");
    });

    // output
    blk3
    blk2
    blk1
    done

dispatch-group-async函數(shù)會將隊列與相應(yīng)的任務(wù)組進(jìn)行關(guān)聯(lián)同時自動執(zhí)行,當(dāng)與任務(wù)組關(guān)聯(lián)的隊列中的任務(wù)都執(zhí)行完畢后盏檐,會通過dispatch-group-notify函數(shù)發(fā)出通知告訴用戶任務(wù)組中的所有任務(wù)都執(zhí)行完畢了歇式,有點類似于dispatch-barrier-async,另外dispatch-group-notify方式并不會阻塞線程胡野。

但是如果我們使用dispatch-group-wait函數(shù)材失,那么就會阻塞當(dāng)前線程,等待全部處理執(zhí)行結(jié)束硫豆。

    dispatch_queue_t queue =
    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"blk1");
    });
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"blk2");
    });
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"blk3");
    });
    
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    NSLog(@"done");
    
    //output
    blk2
    blk3
    blk1
    done

dispatch-group-wait函數(shù)的返回值不為0就意味著雖然經(jīng)過了指定的時間龙巨,但是屬于Dispatch Group的某一個處理還在執(zhí)行中,如果返回值為0為全部執(zhí)行結(jié)束熊响,當(dāng)?shù)却龝r間為DISPATCH-TIME-FOREVER恭应,由dispatch-group-wait函數(shù)返回時,由于屬于Dispatch Group的處理必定全部執(zhí)行結(jié)束耘眨,因此返回值一直為0昼榛。當(dāng)指定為DISPATCH-TIME-NOW則不用任何等待即可判定屬于Dispatch Group的處理是否執(zhí)行結(jié)束。

第二種是使用手動的將隊列與組進(jìn)行關(guān)聯(lián)然后使用異步將隊列進(jìn)行執(zhí)行,也就是dispatch-group-enter與dispatch-group-leave方法的使用胆屿。dispatch-group-enter函數(shù)進(jìn)入到任務(wù)組中奥喻,然后異步執(zhí)行隊列中的任務(wù),最后使用dispatch-group-leave函數(shù)離開任務(wù)組即可非迹。

    dispatch_queue_t queue =
    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"blk1");
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"blk2");
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"blk3");
        dispatch_group_leave(group);
    });
    
    dispatch_group_notify(group, queue, ^{
        NSLog(@"done");
    });
    
    // output
    blk2
     blk3
     blk1
     done

dispatch-group-enter和dispatch-group-leave必須同時匹配出現(xiàn)才可以环鲤,不然就會出現(xiàn)無法預(yù)知的情況。

關(guān)于dispatch-barrier-async

dispatch-barrier-async函數(shù)的目的基本就是為了讀寫鎖的問題憎兽。

對于Concurrent Dispatch Queue可能會所產(chǎn)生的數(shù)據(jù)庫同時讀寫的問題冷离,使用dispatch-barrier-async就可以很好的避免這個問題。

    dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.queue", DISPATCH_QUEUE_CONCURRENT);
    __block NSInteger index = 0;
    dispatch_async(queue, ^{NSLog(@"blk0_for_reading index %ld", index);});
    dispatch_async(queue, ^{NSLog(@"blk1_for_reading index %ld", index);});
    dispatch_async(queue, ^{NSLog(@"blk2_for_reading index %ld", index);});
    dispatch_async(queue, ^{NSLog(@"blk3_for_reading index %ld", index);});
    dispatch_async(queue, ^{NSLog(@"blk4_for_reading index %ld", index);});
    dispatch_async(queue, ^{NSLog(@"blk5_for_reading index %ld", index);});
    
    dispatch_barrier_sync(queue, ^{
        NSLog(@"----------------------------------");
        index++;
    });
    
    dispatch_async(queue, ^{NSLog(@"blk6_for_reading index %ld", index);});
    dispatch_async(queue, ^{NSLog(@"blk7_for_reading index %ld", index);});
    dispatch_async(queue, ^{NSLog(@"blk8_for_reading index %ld", index);});
    dispatch_async(queue, ^{NSLog(@"blk9_for_reading index %ld", index);});
    dispatch_async(queue, ^{NSLog(@"blk10_for_reading index %ld", index);});
    dispatch_async(queue, ^{NSLog(@"blk11_for_reading index %ld", index);});
    
//output
blk0_for_reading index 0
blk1_for_reading index 0
blk2_for_reading index 0
blk3_for_reading index 0
blk5_for_reading index 0
blk4_for_reading index 0
----------------------------------
blk6_for_reading index 1
blk7_for_reading index 1
blk8_for_reading index 1
blk9_for_reading index 1
blk10_for_reading index 1
blk11_for_reading index 1

dispatch-barrier-sync 函數(shù)會等待追加到Concurrent Dispatch Queue上的并行執(zhí)行的處理全部結(jié)束以后纯命,再將指定的處理追加到該Concurrent Dispatch Queue中西剥。然后在由Concurrent Dispatch Queue函數(shù)追加的處理執(zhí)行完畢后。Concurrent Dispatch Queue才恢復(fù)為一般的動作亿汞,追加到該Concurrent Dispatch Queue的處理又開始并行執(zhí)行瞭空。

關(guān)于dispatch-suspend(掛起)/dispatch-resume(恢復(fù))

dispatch-suspend/dispatch-resume一般用于當(dāng)追加大量處理到Dispatch Queue時,在追加處理的過程中有時希望不執(zhí)行已追加的處理疗我。

    dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.queue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_suspend(queue);
    dispatch_async(queue, ^{
        NSLog(@"blk0");
    });
    
    dispatch_async(queue, ^{
        NSLog(@"blk1");
    });
    sleep(2);
    dispatch_resume(queue);
    // 兩秒后恢復(fù)queue咆畏,然后才會執(zhí)行queue里面的操作

關(guān)于dispatch-sync

dispatch-sync相對于dispatch-async的區(qū)別就在于它是就是同步的線程操作,只有指定的block完成以后dispatch-sync才會返回吴裤。

但是dispatch-sync會帶來一些死鎖的情況旧找。

將主線程的Main Disaptch Queue,在主線程中執(zhí)行dispatch_sync就會造成死鎖麦牺。

    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue, ^{
        NSLog(@"lock");
    });

因為主線程正在執(zhí)行上述代碼塊钦讳,所以此時的block無法執(zhí)行到Main Disaptch Queue,由于Main Disaptch Queue是Serial Dispatch Queue;但是由于block無法執(zhí)行枕面,所以dispatch-sync就會一直等待block的執(zhí)行,主線程此時死鎖缚去。

下面的例子也是同樣的道理潮秘。

    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_async(queue, ^{
        dispatch_sync(queue, ^{
            NSLog(@"lock");
        });
    });

所以由此可知,我把Main Disaptch Queue替換成任何Serial Dispatch Queue都會造成死鎖的問題易结。

    dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.queue", NULL);
    dispatch_async(queue, ^{
        dispatch_sync(queue, ^{
            NSLog(@"lock");
        });
    });

關(guān)于Dispatch Semaphore的問題

Dispatch Semaphore主要用于信號量的控制并發(fā)枕荞,當(dāng)處理一系列線程的時候,當(dāng)數(shù)量達(dá)到一定量時搞动,我們需要控制線程的并發(fā)量躏精。

在GCD中有三個函數(shù)是semaphore的操作,分別是:

  • dispatch-semaphore-create   創(chuàng)建一個初始指定數(shù)量的信號量
  • dispatch-semaphore-signal   發(fā)送一個信號鹦肿,使信號量+1矗烛,計數(shù)為0時等待,計數(shù)為1或大于1時箩溃,減去1而不等待瞭吃。
  • dispatch-semaphore-wait    等待信號碌嘀,使信號量-1
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(5);
    dispatch_queue_t global = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    for (NSInteger index = 0; index < 50; index++) {
        /**
         *等待處理,直到信號量>0歪架,信號量減1股冗,當(dāng)信號量為0時不需要在減1
         */
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        dispatch_async(global, ^{
            NSLog(@"thread %ld", index);
            /**
             *數(shù)據(jù)處理完畢,信號量加1
             */
            dispatch_semaphore_signal(semaphore);
        });
    }

總結(jié)

GCD算是OC中一項很基礎(chǔ)的知識了和蚪,靈活使用GCD會很大程度上提高我們的代碼質(zhì)量止状。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市攒霹,隨后出現(xiàn)的幾起案子怯疤,更是在濱河造成了極大的恐慌,老刑警劉巖剔蹋,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件旅薄,死亡現(xiàn)場離奇詭異,居然都是意外死亡泣崩,警方通過查閱死者的電腦和手機(jī)少梁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來矫付,“玉大人凯沪,你說我怎么就攤上這事÷蛴牛” “怎么了妨马?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長杀赢。 經(jīng)常有香客問我烘跺,道長,這世上最難降的妖魔是什么脂崔? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任滤淳,我火速辦了婚禮,結(jié)果婚禮上砌左,老公的妹妹穿的比我還像新娘脖咐。我一直安慰自己,他們只是感情好汇歹,可當(dāng)我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布屁擅。 她就那樣靜靜地躺著,像睡著了一般产弹。 火紅的嫁衣襯著肌膚如雪派歌。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天,我揣著相機(jī)與錄音硝皂,去河邊找鬼常挚。 笑死,一個胖子當(dāng)著我的面吹牛稽物,可吹牛的內(nèi)容都是我干的奄毡。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼贝或,長吁一口氣:“原來是場噩夢啊……” “哼吼过!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起咪奖,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤盗忱,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后羊赵,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體趟佃,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年昧捷,在試婚紗的時候發(fā)現(xiàn)自己被綠了闲昭。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡靡挥,死狀恐怖序矩,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情跋破,我是刑警寧澤簸淀,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站毒返,受9級特大地震影響租幕,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜拧簸,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一劲绪、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧狡恬,春花似錦、人聲如沸蝎宇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽姥芥。三九已至兔乞,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背庸追。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工霍骄, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人淡溯。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓读整,卻偏偏與公主長得像,于是被迫代替她去往敵國和親咱娶。 傳聞我的和親對象是個殘疾皇子米间,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 3.1 Grand Central Dispatch(GCD)概要 3.1.1 什么是CGD Grand Cent...
    SkyMing一C閱讀 1,612評論 0 22
  • 我們知道在iOS開發(fā)中,一共有四種多線程技術(shù):pthread膘侮,NSThread屈糊,GCD,NSOperation: ...
    請叫我周小帥閱讀 1,484評論 0 1
  • 背景 擔(dān)心了兩周的我終于輪到去醫(yī)院做胃鏡檢查了琼了!去的時候我都想好了最壞的可能(胃癌)逻锐,之前在網(wǎng)上查的癥狀都很相似。...
    Dely閱讀 9,226評論 21 42
  • 同步/異步 同步:多個任務(wù)情況下雕薪,一個任務(wù)A執(zhí)行結(jié)束昧诱,才可以執(zhí)行另一個任務(wù)B。只存在一個線程也就是主線程蹦哼。 異步:...
    XLsn0w閱讀 297評論 0 0
  • 一鳄哭、GCD的API 1. Dispatch queue 在執(zhí)行處理時存在兩種Dispatch queue: 等待現(xiàn)...
    doudo閱讀 493評論 0 0