iOS開發(fā)-多線程

首先明確線程和進程的關(guān)系和區(qū)別:

一個程序至少有一個進程,一個進程至少有一個線程.

線程的劃分尺度小于進程,使得多線程程序的并發(fā)性高。

另外,進程在執(zhí)行過程中擁有獨立的內(nèi)存單元,而多個線程共享內(nèi)存,從而極大地提高了程序的運行效率。

線程在執(zhí)行過程中與進程還是有區(qū)別的。每個獨立的線程有一個程序運行的入口、順序執(zhí)行序列和程序的出口。但是線程不能夠獨立執(zhí)行,必須依存在應用程序中,由應用程序提供多個線程執(zhí)行控制。

從邏輯角度來看,多線程的意義在于一個應用程序中,有多個執(zhí)行部分可以同時執(zhí)行。但操作系統(tǒng)并沒有將多個線程看做多個獨立的應用,來實現(xiàn)進程的調(diào)度和管理以及資源分配。這就是進程和線程的重要區(qū)別。

進程是具有一定獨立功能的程序關(guān)于某個數(shù)據(jù)集合上的一次運行活動,進程是系統(tǒng)進行資源分配和調(diào)度的一個獨立單位.

線程是進程的一個實體,是CPU調(diào)度和分派的基本單位,它是比進程更小的能獨立運行的基本單位.線程自己基本上不擁有系統(tǒng)資源,只擁有一點在運行中必不可少的資源(如程序計數(shù)器,一組寄存器和棧),但是它可與同屬一個進程的其他的線程共享進程所擁有的全部資源.

一個線程可以創(chuàng)建和撤銷另一個線程;同一個進程中的多個線程之間可以并發(fā)執(zhí)行.

進程和線程的主要差別在于它們是不同的操作系統(tǒng)資源管理方式。進程有獨立的地址空間,一個進程崩潰后,在保護模式下不會對其它進程產(chǎn)生影響,而線程只是一個進程中的不同執(zhí)行路徑。線程有自己的堆棧和局部變量,但線程之間沒有單獨的地址空間,一個線程死掉就等于整個進程死掉,所以多進程的程序要比多線程的程序健壯,但在進程切換時,耗費資源較大,效率要差一些。但對于一些要求同時進行并且又要共享某些變量的并發(fā)操作,只能用線程,不能用進程。


iOS中多線程有以下四種

·PThread

·GCD

·NSThread

·NSOperation & NSOperationQueue


1.PThread(不常用)

POSIX線程(POSIX threads),簡稱Pthreads,是線程的POSIX標準。該標準定義了創(chuàng)建和操縱線程的一整套API。在類Unix操作系統(tǒng)(Unix、Linux、Mac OS X等)中,都使用Pthreads作為操作系統(tǒng)的線程。


2.GCD(*)Grand Center Dispatch

自動管理縣城的生命周期,自動創(chuàng)建線程、調(diào)度任務、銷毀線程。(使用了block)

GCD中的兩個概念:任務、隊列。

任務:block。分為同步執(zhí)行和異步執(zhí)行。同步會阻塞當前進程,直到block執(zhí)行完畢;異步不會阻塞當前進程。

隊列:用于存放任務。分為串行隊列和并行隊列。串行隊列FIFO;并行隊列同樣是FIFO,不過取出之后放入新的線程里,取出速度很快所以相當于并行。


創(chuàng)建隊列

1.主隊列(刷新UI)

dispatch_queue_t queue = ispatch_get_main_queue();

2.創(chuàng)建自己的隊列

//串行隊列

dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", NULL);

dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_SERIAL);

//并行隊列

dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_CONCURRENT);

3.全局并行隊列(只要是并行的任務都加入這個隊列,系統(tǒng)提供的并行隊列)

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

創(chuàng)建任務

1.同步任務(會阻塞當前線程)SYNC

dispatch_sync(<#queue#>, ^{

//code here

NSLog(@"%@", [NSThread currentThread]);

});

2.異步任務(不會阻塞當前線程)ASYNC

dispatch_async(<#queue#>, ^{

//code here

NSLog(@"%@", [NSThread currentThread]);

});

隊列組

隊列組可以將很多隊列添加到一個組里,這樣做的好處是,當這個組里所有的任務都執(zhí)行完了,隊列組會通過一個方法通知我們。下面是使用方法,這是一個很實用的功能。

//1.創(chuàng)建隊列組

dispatch_group_t group = dispatch_group_create();

//2.創(chuàng)建隊列

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

//3.多次使用隊列組的方法執(zhí)行任務, 只有異步方法

//3.1.執(zhí)行3次循環(huán)

dispatch_group_async(group, queue, ^{

for (NSInteger i = 0; i < 3; i++) {

NSLog(@"group-01 - %@", [NSThread currentThread]);

}

});

//3.2.主隊列執(zhí)行8次循環(huán)

dispatch_group_async(group, dispatch_get_main_queue(), ^{

for (NSInteger i = 0; i < 8; i++) {

NSLog(@"group-02 - %@", [NSThread currentThread]);

}

});

//3.3.執(zhí)行5次循環(huán)

dispatch_group_async(group, queue, ^{

for (NSInteger i = 0; i < 5; i++) {

NSLog(@"group-03 - %@", [NSThread currentThread]);

}

});

//4.都完成后會自動通知

dispatch_group_notify(group, dispatch_get_main_queue(), ^{

NSLog(@"完成 - %@", [NSThread currentThread]);

});


func dispatch_barrier_async(_ queue: dispatch_queue_t, _ block: dispatch_block_t):

這個方法重點是你傳入的queue,當你傳入的queue是通過DISPATCH_QUEUE_CONCURRENT參數(shù)自己創(chuàng)建的queue時,這個方法會阻塞這個queue注意是阻塞 queue ,而不是阻塞當前線程),一直等到這個queue中排在它前面的任務都執(zhí)行完成后才會開始執(zhí)行自己,自己執(zhí)行完畢后,再會取消阻塞,使這個queue中排在它后面的任務繼續(xù)執(zhí)行。

如果你傳入的是其他的queue, 那么它就和dispatch_async一樣了。

func dispatch_barrier_sync(_ queue: dispatch_queue_t, _ block: dispatch_block_t):

這個方法的使用和上一個一樣,傳入自定義的并發(fā)隊列(DISPATCH_QUEUE_CONCURRENT),它和上一個方法一樣的阻塞queue,不同的是 這個方法還會阻塞當前線程。

如果你傳入的是其他的queue, 那么它就和dispatch_sync一樣了。


3.NSTread

這套方案是經(jīng)過蘋果封裝后的,并且完全面向?qū)ο蟮摹K阅憧梢灾苯硬倏鼐€程對象,非常直觀和方便。但是,它的生命周期還是需要我們手動管理。

創(chuàng)建線程啟動:

// 創(chuàng)建

NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:nil];

// 啟動

[thread start];

創(chuàng)建線程并啟動:

[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:nil];

使用NSObject的方法創(chuàng)建并啟動:

[self performSelectorInBackground:@selector(run:) withObject:nil];

其他常用方法:

//取消線程

- (void)cancel;

//啟動線程

- (void)start;

//判斷某個線程的狀態(tài)的屬性

@property (readonly, getter=isExecuting) BOOL executing;

@property (readonly, getter=isFinished) BOOL finished;

@property (readonly, getter=isCancelled) BOOL cancelled;

//設置和獲取線程名字

-(void)setName:(NSString *)n;

-(NSString *)name;

//獲取當前線程信息

+ (NSThread *)currentThread;

//獲取主線程信息

+ (NSThread *)mainThread;

//使當前線程暫停一段時間,或者暫停到某個時刻

+ (void)sleepForTimeInterval:(NSTimeInterval)time;

+ (void)sleepUntilDate:(NSDate *)date;


4.NSOperation和NSOperationQueue

NSOperation 是蘋果公司對 GCD 的封裝,完全面向?qū)ο蟆?NSOperation 和 NSOperationQueue分別對應 GCD 的任務 和 隊列。

操作步驟:

將要執(zhí)行的任務封裝到一個NSOperation對象中。

將此任務添加到一個NSOperationQueue對象中。

然后系統(tǒng)就會自動在執(zhí)行任務。至于同步還是異步、串行還是并行請繼續(xù)往下看:

添加任務

值得說明的是,NSOperation只是一個抽象類,所以不能封裝任務。但它有 2 個子類用于封裝任務。分別是:NSInvocationOperation和NSBlockOperation。創(chuàng)建一個 Operation 后,需要調(diào)用start方法來啟動任務,它會默認在當前隊列同步執(zhí)行。當然你也可以在中途取消一個任務,只需要調(diào)用其cancel方法即可。

NSInvocationOperation : 需要傳入一個方法名。

//1.創(chuàng)建NSInvocationOperation對象

NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];

//2.開始執(zhí)行

[operation start];

NSBlockOperation

//1.創(chuàng)建NSBlockOperation對象

NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{

NSLog(@"%@", [NSThread currentThread]);

}];

//2.開始任務

[operation start];

之前說過這樣的任務,默認會在當前線程執(zhí)行。但是NSBlockOperation還有一個方法:addExecutionBlock:,通過這個方法可以給 Operation 添加多個執(zhí)行 Block。這樣 Operation 中的任務會并發(fā)執(zhí)行,它會在主線程和其它的多個線程執(zhí)行這些任務,注意下面的打印結(jié)果:

//1.創(chuàng)建NSBlockOperation對象

NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{

NSLog(@"%@", [NSThread currentThread]);

}];

//添加多個Block

for (NSInteger i = 0; i < 5; i++) {

[operation addExecutionBlock:^{

NSLog(@"第%ld次:%@", i, [NSThread currentThread]);

}];

}

//2.開始任務

[operation start];

打印輸出

2015-07-28 17:50:16.585 test[17527:4095467] 第2次 - {number = 1, name = main}

2015-07-28 17:50:16.585 test[17527:4095666] 第1次 - {number = 4, name = (null)}

2015-07-28 17:50:16.585 test[17527:4095665] {number = 3, name = (null)}

2015-07-28 17:50:16.585 test[17527:4095662] 第0次 - {number = 2, name = (null)}

2015-07-28 17:50:16.586 test[17527:4095666] 第3次 - {number = 4, name = (null)}

2015-07-28 17:50:16.586 test[17527:4095467] 第4次 - {number = 1, name = main}

?

NOTE:addExecutionBlock方法必須在start()方法之前執(zhí)行,否則就會報錯:

‘*** -[NSBlockOperation addExecutionBlock:]: blocks cannot be added after the operation has started executing or finished'

?自定義Operation

除了上面的兩種 Operation 以外,我們還可以自定義 Operation。自定義 Operation 需要繼承NSOperation類,并實現(xiàn)其main()方法,因為在調(diào)用start()方法的時候,內(nèi)部會調(diào)用main()方法完成相關(guān)邏輯。所以如果以上的兩個類無法滿足你的欲望的時候,你就需要自定義了。你想要實現(xiàn)什么功能都可以寫在里面。除此之外,你還需要實現(xiàn)cancel()在內(nèi)的各種方法。所以這個功能提供給高級玩家,我在這里就不說了,等我需要用到時在研究它,到時候可能會再做更新。

創(chuàng)建隊列

看過上面的內(nèi)容就知道,我們可以調(diào)用一個NSOperation對象的start()方法來啟動這個任務,但是這樣做他們默認是同步執(zhí)行的。就算是addExecutionBlock方法,也會在當前線程和其他線程中執(zhí)行,也就是說還是會占用當前線程。這是就要用到隊列NSOperationQueue了。而且,按類型來說的話一共有兩種類型:主隊列、其他隊列。只要添加到隊列,會自動調(diào)用任務的 start() 方法

主隊列

細心的同學就會發(fā)現(xiàn),每套多線程方案都會有一個主線程(當然啦,說的是iOS中,像 pthread 這種多系統(tǒng)的方案并沒有,因為UI線程理論需要每種操作系統(tǒng)自己定制)。這是一個特殊的線程,必須串行。所以添加到主隊列的任務都會一個接一個地排著隊在主線程處理。

//OBJECTIVE-C

NSOperationQueue *queue = [NSOperationQueue mainQueue];

其他隊列

因為主隊列比較特殊,所以會單獨有一個類方法來獲得主隊列。那么通過初始化產(chǎn)生的隊列就是其他隊列了,因為只有這兩種隊列,除了主隊列,其他隊列就不需要名字了。

注意:其他隊列的任務會在其他線程并行執(zhí)行。

OBJECTIVE-C

//1.創(chuàng)建一個其他隊列

NSOperationQueue *queue = [[NSOperationQueue alloc] init];

//2.創(chuàng)建NSBlockOperation對象

NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{

NSLog(@"%@", [NSThread currentThread]);

}];

//3.添加多個Block

for (NSInteger i = 0; i < 5; i++) {

[operation addExecutionBlock:^{

NSLog(@"第%ld次:%@", i, [NSThread currentThread]);

}];

}

//4.隊列添加任務

[queue addOperation:operation];

打印輸出

2015-07-28 20:26:28.463 test[18622:4443534] {number = 5, name = (null)}

2015-07-28 20:26:28.463 test[18622:4443536] 第2次 - {number = 2, name = (null)}

2015-07-28 20:26:28.463 test[18622:4443535] 第0次 - {number = 4, name = (null)}

2015-07-28 20:26:28.463 test[18622:4443533] 第1次 - {number = 3, name = (null)}

2015-07-28 20:26:28.463 test[18622:4443534] 第3次 - {number = 5, name = (null)}

2015-07-28 20:26:28.463 test[18622:4443536] 第4次 - {number = 2, name = (null)}

OK, 這時應該發(fā)問了,大家將NSOperationQueue與GCD的隊列相比較就會發(fā)現(xiàn),這里沒有串行隊列,那如果我想要10個任務在其他線程串行的執(zhí)行怎么辦?

這就是蘋果封裝的妙處,你不用管串行、并行、同步、異步這些名詞。NSOperationQueue有一個參數(shù)maxConcurrentOperationCount最大并發(fā)數(shù),用來設置最多可以讓多少個任務同時執(zhí)行。當你把它設置為1的時候,他不就是串行了嘛!

NSOperationQueue還有一個添加任務的方法,- (void)addOperationWithBlock:(void (^)(void))block;,這是不是和 GCD 差不多?這樣就可以添加一個任務到隊列中了,十分方便。

NSOperation有一個非常實用的功能,那就是添加依賴。比如有 3 個任務:A: 從服務器上下載一張圖片,B:給這張圖片加個水印,C:把圖片返回給服務器。這時就可以用到依賴了:

OBJECTIVE-C

//1.任務一:下載圖片

NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{

NSLog(@"下載圖片 - %@", [NSThread currentThread]);

[NSThread sleepForTimeInterval:1.0];

}];

//2.任務二:打水印

NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{

NSLog(@"打水印? - %@", [NSThread currentThread]);

[NSThread sleepForTimeInterval:1.0];

}];

//3.任務三:上傳圖片

NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{

NSLog(@"上傳圖片 - %@", [NSThread currentThread]);

[NSThread sleepForTimeInterval:1.0];

}];

//4.設置依賴

[operation2 addDependency:operation1];? ? ? //任務二依賴任務一

[operation3 addDependency:operation2];? ? ? //任務三依賴任務二

//5.創(chuàng)建隊列并加入任務

NSOperationQueue *queue = [[NSOperationQueue alloc] init];

[queue addOperations:@[operation3, operation2, operation1] waitUntilFinished:NO];

打印結(jié)果

2015-07-28 21:24:28.622 test[19392:4637517] 下載圖片 - {number = 2, name = (null)}

2015-07-28 21:24:29.622 test[19392:4637515] 打水印? - {number = 3, name = (null)}

2015-07-28 21:24:30.627 test[19392:4637515] 上傳圖片 - {number = 3, name = (null)}

注意:不能添加相互依賴,會死鎖,比如 A依賴B,B依賴A。

可以使用removeDependency來解除依賴關(guān)系。

可以在不同的隊列之間依賴,反正就是這個依賴是添加到任務身上的,和隊列沒關(guān)系。

其他方法

以上就是一些主要方法, 下面還有一些常用方法需要大家注意:

NSOperation

BOOL executing;? ? ? ? ? ? ? ? ? ? //判斷任務是否正在執(zhí)行

BOOL finished;? ? ? ? ? ? ? ? ? ? //判斷任務是否完成

void (^completionBlock)(void);? ? ? ? //用來設置完成后需要執(zhí)行的操作

- (void)cancel;? ? ? ? ? ? ? ? ? ? //取消任務

- (void)waitUntilFinished;? ? ? ? ? ? //阻塞當前線程直到此任務執(zhí)行完畢

NSOperationQueue

NSUInteger operationCount;? ? ? ? //獲取隊列的任務數(shù)

- (void)cancelAllOperations;? ? ? ? //取消隊列中所有的任務

- (void)waitUntilAllOperationsAreFinished;? ? //阻塞當前線程直到此隊列中的所有任務執(zhí)行完畢

[queue setSuspended:YES];? ? ? ? ? // 暫停queue

[queue setSuspended:NO];? ? ? ? ? // 繼續(xù)queue

好啦,到這里差不多就講完了。當然,我講的并不完整,可能有一些知識我并沒有講到,但作為常用方法,這些已經(jīng)足夠了。不過我在這里只是告訴你了一些方法的功能,只是怎么把他們用到合適的地方,就需要多多實踐了。下面我會說一些關(guān)于多線程的案例,是大家更加什么地了解。

其他用法

在這部分,我會說一些和多線程知識相關(guān)的案例,可能有些很簡單,大家早都知道的,不過因為這篇文章講的是多線程嘛,所以應該盡可能的全面嘛。還有就是,我會盡可能的使用多種方法實現(xiàn),讓大家看看其中的區(qū)別。

線程同步

所謂線程同步就是為了防止多個線程搶奪同一個資源造成的數(shù)據(jù)安全問題,所采取的一種措施。當然也有很多實現(xiàn)方法,請往下看:

互斥鎖:給需要同步的代碼塊加一個互斥鎖,就可以保證每次只有一個線程訪問此代碼塊。

OBJECTIVE-C

@synchronized(self) {

//需要執(zhí)行的代碼塊

}

同步執(zhí)行:我們可以使用多線程的知識,把多個線程都要執(zhí)行此段代碼添加到同一個串行隊列,這樣就實現(xiàn)了線程同步的概念。當然這里可以使用GCD和NSOperation兩種方案,我都寫出來。

OBJECTIVE-C

//GCD

//需要一個全局變量queue,要讓所有線程的這個操作都加到一個queue中

dispatch_sync(queue, ^{

NSInteger ticket = lastTicket;

[NSThread sleepForTimeInterval:0.1];

NSLog(@"%ld - %@",ticket, [NSThread currentThread]);

ticket -= 1;

lastTicket = ticket;

});

//NSOperation & NSOperationQueue

//重點:1. 全局的 NSOperationQueue, 所有的操作添加到同一個queue中

//? ? ? 2. 設置 queue 的 maxConcurrentOperationCount 為 1

//? ? ? 3. 如果后續(xù)操作需要Block中的結(jié)果,就需要調(diào)用每個操作的waitUntilFinished,阻塞當前線程,一直等到當前操作完成,才允許執(zhí)行后面的。waitUntilFinished 要在添加到隊列之后!

NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{

NSInteger ticket = lastTicket;

[NSThread sleepForTimeInterval:1];

NSLog(@"%ld - %@",ticket, [NSThread currentThread]);

ticket -= 1;

lastTicket = ticket;

}];

[queue addOperation:operation];

[operation waitUntilFinished];

//后續(xù)要做的事

延遲執(zhí)行

所謂延遲執(zhí)行就是延時一段時間再執(zhí)行某段代碼。下面說一些常用方法。

perform

OBJECTIVE-C

// 3秒后自動調(diào)用self的run:方法,并且傳遞參數(shù):@"abc"

[self performSelector:@selector(run:) withObject:@"abc" afterDelay:3];

GCD

可以使用 GCD 中的dispatch_after方法,OC 和 Swift 都可以使用,這里只寫 OC 的,Swift 的是一樣的。

OBJECTIVE-C

// 創(chuàng)建隊列

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

// 設置延時,單位秒

double delay = 3;

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), queue, ^{

// 3秒后需要執(zhí)行的任務

});

NSTimer

NSTimer 是iOS中的一個計時器類,除了延遲執(zhí)行還有很多用法,不過這里直說延遲執(zhí)行的用法。同樣只寫 OC 版的,Swift 也是相同的。

OBJECTIVE-C

[NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:@selector(run:) userInfo:@"abc" repeats:NO];

單例模式

至于什么是單例模式,我也不多說,我只說說一般怎么實現(xiàn)。在 Objective-C 中,實現(xiàn)單例的方法已經(jīng)很具體了,雖然有別的方法,但是一般都是用一個標準的方法了,下面來看看。

OBJECTIVE-C

@interface Tool : NSObject

+ (instancetype)sharedTool;

@end

@implementation Tool

static id _instance;

+ (instancetype)sharedTool {

static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

_instance = [[Tool alloc] init];

});

return _instance;

}

@end

這里之所以將單例模式,是因為其中用到了 GCD 的dispatch_once方法。

從其他線程回到主線程的方法

我們都知道在其他線程操作完成后必須到主線程更新UI。所以,介紹完所有的多線程方案后,我們來看看有哪些方法可以回到主線程。

NSThread

//Objective-C

[self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:NO];


GCD

//Objective-C

dispatch_async(dispatch_get_main_queue(), ^{

});


NSOperationQueue

//Objective-C

[[NSOperationQueue mainQueue] addOperationWithBlock:^{

}];

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

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

  • iOS開發(fā)中常用的幾種多線程方案,簡單做個小結(jié),方便日后查閱。 Pthreads NSThead GCD NSOp...
    acqiang閱讀 468評論 0 4
  • 在這篇文章中,我將為你整理一下 iOS 開發(fā)中幾種多線程方案,以及其使用方法和注意事項。當然也會給出幾種多線程的案...
    張戰(zhàn)威ican閱讀 684評論 0 0
  • NSThread 第一種:通過NSThread的對象方法 NSThread *thread = [[NSThrea...
    攻城獅GG閱讀 947評論 0 3
  • 又是經(jīng)年艾草香, 幾多熱血賦端陽。 一江恨水毫尖涌, 千載愁懷韻底藏。 天問吟成悲濁世, 離騷讀罷嘆華章。 沉冤屈...
    若水塵心閱讀 230評論 0 2
  • 妻懷孕已經(jīng)六個月了,看著妻日漸隆起的腹部,想象著一個幼小的生命正在那一片羊水中舞拳弄腳,幸福的感覺立時便彌漫...
    蘇北雨人閱讀 894評論 23 7

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