多線(xiàn)程總結(jié)之GCD

多線(xiàn)程有幾個(gè)重要的概念,任務(wù)、隊(duì)列、線(xiàn)程。
任務(wù):是指執(zhí)行什么樣的操作,在GCD中就是block。
隊(duì)列:用來(lái)存放任務(wù),分為串行隊(duì)列和并行隊(duì)列。放在串行隊(duì)列中的任務(wù)要等到正在執(zhí)行的任務(wù)執(zhí)行完后才會(huì)被執(zhí)行;放在并行隊(duì)列中的任務(wù)不用等到正在執(zhí)行的任務(wù)執(zhí)行完就可以被執(zhí)行。
線(xiàn)程:執(zhí)行任務(wù)需要線(xiàn)程,線(xiàn)程從隊(duì)列中以先進(jìn)先出(FIFO)的方式取出任務(wù)執(zhí)行,線(xiàn)程一次只能執(zhí)行一個(gè)任務(wù)。
這三者的關(guān)系就是:線(xiàn)程從隊(duì)列中取任務(wù)執(zhí)行。

GCD中的隊(duì)列:
dispatch_main_queue 主隊(duì)列,程序啟動(dòng)時(shí)與主線(xiàn)程一起由系統(tǒng)自動(dòng)創(chuàng)建,UI操作都放在主隊(duì)列中
dispatch_get_global_queu 全局并發(fā)隊(duì)列,由系統(tǒng)定義,調(diào)用函數(shù)dispatch_get_global_queue(identifier: Int, flags: Uint)獲取。identifier以前叫做優(yōu)先級(jí),現(xiàn)在稱(chēng)為服務(wù)質(zhì)量,現(xiàn)在傳入優(yōu)先級(jí)的宏定義也可以,目測(cè)優(yōu)先級(jí)的界限并不明顯,所以平時(shí)直接傳入0,見(jiàn)下面對(duì)應(yīng)關(guān)系。flags是系統(tǒng)保留的一個(gè)參數(shù),傳入0即可。

DISPATCH_QUEUE_PRIORITY_HIGH        QOS_CLASS_USER_INITIATED       2
DISPATCH_QUEUE_PRIORITY_DEFAULT     QOS_CLASS_DEFAULT              0
DISPATCH_QUEUE_PRIORITY_LOW         QOS_CLASS_UTILITY             -2
DISPATCH_QUEUE_PRIORITY_BACKGROUND  QOS_CLASS_BACKGROUND           INT16_MIN

對(duì)于同一優(yōu)先級(jí),獲取到的全局并發(fā)隊(duì)列是同一個(gè); 不同優(yōu)先級(jí),獲取到的全局并發(fā)隊(duì)列也不同??聪旅娅@取全局并發(fā)隊(duì)列

NSLog(@"%@",dispatch_get_global_queue(0, 0));
NSLog(@"%@",dispatch_get_global_queue(0, 0));
NSLog(@"%@",dispatch_get_global_queue(2, 0));
NSLog(@"%@",dispatch_get_global_queue(2, 0));
運(yùn)行結(jié)果
dispatch_queue_create(<#const char * _Nullable label#>, <#dispatch_queue_attr_t  _Nullable attr#>)

開(kāi)發(fā)者自己創(chuàng)建的隊(duì)列,參數(shù)label是該隊(duì)列的名字,可以通過(guò)

dispatch_queue_get_label(<#dispatch_queue_t  _Nullable queue#>)

獲取。參數(shù)attr傳入DISPATCH_QUEUE_SERIAL或nil返回串行隊(duì)列,傳入DISPATCH_QUEUE_CONCURRENT返回并行隊(duì)列。
dispatch_queue_create 創(chuàng)建的優(yōu)先級(jí)與全局并發(fā)隊(duì)列的默認(rèn)優(yōu)先級(jí)是同一級(jí)別,也就是說(shuō)創(chuàng)建的隊(duì)列的優(yōu)先級(jí)為默認(rèn)優(yōu)先級(jí)。至于怎么改變它的優(yōu)先級(jí)需要調(diào)用dispatch_set_target_queue,這個(gè)函數(shù)后面描述。

調(diào)度函數(shù)

dispatch_sync 將任務(wù)提交到相應(yīng)隊(duì)列,同步執(zhí)行,不開(kāi)新線(xiàn)程
dispatch_async 將任務(wù)提交到相應(yīng)隊(duì)列,異步執(zhí)行,如果從主隊(duì)列取任務(wù),不開(kāi)新線(xiàn)程,在主線(xiàn)程中執(zhí)行;其他隊(duì)列,開(kāi)新線(xiàn)程執(zhí)行
現(xiàn)在來(lái)看看調(diào)度函數(shù)與各種隊(duì)列的組合后的情況:
1、dispatch_sync 與 串行隊(duì)列:
(1)在串行隊(duì)列queue中調(diào)用該函數(shù)、且該函數(shù)是從串行隊(duì)列queue中去任務(wù)執(zhí)行,就會(huì)出現(xiàn)線(xiàn)程死鎖。 例如下面兩種情況:

在主線(xiàn)程中調(diào)用

dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"在主線(xiàn)程中調(diào)用dispatch_sync,并且該函數(shù)第一個(gè)參數(shù)傳入的是主隊(duì)列,則會(huì)出現(xiàn)線(xiàn)程死鎖");
    });

在同步執(zhí)行任務(wù)的子線(xiàn)程中調(diào)用

dispatch_queue_t queue = dispatch_queue_create("串行隊(duì)列", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        /*
         其他代碼
         */
        dispatch_sync(queue, ^{
            NSLog(@"queue是串行隊(duì)列就會(huì)出現(xiàn)線(xiàn)程死鎖");
        });
    });

(2)該串行隊(duì)列與當(dāng)前隊(duì)列不同,不會(huì)出現(xiàn)線(xiàn)程死鎖。在當(dāng)前線(xiàn)程中同步執(zhí)行。dispatch_sync執(zhí)行完后它后面的代碼才會(huì)執(zhí)行。

2、dispatch_sync 與 并行隊(duì)列:
因?yàn)閐ispatch_sync不開(kāi)線(xiàn)程,并且線(xiàn)程一次只能執(zhí)行一個(gè)任務(wù),所以依然是,dispatch_sync執(zhí)行完后它后面的代碼才會(huì)執(zhí)行。
3、dispatch_async 與 串行隊(duì)列:
(1)主隊(duì)列,不開(kāi)新線(xiàn)程,任務(wù)將在主線(xiàn)程中執(zhí)行。但不會(huì)馬上執(zhí)行,而是將任務(wù)從棧copy到堆,等待主隊(duì)列中棧區(qū)的任務(wù)執(zhí)行完,再執(zhí)行堆區(qū)的任務(wù)。因此

dispatch_async(dispatch_get_main_queue(), ^{
        /*
         在主線(xiàn)程執(zhí)行任務(wù)
         */
    });

有兩個(gè)作用:1)子線(xiàn)程處理耗時(shí)操作后回到主線(xiàn)程處理UI; 2)延時(shí)。
(2)開(kāi)發(fā)者創(chuàng)建的串行隊(duì)列,開(kāi)新線(xiàn)程同步執(zhí)行。
4、dispatch_async 與 并發(fā)隊(duì)列:
開(kāi)新線(xiàn)程并發(fā)執(zhí)行任務(wù),至于開(kāi)多少條線(xiàn)程由系統(tǒng)決定??聪旅娴拇a以及運(yùn)行結(jié)果:

for (int i = 0; i < 20; i++) {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"線(xiàn)程%@執(zhí)行第%d個(gè)任務(wù)",[NSThread currentThread],i);
    });
}
運(yùn)行結(jié)果 number從3到14,新開(kāi)線(xiàn)程個(gè)數(shù)<20

dispatch_after 延時(shí)操作,調(diào)用函數(shù)

dispatch_after(<#dispatch_time_t when#>, <#dispatch_queue_t  _Nonnull queue#>, <#^(void)block#>)

參數(shù)when 指定時(shí)間,傳入DISPATCH_TIME_NOW,效果等同于dispatch_async;傳入DISPATCH_TIME_FOREVER,任務(wù)將永不執(zhí)行;所以一般傳入dispatch_time(DISPATCH_TIME_NOW, (int64_t)(t * NSEC_PER_SEC))
t*NSEC_PER_SEC代表t秒。
參數(shù)queue可以是任意隊(duì)列,一般傳入主隊(duì)列。

dispatch_group 調(diào)度組
幾個(gè)任務(wù)并發(fā)執(zhí)行,并且等到都執(zhí)行完后再做其他操作,就可調(diào)使用該函數(shù)。例如異步下載小說(shuō)A、B、C,下載完后提示用戶(hù),代碼如下:

    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_group_async(group, queue, ^{
        NSLog(@"下載小說(shuō)A");
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"下載小說(shuō)B");
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"下載小說(shuō)C");
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"所有小說(shuō)下載完成");
    });
運(yùn)行結(jié)果

也可將

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"所有小說(shuō)下載完成");
});

替換成

BOOL flag = dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
if (flag) {
    NSLog(@"所有小說(shuō)下載完成");
} else {
    NSLog(@"小說(shuō)下載超時(shí)");
}

dispatch_group_wait的第二個(gè)參數(shù)可傳入指定時(shí)間。

dispatch_once 一次性操作
調(diào)用函數(shù)

dispatch_once(<#dispatch_once_t * _Nonnull predicate#>, <#^(void)block#>)

參數(shù)predicate 是用來(lái)標(biāo)識(shí)block是否執(zhí)行的指針,必須是全局或靜態(tài)變量,并且該指針?biāo)赶虻膮^(qū)域的初始值為0,當(dāng)任務(wù)執(zhí)行完畢,系統(tǒng)會(huì)將其值置為-1。
這個(gè)函數(shù)用于初始化全局?jǐn)?shù)據(jù),并且保證線(xiàn)程安全,常用于OC中的單例模式。
dispatch_apply dispatch_sync 與 dispatch_group 的結(jié)合

調(diào)用函數(shù)

dispatch_apply(<#size_t iterations#>, <#dispatch_queue_t  _Nonnull queue#>, <#^(size_t)block#>)

參數(shù) iterations 將任務(wù)循環(huán)添加到指定隊(duì)列的次數(shù); block的參數(shù)是循環(huán)下標(biāo)(從0開(kāi)始)
調(diào)用該函數(shù)會(huì)將queue中的任務(wù)執(zhí)行完才執(zhí)行它后面的代碼。queue如果是串行隊(duì)列,queue中的任務(wù)會(huì)在當(dāng)前線(xiàn)程中同步執(zhí)行;queue如果是并行隊(duì)列,當(dāng)前線(xiàn)程會(huì)取出queue中的第一個(gè)任務(wù)執(zhí)行,然后開(kāi)新線(xiàn)程執(zhí)行后面的任務(wù),所開(kāi)線(xiàn)程個(gè)數(shù)由系統(tǒng)決定。因?yàn)閐ispatch_apply具有dispatch_sync的特征,所以如果queue是串行隊(duì)列并且與當(dāng)前的隊(duì)列是同一個(gè)對(duì)象時(shí),就會(huì)出現(xiàn)線(xiàn)程死鎖。
dispatch_apply主要用于異步并發(fā)處理數(shù)據(jù),并且處理完后統(tǒng)一操作。所以dispatch_apply一般在子線(xiàn)程中調(diào)用。當(dāng)然,像這樣的操作用dispatch_group也可以實(shí)現(xiàn)。但是,思考這種情況,如果對(duì)象數(shù)組,里面所有元素需要并發(fā)執(zhí)行某種操作,并且都執(zhí)行完之后要統(tǒng)一做處理,這時(shí)如果用dispatch_group,則需要for in 遍歷數(shù)組,用dispatch_apply則可以省去這種遍歷。代碼如下:

dispatch_async(queue, ^{
    dispatch_apply(arr.count, dispatch_get_global_queue(0, 0), ^(size_t i) {
        NSLog(@"%@",arr[i]);
    });
});

dispatch_barrier_async 在處理數(shù)據(jù)讀取時(shí),為了避免數(shù)據(jù)競(jìng)爭(zhēng)的問(wèn)題,寫(xiě)入操作與寫(xiě)入操作或讀取操作不能并發(fā)執(zhí)行。針對(duì)這個(gè)例子,可以使用dispatch_barrier_async將寫(xiě)入操作提交到并發(fā)隊(duì)列中,此時(shí)被提交的任務(wù)暫不執(zhí)行,當(dāng)他前面的任務(wù)執(zhí)行完畢時(shí)在執(zhí)行該任務(wù),等到該任務(wù)執(zhí)行完畢,后面提交的才執(zhí)行。需要注意的是指定的queue應(yīng)該是通過(guò)dispatch_queue_create創(chuàng)建的,系統(tǒng)定義的全局并發(fā)隊(duì)列無(wú)效。示例代碼如下:

dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
    NSLog(@"讀取操作1");
});
dispatch_async(queue, ^{
    NSLog(@"讀取操作2");
});
dispatch_async(queue, ^{
    NSLog(@"讀取操作3");
});
dispatch_barrier_async(queue, ^{
    NSLog(@"寫(xiě)入操作1");
});
dispatch_async(queue, ^{
    NSLog(@"讀取操作4");
});
dispatch_async(queue, ^{
    NSLog(@"讀取操作5");
});
dispatch_async(queue, ^{
    NSLog(@"讀取操作6");
});
dispatch_barrier_async(queue, ^{
    NSLog(@"寫(xiě)入操作2");
});
dispatch_async(queue, ^{
    NSLog(@"讀取操作7");
});
dispatch_async(queue, ^{
    NSLog(@"讀取操作8");
});

運(yùn)行結(jié)果

與之對(duì)應(yīng)的是dispatch_barrier_sync,該函數(shù)會(huì)阻塞當(dāng)前線(xiàn)程。它也會(huì)等待前面提交的任務(wù)執(zhí)行完畢在執(zhí)行該函數(shù)提交的任務(wù),等它執(zhí)行完畢后面的任務(wù)才提交到queue中執(zhí)行

dispatch_semaphore 信號(hào)量。 簡(jiǎn)單來(lái)說(shuō)就是控制訪(fǎng)問(wèn)資源的數(shù)量,比如系統(tǒng)有兩個(gè)資源可以被利用,同時(shí)有三個(gè)線(xiàn)程要訪(fǎng)問(wèn),只能允許兩個(gè)線(xiàn)程訪(fǎng)問(wèn),第三個(gè)應(yīng)當(dāng)?shù)却Y源被釋放后再訪(fǎng)問(wèn)。
下面逐一介紹與之相關(guān)的三個(gè)函數(shù):
(1)dispatch_semaphore_create(<#long value#>) 創(chuàng)建信號(hào)量,返回值類(lèi)型dispatch_semaphore_t
參數(shù)value 為允許訪(fǎng)問(wèn)資源的線(xiàn)程數(shù),該值必須 >= 0,否則會(huì)返回nil。當(dāng)value為0時(shí)對(duì)訪(fǎng)問(wèn)資源的線(xiàn)程沒(méi)有限制。
(2)dispatch_semaphore_signal(<#dispatch_semaphore_t _Nonnull dsema#>) 信號(hào)量+1
(3)dispatch_semaphore_wait(<#dispatch_semaphore_t _Nonnull dsema#>, <#dispatch_time_t timeout#> 該函數(shù)會(huì)讓信號(hào)量-1

這個(gè)函數(shù)的具體作用是這樣的,如果dsema信號(hào)量的值大于0,該函數(shù)所處線(xiàn)程就繼續(xù)執(zhí)行下面的語(yǔ)句,并且將信號(hào)量的值減1,返回值為0;如果desema的值為0,那么這個(gè)函數(shù)就阻塞當(dāng)前線(xiàn)程等待timeout,如果等待的期間desema的值被dispatch_semaphore_signal函數(shù)加1了,且該函數(shù)所處線(xiàn)程獲得了信號(hào)量,那么就繼續(xù)向下執(zhí)行并將信號(hào)量減1, 返回值為0。如果等待期間沒(méi)有獲取到信號(hào)量或者信號(hào)量的值一直為0,那么等到timeout時(shí),其所處線(xiàn)程自動(dòng)執(zhí)行其后語(yǔ)句,返回值大于0。通過(guò)返回值是否為0,可以判斷等待是否超時(shí)。

第二個(gè)函數(shù)和第一個(gè)函數(shù)是配套使用的,使用時(shí)先調(diào)用函數(shù)(3)將信號(hào)量-1,后調(diào)用函數(shù)(2)將信號(hào)量+1

為了加深理解,舉一個(gè)在餐廳排隊(duì)吃放的例子。假設(shè)某餐廳有50個(gè)座位,這相當(dāng)于調(diào)用dispatch_semaphore_wait(value: Int)參數(shù)傳入50,有顧客來(lái)吃飯相當(dāng)于函數(shù)dispatch_semaphore_wait(dsema: dispatch_semaphore_t, timeout: dispatch_time_t),前50個(gè)顧客都有座位直接就餐。第51個(gè)顧客來(lái)到時(shí)就需要等待前面的顧客吃完飯離開(kāi),他才能就坐用餐。此時(shí)相當(dāng)于dispatch_semaphore_wait函數(shù)阻塞當(dāng)前線(xiàn)程。當(dāng)有顧客用餐完畢離開(kāi)時(shí),此時(shí)就有了一個(gè)空位。相當(dāng)于dispatch_semaphore_signal將型號(hào)來(lái)加1,第51個(gè)顧客等到座位,相當(dāng)于dispatch_semaphore_wait返回值為0。

信號(hào)量是GCD同步的一種方式。前面介紹過(guò)dispatch_barrier_async是對(duì)queue中的任務(wù)進(jìn)行批量同步處理,dispatch_sync是對(duì)queue中的任務(wù)單個(gè)同步處理,而dispatch_semaphore是對(duì)queue中的某個(gè)任務(wù)中的某部分(某段代碼)同步處理。此時(shí)將dispatch_semaphore_wait中的參數(shù)傳入1。
dispatch_semaphore 的使用如下:

for (int i = 0; i < 100; i++) {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        /*
         其他并發(fā)操作
        */
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        /*
         同步操作,例如 [arr addObject:@(i)];
         */
        [arr addObject:@(i)];
        dispatch_semaphore_signal(semaphore);
        /*
         其他并發(fā)操作
         */
    });
}

dispatch_suspend / dispatch_resume 暫停指定的隊(duì)列 / 恢復(fù)指定的隊(duì)列
有時(shí)候獲取希望提交到queue中的任務(wù)暫不執(zhí)行,等待某一時(shí)刻執(zhí)行,這時(shí)候就可使用dispatch_suspend 和 dispatch_resume, 使用dispatch_suspend時(shí)對(duì)正在執(zhí)行的任務(wù)不會(huì)起作用。

dispatch_suspend使指定隊(duì)列的暫停計(jì)數(shù)+1,dispatch_resume使指定隊(duì)列的暫停計(jì)數(shù)-1。
使用它們時(shí)要注意幾點(diǎn):
(1)dispatch_suspend 與 dispatch_resume 成對(duì)出現(xiàn) ,否則程序在運(yùn)行時(shí)會(huì)crash
(2)dispatch_suspend 與 dispatch_resume 中指定的隊(duì)列是通過(guò) dispatch_queue_create 創(chuàng)建的,其他隊(duì)列無(wú)效

dispatch_set_target_queue 前面提到過(guò),我們自己創(chuàng)建的隊(duì)列的優(yōu)先級(jí)是默認(rèn)優(yōu)先級(jí),需要更改優(yōu)先級(jí)需要調(diào)用dispatch_set_target_queue(系統(tǒng)的隊(duì)列優(yōu)先級(jí)不能修改)。實(shí)際上,dispatch_set_target_queue 主要有兩個(gè)作用:
(1)更改 dispatch_queue_create 函數(shù)創(chuàng)建的隊(duì)列的優(yōu)先級(jí)。代碼如下:

dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_set_target_queue(queue, dispatch_get_global_queue(0, 0));

函數(shù)中第一個(gè)是需要變更優(yōu)先級(jí)的隊(duì)列,第二個(gè)參數(shù)是需要傳入全局并發(fā)隊(duì)列,并且優(yōu)先級(jí)是第一個(gè)參數(shù)想要的優(yōu)先級(jí)。
(2)修改用戶(hù)隊(duì)列的目標(biāo)隊(duì)列,使多個(gè)serial queue在目標(biāo)queue上一次只有一個(gè)執(zhí)行。第二個(gè)參數(shù)是目標(biāo)隊(duì)列,第一個(gè)參數(shù)是任務(wù)將放在目標(biāo)隊(duì)列中的另一串行隊(duì)列。
例如,用戶(hù)要依次下載小說(shuō)A、B、C,但下載任務(wù)放在不同的串行隊(duì)列中,這時(shí)就可以依次調(diào)用dispatch_set_target_queue,將放有下載任務(wù)的隊(duì)列作為第一個(gè)參數(shù)傳入,讓任務(wù)將目標(biāo)隊(duì)列中同步執(zhí)行。 示例代碼如下:

dispatch_queue_t targetQueue = dispatch_queue_create("targetQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue3 = dispatch_queue_create("queue3", DISPATCH_QUEUE_SERIAL);
    dispatch_set_target_queue(queue1, targetQueue);
    dispatch_set_target_queue(queue2, targetQueue);
    dispatch_set_target_queue(queue3, targetQueue);
    dispatch_async(queue1, ^{
        NSLog(@"開(kāi)始下載小說(shuō)A");
        /*
         下載中
         */
        NSLog(@"小說(shuō)A下載完成");
    });
    dispatch_async(queue2, ^{
        NSLog(@"開(kāi)始下載小說(shuō)B");
        /*
         下載中
         */
        NSLog(@"小說(shuō)B下載完成");
    });
    dispatch_async(queue3, ^{
        NSLog(@"開(kāi)始下載小說(shuō)C");
        /*
         下載中
         */
        NSLog(@"小說(shuō)C下載完成");
    });
運(yùn)行結(jié)果
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • GCD (Grand Central Dispatch) :iOS4 開(kāi)始引入,使用更加方便,程序員只需要將任務(wù)添...
    池鵬程閱讀 1,444評(píng)論 0 2
  • 從哪說(shuō)起呢? 單純講多線(xiàn)程編程真的不知道從哪下嘴。。 不如我直接引用一個(gè)最簡(jiǎn)單的問(wèn)題,以這個(gè)作為切入點(diǎn)好了 在ma...
    Mr_Baymax閱讀 2,918評(píng)論 1 17
  • 一、多線(xiàn)程簡(jiǎn)介: 所謂多線(xiàn)程是指一個(gè) 進(jìn)程 -- process(可以理解為系統(tǒng)中正在運(yùn)行的一個(gè)應(yīng)用程序)中可以開(kāi)...
    尋形覓影閱讀 1,189評(píng)論 0 6
  • 最近頗花了一番功夫把多線(xiàn)程GCD人的一些用法總結(jié)出來(lái),一來(lái)幫自己鞏固一下知識(shí)、二來(lái)希望能幫到對(duì)這一塊還迷茫...
    人活一世閱讀 364評(píng)論 1 1
  • 你為什么要這么對(duì)我?我就想問(wèn)問(wèn)你,為什么這么對(duì)我?談戀愛(ài)5年了,我對(duì)你怎么樣你心里清楚,一直以來(lái),我都對(duì)你這么好,...
    小月亮芽?jī)?/span>閱讀 294評(píng)論 2 1

友情鏈接更多精彩內(nèi)容