ObjC-多線程之GCD

介紹

GCD(Grand Central Dispatch)是基于C語言開發(fā)的一套多線程開發(fā)機(jī)制腊状,也是目前蘋果官方推薦的多線程開發(fā)方法。GCD 是一套低層API袋狞,基于C語言開發(fā)映屋,完全面向過程的,用于將任務(wù)切分成單一任務(wù)提交至隊列并發(fā)或者串行執(zhí)行。遵循FIFO原則早处,先提交到隊列的先執(zhí)行乙濒。

iOS4.0中首度引入GCD,GCD是管理任務(wù)執(zhí)行的一項技術(shù)傻丝,它使得我們對多任務(wù)處理變得更加方便和有效诉儒。它支持同步或異步任務(wù)處理,串行或并行的處理隊列(Dispath Queue)泛释,非系統(tǒng)調(diào)用的信號量機(jī)制温算,定時任務(wù)處理注竿,進(jìn)程、文件或網(wǎng)絡(luò)的監(jiān)聽任務(wù)等裙顽。這個龐大的任務(wù)處理技術(shù)大大減少了線程的管理工作宣谈,使基于任務(wù)的開發(fā)變得更加高效闻丑。

 GCD 和 block (<http://scottmaxiao.github.io/IOS-Block.html>)的配合使用,可以方便地進(jìn)行多線程編程扬卷。

基本概念

串行和并行

串行:任務(wù)按先后順序逐個執(zhí)行酸钦。

并行:后面的任務(wù)不會等前面的任務(wù)完成了再執(zhí)行卑硫,同樣會遵循先添加先執(zhí)行的原則,但添加間隔往往忽略不計入挣。所以看上去像是一起執(zhí)行硝拧。

并發(fā)和并行

并發(fā)是指兩個或多個事件在同一時間間隔內(nèi)發(fā)生。單核CPU并發(fā)切換執(zhí)行多個任務(wù)滋恬。

并行是指兩個或者多個事件在同一時刻發(fā)生恢氯。多核CPU可以平行執(zhí)行多個任務(wù)。

下圖描述的就是并發(fā)和并行的區(qū)別勋磕。

gcd

同步和異步

同步:一個同步函數(shù)只在完成了預(yù)定任務(wù)后才返回敢靡。會阻塞當(dāng)前線程啸胧。

異步:異步時任務(wù)開啟會立即返回,不阻塞當(dāng)前線程去執(zhí)行下一個函數(shù)。異步會開啟其他線程柠辞。

函數(shù)說明

Dispatch Queue

Dispatch Queue是用來執(zhí)行任務(wù)的隊列主胧,是GCD中最基本的元素之一。

Dispatch Queue分為兩種:
- Serial Dispatch Queue焙格,按添加進(jìn)隊列的順序(先進(jìn)先出)一個接一個的執(zhí)行
- Concurrent Dispatch Queue眷唉,并發(fā)執(zhí)行隊列里的任務(wù)

簡而言之囤官,Serial Dispatch Queue只使用了一個線程,Concurrent Dispatch Queue使用了多個線程(具體使用了多少個肝陪,由系統(tǒng)決定)氯窍。
手動創(chuàng)建Dispatch Queue
//創(chuàng)建串行隊列
dispatch_queue_t queue = dispatch_queue_create("me.tutuge.test.gcd", nil);
//創(chuàng)建串行隊列
dispatch_queue_t queue = dispatch_queue_create("me.tutuge.test.gcd", DISPATCH_QUEUE_SERIAL);
//創(chuàng)建并行隊列
dispatch_queue_t queue = dispatch_queue_create("me.tutuge.test.gcd", DISPATCH_QUEUE_CONCURRENT);
獲取系統(tǒng)提供的Dispatch Queue

系統(tǒng)提供的Dispatch Queue有兩種類型

  • Main Dispatch Queue:
    實際上就是Serial Dispatch Queue(并且只有一個),一般只在需要更新UI時我們才獲取Main Dispatch Queue

  • Global Dispatch Queue:
    實際上是一個Concurrent Dispatch Queue贝淤。大多數(shù)情況下熊楼,可以不必通過dispatch_queue_create函數(shù)生成Concurrent Dispatch Queue,而是只要獲取Global Dispatch Queue使用即可犬耻。Global Dispatch Queue有4個優(yōu)先級执泰,分別是:High、Default计济、Low沦寂、Background淘衙。

//獲取Main Dispatch Queue
dispatch_queue_t mainQueue = dispatch_get_main_queue()
//獲取Global Dispatch Queue
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)

需要注意一點,如果是在OS X 10.8或iOS 6以及之后版本中使用毯侦,Dispatch Queue將會由ARC自動管理侈离,如果是在此之前的版本筝蚕,需要自己手動釋放,如下:

//創(chuàng)建串行隊列
dispatch_queue_t queue = dispatch_queue_create("me.tutuge.test.gcd", nil);

dispatch_async(queue, ^{
        ...
});

dispatch_release(queue) ;

dispatch_async和dispatch_sync,dispatch_barrier_async

dispatch_async()

dispatch_async():異步添加進(jìn)任務(wù)隊列蔗坯,它不會做任何等待宾濒。
    dispatch_queue_t concurrentQueue = dispatch_queue_create("my.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"1");
    dispatch_async(concurrentQueue, ^{
        NSLog(@"2");
        [NSThread sleepForTimeInterval:5];
        NSLog(@"3");
    });
    NSLog(@"4");

輸出
 11:42:43.820 GCDSeTest[568:303] 1

 11:42:43.820 GCDSeTest[568:303] 4

 11:42:43.820 GCDSeTest[568:1003] 2

 11:42:48.821 GCDSeTest[568:1003] 3//模擬長時間操作時間

dispatch_sync()

dispatch_sync():同步添加操作绘梦。他是等待添加進(jìn)隊列里面的操作完成之后再繼續(xù)執(zhí)行。
dispatch_queue_t concurrentQueue = dispatch_queue_create("my.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"1");
    dispatch_sync(concurrentQueue, ^(){
        NSLog(@"2");
        [NSThread sleepForTimeInterval:10];
        NSLog(@"3");
    });
    NSLog(@"4");
輸出
11:36:25.313 GCDSeTest[544:303] 1

11:36:25.313 GCDSeTest[544:303] 2

11:36:30.313 GCDSeTest[544:303] 3//模擬長時間操作

11:36:30.314 GCDSeTest[544:303] 4

關(guān)于死鎖:

//在main線程使用“同步”方法提交Block钝诚,必定會死鎖榄棵。
dispatch_sync(dispatch_get_main_queue(), ^{
    NSLog(@"I am block...");
});

dispatch_sync在等待block語句執(zhí)行完成疹鳄,而block語句需要在主線程里執(zhí)行,所以dispatch_sync如果在主線程調(diào)用就會造成死鎖

dispatch_barrier_async

dispatch_barrier_async用于等待前面的任務(wù)執(zhí)行完畢后自己才執(zhí)行垫蛆,而它后面的任務(wù)需等待它完成之后才執(zhí)行袱饭。一個典型的例子就是數(shù)據(jù)的讀寫呛占,通常為了防止文件讀寫導(dǎo)致沖突,我們會創(chuàng)建一個串行的隊列决左,所有的文件操作都是通過這個隊列來執(zhí)行走贪,比如FMDB坠狡,這樣就可以避免讀寫沖突遂跟。不過其實這樣效率是有提升的空間的幻锁,當(dāng)沒有更新數(shù)據(jù)時,讀操作其實是可以并行進(jìn)行的假消,而寫操作需要串行的執(zhí)行岭接,如何實現(xiàn)呢:
dispatch_queue_t queue = dispatch_queue_create("Database_Queue", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(queue, ^{
        NSLog(@"reading data1");
    });
    dispatch_async(queue, ^{
        NSLog(@"reading data2");
    });
    dispatch_barrier_async(queue, ^{
        NSLog(@"writing data1");
        [NSThread sleepForTimeInterval:1];

    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"reading data3");
    });

我們將寫數(shù)據(jù)的操作放在dispatch_barrier_async中,這樣能確保在寫數(shù)據(jù)的時候會等待前面的讀操作完成粘拾,而后續(xù)的讀操作也會等到寫操作完成后才能繼續(xù)執(zhí)行创千,提高文件讀寫的執(zhí)行效率追驴。

dispatch_apply

dispatch_apply類似一個for循環(huán),會在指定的dispatch queue中運(yùn)行block任務(wù)n次戒良,如果隊列是并發(fā)隊列冠摄,則會并發(fā)執(zhí)行block任務(wù)河泳,dispatch_apply是一個同步調(diào)用,block任務(wù)執(zhí)行n次后才返回,循環(huán)迭代的執(zhí)行順序是不確定的薄霜。
dispatch_queue_t queue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_CONCURRENT);
//并發(fā)的運(yùn)行一個block任務(wù)5次
dispatch_apply(5, queue, ^(size_t i) {
    NSLog(@"do a job %zu times",i+1);
});
NSLog(@"go on");


Dispatch Block

添加到gcd隊列中執(zhí)行的任務(wù)是以block的形式添加的惰瓜,block封裝了需要執(zhí)行功能汉矿,block帶來的開發(fā)效率提升就不說了洲拇,gcd跟block可以說是一對好基友求橄,能夠很好的配合使用。
創(chuàng)建 block
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
//創(chuàng)建block
dispatch_block_t block = dispatch_block_create(0, ^{
        NSLog(@"do something");
    });
dispatch_async(queue, block);

----
在創(chuàng)建block的時候我們也可以通過設(shè)置QoS蛾绎,指定block對應(yīng)的優(yōu)先級秘通,在dispatch_block_create_with_qos_class中指定QoS類別即可:
----
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
dispatch_block_t block = dispatch_block_create_with_qos_class(0, QOS_CLASS_USER_INITIATED, -1, ^{
        NSLog(@"do something with QoS");
    });
dispatch_async(queue, block);

dispatch_block_wait
當(dāng)需要等待前面的任務(wù)執(zhí)行完畢時,我們可以使用dispatch_block_wait這個接口第股,設(shè)置等待時間DISPATCH_TIME_FOREVER會一直等待直到前面的任務(wù)完成:
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
dispatch_block_t block = dispatch_block_create(0, ^{
    NSLog(@"before sleep");
    [NSThread sleepForTimeInterval:1];
    NSLog(@"after sleep");
});
dispatch_async(queue, block);
//等待前面的任務(wù)執(zhí)行完畢
dispatch_block_wait(block, DISPATCH_TIME_FOREVER);
NSLog(@"coutinue");

dispatch_block_notify
dispatch_block_notify當(dāng)觀察的某個block執(zhí)行結(jié)束之后立刻通知提交另一特定的block到指定的queue中執(zhí)行夕吻,該函數(shù)有三個參數(shù)繁仁,第一參數(shù)是需要觀察的block黄虱,第二個參數(shù)是被通知block提交執(zhí)行的queue,第三參數(shù)是當(dāng)需要被通知執(zhí)行的block晤揣,函數(shù)的原型:
void dispatch_block_notify(dispatch_block_t block, dispatch_queue_t queue,
      dispatch_block_t notification_block);
      
-----

dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
  dispatch_block_t previousBlock = dispatch_block_create(0, ^{
      NSLog(@"previousBlock begin");
      [NSThread sleepForTimeInterval:1];
      NSLog(@"previousBlock done");
  });
  dispatch_async(queue, previousBlock);
  dispatch_block_t notifyBlock = dispatch_block_create(0, ^{
      NSLog(@"notifyBlock");
  });
  //當(dāng)previousBlock執(zhí)行完畢后昧识,提交notifyBlock到global queue中執(zhí)行
  dispatch_block_notify(previousBlock, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), notifyBlock);

dispatch_block_cancel
之前在介紹nsopreration的時候提到它的一個優(yōu)點是可以取消某個operation跪楞,現(xiàn)在在iOS8之后侣灶,提交到gcd隊列中的dispatch block也可取消了,只需要簡單的調(diào)用dispatch_block_cancel傳入想要取消的block即可:
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
dispatch_block_t block1 = dispatch_block_create(0, ^{
    NSLog(@"block1 begin");
    [NSThread sleepForTimeInterval:1];
    NSLog(@"block1 done");
});
dispatch_block_t block2 = dispatch_block_create(0, ^{
    NSLog(@"block2 ");
});
dispatch_async(queue, block1);
dispatch_async(queue, block2);
dispatch_block_cancel(block2);


dispatch_semaphore

在GCD中有三個函數(shù)是semaphore的操作褥影,分別是:
  dispatch_semaphore_create   創(chuàng)建一個semaphore
  dispatch_semaphore_signal   發(fā)送一個信號
  dispatch_semaphore_wait    等待信號
  簡單的介紹一下這三個函數(shù)伪阶,第一個函數(shù)有一個整形的參數(shù)处嫌,我們可以理解為信號的總量熏迹,dispatch_semaphore_signal是發(fā)送一個信號,自然會讓信號總量加1坛缕,dispatch_semaphore_wait等待信號赚楚,當(dāng)信號總量少于0的時候就會一直等待,否則就可以正常的執(zhí)行左胞,并讓信號總量-1举户,根據(jù)這樣的原理俭嘁,我們便可以快速的創(chuàng)建一個并發(fā)控制來同步任務(wù)和有限資源訪問控制躺枕。
  
  
http://www.cnblogs.com/zhidao-chen/p/3600399.html
http://blog.csdn.net/fhbystudy/article/details/25918451
http://www.w2bc.com/Article/16104
http://www.mamicode.com/info-detail-1218654.html


dispatch_after()

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

這里要說一下dispatch_time函數(shù)供填,其原型如下:

dispatch_time_t dispatch_time ( dispatch_time_t when, int64_t delta );
  • 第一個參數(shù)一般是DISPATCH_TIME_NOW拐云,表示從現(xiàn)在開始。

  • 第二個參數(shù)就是真正的延時的具體時間捕虽。這里要特別注意的是慨丐,delta參數(shù)是“納秒!”泄私,就是說房揭,延時1秒的話晌端,delta應(yīng)該是“1000000000”=捅暴。=,太長了咧纠,所以理所當(dāng)然系統(tǒng)提供了常量蓬痒,如下:

#define NSEC_PER_SEC 1000000000ull
#define USEC_PER_SEC 1000000ull
#define NSEC_PER_USEC 1000ull
關(guān)鍵詞解釋:
 NSEC:納秒。
 USEC:微妙漆羔。
 SEC:秒
 PER:每

所以:
 NSEC_PER_SEC梧奢,每秒有多少納秒。
 USEC_PER_SEC演痒,每秒有多少毫秒亲轨。(注意是指在納秒的基礎(chǔ)上)
 NSEC_PER_USEC,每毫秒有多少納秒鸟顺。

[參考]http://www.cocoachina.com/ios/20150505/11751.html


dispatch_once

dispatch\_once是線程安全的惦蚊。它能保證多個線程同時調(diào)用卻只會執(zhí)行塊一次器虾。在dispatch\_once返回之前,所有線程將會等待直到執(zhí)行完畢蹦锋。

dispatch\_once函數(shù)通常用在單例模式上兆沙,它可以保證在程序運(yùn)行期間某段代碼只執(zhí)行一次,如果我們要通過dispatch\_once創(chuàng)建一個單例類莉掂。
+(id)getInstance{
    
    static GapSet *gapSet;
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        gapSet = [[GapSet alloc] initObject];
    });
    
    return gapSet;
}

注意:dispatch_once_t必須是全局或static變量葛圃。

[參考]
http://blog.afantree.com/gcd/translation-dispatch-once-secret.html


dispatch_suspend和dispatch_resume

http://www.reibang.com/p/85b75c7a6286

dispatch_group

如果想在dispatch\_queue中所有的任務(wù)執(zhí)行完成后在做某種操作,在串行隊列中巫湘,可以把該操作放到最后一個任務(wù)執(zhí)行完成后繼續(xù)装悲,但是在并行隊列中怎么做呢。這就有dispatch\_group 成組操作尚氛。比如
 dispatch_group_t group = dispatch_group_create();
 dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
      // 并行執(zhí)行的線程一
      NSLog(@"dispatch-1");
 });
 dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
      // 并行執(zhí)行的線程二
      NSLog(@"dispatch-2");
 });
 dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
      // 匯總結(jié)果
      NSLog(@"dispatch-end");
 });
dispatch_group_wait(group,DISPATCH_TIME_FOREVER);

Dispatch IO


Dispatch Source

http://www.reibang.com/p/f9e01c69a46f


[參考]

http://www.reibang.com/p/f9e01c69a46f

http://blog.devtang.com/blog/2012/02/22/use-gcd/

http://blog.csdn.net/zhangao0086/article/details/38904923

http://www.cocoachina.com/ios/20150505/11751.html

http://www.cnblogs.com/SnailFish/articles/3199863.html

http://www.cnblogs.com/zhidao-chen/category/461198.html

http://blog.afantree.com/ios/the-gcd-learning-directorygcd-debrief.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末诀诊,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子阅嘶,更是在濱河造成了極大的恐慌属瓣,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件讯柔,死亡現(xiàn)場離奇詭異抡蛙,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)魂迄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進(jìn)店門粗截,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人捣炬,你說我怎么就攤上這事熊昌。” “怎么了湿酸?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵婿屹,是天一觀的道長。 經(jīng)常有香客問我推溃,道長昂利,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任铁坎,我火速辦了婚禮蜂奸,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘硬萍。我一直安慰自己扩所,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布襟铭。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪寒砖。 梳的紋絲不亂的頭發(fā)上赐劣,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天,我揣著相機(jī)與錄音哩都,去河邊找鬼魁兼。 笑死,一個胖子當(dāng)著我的面吹牛漠嵌,可吹牛的內(nèi)容都是我干的咐汞。 我是一名探鬼主播,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼儒鹿,長吁一口氣:“原來是場噩夢啊……” “哼化撕!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起约炎,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤植阴,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后圾浅,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體掠手,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年狸捕,在試婚紗的時候發(fā)現(xiàn)自己被綠了喷鸽。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡灸拍,死狀恐怖做祝,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情株搔,我是刑警寧澤剖淀,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站纤房,受9級特大地震影響纵隔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜炮姨,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一捌刮、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧舒岸,春花似錦绅作、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽个少。三九已至,卻和暖如春眯杏,著一層夾襖步出監(jiān)牢的瞬間夜焦,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工岂贩, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留茫经,地道東北人。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓萎津,卻偏偏與公主長得像卸伞,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子锉屈,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,037評論 2 355

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

  • 談到iOS多線程荤傲,一般都會談到四種方式:pthread、NSThread部念、GCD和NSOperation弃酌。其中,蘋...
    攻城獅GG閱讀 271評論 0 3
  • 程序中同步和異步是什么意思儡炼?有什么區(qū)別妓湘? 解釋一:異步調(diào)用是通過使用單獨的線程執(zhí)行的。原始線程啟動異步調(diào)用乌询,異步調(diào)...
    風(fēng)繼續(xù)吹0閱讀 1,033評論 1 2
  • GCD (Grand Central Dispatch) :iOS4 開始引入榜贴,使用更加方便,程序員只需要將任務(wù)添...
    池鵬程閱讀 1,334評論 0 2
  • Dispatch Queues dispatch queues是執(zhí)行任務(wù)的強(qiáng)大工具妹田,允許你同步或異步地執(zhí)行任意代碼...
    YangPu閱讀 646評論 0 4
  • 我愛你唬党!沒有任何人能代替你在我心里的位置!盡管我現(xiàn)在無法和你在一起鬼佣,但是我相信我一定會和你共度余生的驶拱!我要和你一起...
    太虛環(huán)境閱讀 235評論 0 0