我們在前面兩節(jié)分別講了iOS多線程的Pthrea缘揪、NSThread和GCD,那么我們關(guān)于多線程的學(xué)習(xí)就剩下最后一個內(nèi)容,就是NSOperation。
NSOperation NSOperation其實是對GCD的封裝浴讯,表示了一個獨立的計算單元。NSOperation本身是一個抽象類蔼啦,對我們來說并沒有什么實用價值榆纽,但是系統(tǒng)幫我們封裝了兩個它的子類,并且我們也可以自己去封裝,它的之類都是用線程安全的方式來建立狀態(tài)奈籽、優(yōu)先級饥侵、依賴性和取消等的模型。
很多執(zhí)行任務(wù)類型的案例都很好的運用了NSOperation衣屏,包括網(wǎng)絡(luò)請求躏升,圖像壓縮,自然語言處理或者其他很多需要返回處理后數(shù)據(jù)的狼忱、可重復(fù)的膨疏、結(jié)構(gòu)化的、相對長時間運行的任務(wù)钻弄。
前面學(xué)習(xí)GCD的時候我們知道佃却,GCD有兩個核心概念就是任務(wù)和隊列,不能只創(chuàng)建任務(wù)斧蜕,然后置之不理双霍,那樣并沒有什么用處,NSOperation也是一樣的批销,我們不能僅僅把計算單元做好之后就不管它了洒闸,我們還是和GCD一樣把它放進(jìn)一個隊列中進(jìn)行調(diào)度,這樣我們的計算單元才會運作起來均芽,這時候我們就需要另一個概念:NSOperationQueue丘逸。
NSOperationQueue 控制著這些操作的執(zhí)行,它扮演者任務(wù)調(diào)度的角色掀宋,它總是能在遵循先進(jìn)先出的原則下‘讓高優(yōu)先級操作’能先于‘低優(yōu)先級操作’運行深纲,使它管理的操作能基本。
接下來我們先不著急看NSOperation和NSOperationQueue給了我們什么接口劲妙,等我們先學(xué)習(xí)NSOperation有哪些操作可以使用湃鹊。
狀態(tài)
NSOperation包含了一個十分優(yōu)雅的狀態(tài)機來描述每一個操作的執(zhí)行。
isReady → isExecuting → isFinished
為了替代不那么清晰的state屬性镣奋,狀態(tài)直接由上面那些keypath的KVO通知決定币呵,也就是說,當(dāng)一個操作在準(zhǔn)備好被執(zhí)行的時候侨颈,它發(fā)送了一個KVO通知給isReady的keypath余赢,讓這個keypath對應(yīng)的屬性isReady在被訪問的時候返回YES。
每一個屬性對于其他的屬性必須是互相獨立不同的哈垢,也就是同時只可能有一個屬性返回YES妻柒,從而才能維護一個連續(xù)的狀態(tài): - isReady: 返回 YES 表示操作已經(jīng)準(zhǔn)備好被執(zhí)行, 如果返回NO則說明還有其他沒有先前的相關(guān)步驟沒有完成。 - isExecuting: 返回YES表示操作正在執(zhí)行耘分,反之則沒在執(zhí)行举塔。 - isFinished : 返回YES表示操作執(zhí)行成功或者被取消了楼誓,NSOperationQueue只有當(dāng)它管理的所有操作的isFinished屬性全標(biāo)為YES以后操作才停止出列械馆,也就是隊列停止運行春贸,所以正確實現(xiàn)這個方法對于避免死鎖很關(guān)鍵居触。
取消
早些取消那些沒必要的操作是十分有用的。取消的原因可能包括用戶的明確操作或者某個相關(guān)的操作失敗痹屹。
與之前的執(zhí)行狀態(tài)類似,當(dāng)NSOperation的-cancel狀態(tài)調(diào)用的時候會通過KVO通知isCancelled的keypath來修改isCancelled屬性的返回值枉氮,NSOperation需要盡快地清理一些內(nèi)部細(xì)節(jié)志衍,而后到達(dá)一個合適的最終狀態(tài)。這個時候isCancelled和isFinished的值將是YES聊替,而isExecuting的值則為NO楼肪。
優(yōu)先級
不可能所有的操作都是一樣重要,通過以下的順序設(shè)置queuePriority屬性可以加快或者推遲操作的執(zhí)行:
NSOperationQueuePriorityVeryHigh
NSOperationQueuePriorityHigh
NSOperationQueuePriorityNormal
NSOperationQueuePriorityLow
NSOperationQueuePriorityVeryLow
此外惹悄,有些操作還可以指定threadPriority的值春叫,它的取值范圍可以從0.0到1.0,1.0代表最高的優(yōu)先級泣港。鑒于queuePriority屬性決定了操作執(zhí)行的順序暂殖,threadPriority則指定了當(dāng)操作開始執(zhí)行以后的CPU計算能力的分配,如果你不知道這是什么当纱,好吧呛每,你可能根本沒必要知道這是什么。
依賴性
根據(jù)你應(yīng)用的復(fù)雜度不同坡氯,將大任務(wù)再分成一系列子任務(wù)一般都是很有意義的晨横,而你能通過NSOperation
的依賴性實現(xiàn)。
比如說箫柳,對于服務(wù)器下載并壓縮一張圖片的整個過程手形,你可能會將這個整個過程分為兩個操作(可能你還會用到這個網(wǎng)絡(luò)子過程再去下載另一張圖片,然后用壓縮子過程去壓縮磁盤上的圖片)悯恍。顯然圖片需要等到下載完成之后才能被調(diào)整尺寸库糠,所以我們定義網(wǎng)絡(luò)子操作是壓縮子操作的依賴,通過代碼來說就是:
[resizingOperation addDependency:networkingOperation];//設(shè)置resizingOperation依賴networkingOperation
[operationQueue addOperation:networkingOperation];//操作添加進(jìn)隊列
[operationQueue addOperation:resizingOperation];//操作添加進(jìn)隊列
除非一個操作的依賴的isFinished返回YES坪稽,不然這個操作不會開始曼玩。時時牢記將所有的依賴關(guān)系添加到操作隊列很重要,不然會像走路遇到一條大溝窒百,就走不過去了喲黍判。
此外,確保不要意外地創(chuàng)建依賴循環(huán)篙梢,像A依賴B顷帖,B又依賴A,這也會導(dǎo)致杯具的死鎖。
completionBlock
有一個在iOS 4和Snow Leopard新加入的十分有用的功能就是completionBlock屬性贬墩。
每當(dāng)一個NSOperation執(zhí)行完畢榴嗅,它就會調(diào)用它的completionBlock屬性一次,這提供了一個非常好的方式讓你能在視圖控制器(View Controller)里或者模型(Model)里加入自己更多自己的代碼邏輯陶舞。比如說嗽测,你可以在一個網(wǎng)絡(luò)請求操作的completionBlock來處理操作執(zhí)行完以后從服務(wù)器下載下來的數(shù)據(jù)。
對于現(xiàn)在Objective-C程序員必須掌握的工具中肿孵,NSOperation依然是最基本的一個唠粥。盡管GCD對于內(nèi)嵌異步操作十分理想,NSOperation依舊提供更復(fù)雜停做、面向?qū)ο蟮挠嬎隳P停鼘τ谏婕暗礁鞣N類型數(shù)據(jù)官份、需要重復(fù)處理的任務(wù)又是更加理想的悄谐。在你的下一個項目里使用它吧情屹,讓它及帶給用戶歡樂,你自己也會很開心的少辣。
之前了解過NSOperation的同學(xué)們可能看出來了,沒錯,上面就是翻譯Mattt Thompson的反浓,學(xué)過iOS的應(yīng)該都知道這位大神巧婶。
下面我們開始學(xué)NSOperation和NSOperationQueue給我們提供的接口湿右,然后針對常用接口的用法示例罚勾。
執(zhí)行操作
//如果你子類化NSOperation類毅人,你要重寫start方法,如果你直接沒有子類化尖殃,直接使用start丈莺,默認(rèn)是在主線程執(zhí)行的。
- (void)start;
//如果你子類化NSOperation類送丰,你可以重寫main方法缔俄,并且實現(xiàn)它。如果你這樣做器躏,你就沒有必要調(diào)用super俐载。
//你絕不能直接調(diào)用mian方法,你應(yīng)該通過調(diào)用start方法啟動你的線程登失。
//如果直接調(diào)用好像是會在主線程執(zhí)行
- (void)main;
//在NSOperation的任務(wù)完成之后要執(zhí)行的方法塊
@property(copy) void (^completionBlock)(void);
獲取Operation狀態(tài)
//Operation是否被取消遏佣,getter方法用isCancelled
@property (readonly, getter=isCancelled) BOOL cancelled;
//取消一個Operation
- (void)cancel;
//Operation是否在執(zhí)行中,getter方法用isExecuting
@property (readonly, getter=isExecuting) BOOL executing;
//Operation操作是否完成壁畸,getter方法用isFinished
@property (readonly, getter=isFinished) BOOL finished;
//Operation操作是否是異步的贼急,getter方法用isConcurrent茅茂,不建議使用,被下邊的asynchronous方法取代了
@property (readonly, getter=isConcurrent) BOOL concurrent;
//同concurrent
@property (readonly, getter=isAsynchronous) BOOL asynchronous NS_AVAILABLE(10_8, 7_0);
//Operation操作是否已經(jīng)準(zhǔn)備就緒太抓,getter方法用isFinished
@property (readonly, getter=isReady) BOOL ready;
依賴關(guān)系
依賴關(guān)系可以有效的安排操作的順序
//添加依賴空闲,就是在你添加的依賴Operation完成之后當(dāng)前Operation才能開始執(zhí)行
- (void)addDependency:(NSOperation *)op;
//刪除一個之前添加的依賴Operation
- (void)removeDependency:(NSOperation *)op;
@property (readonly, copy) NSArray<NSOperation *> *dependencies;
優(yōu)先級
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8
}; //優(yōu)先級
//NSOperationQueue的優(yōu)先級,對應(yīng)上面的枚舉
@property NSOperationQueuePriority queuePriority;
//這是線程優(yōu)先級走敌,已經(jīng)被棄用了
@property double threadPriority NS_DEPRECATED(10_6, 10_10, 4_0, 8_0);
//這個東西在NSThread的時候已經(jīng)說過了
@property NSQualityOfService qualityOfService NS_AVAILABLE(10_10, 8_0);
在NSThread的時候講的NSQualityOfService碴倾,想看就看一下,在最后掉丽。
等待NSOperation完成
//知道operation完成之前跌榔,后面的代碼都不會在執(zhí)行,
//所以為了避免死鎖捶障,在operation啟動之前不要調(diào)用該方法
- (void)waitUntilFinished;
NSOperationQueue
//添加一個操作
- (void)addOperation:(NSOperation *)op;
//添加一組Operation并且等到數(shù)組中的操作全部完成才會繼續(xù)執(zhí)行
- (void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait NS_AVAILABLE(10_6, 4_0);
//快速添加一個operation任務(wù)
- (void)addOperationWithBlock:(void (^)(void))block NS_AVAILABLE(10_6, 4_0);
//隊列中的operations
@property (readonly, copy) NSArray<__kindof NSOperation *> *operations;
//操作的數(shù)量
@property (readonly) NSUInteger operationCount NS_AVAILABLE(10_6, 4_0);
//最大并發(fā)數(shù)
@property NSInteger maxConcurrentOperationCount;
//隊列掛起僧须,getter方法為isSuspended
@property (getter=isSuspended) BOOL suspended;
//隊列名字
@property (nullable, copy) NSString *name NS_AVAILABLE(10_6, 4_0);
//說過了,去看NSThread最那里有寫http://www.reibang.com/p/b1962d8543ca
@property NSQualityOfService qualityOfService NS_AVAILABLE(10_10, 8_0);
//正在執(zhí)行的操作的調(diào)度隊列项炼,默認(rèn)值是nil担平,只有在隊列中沒有正在執(zhí)行或排隊的操作才能被設(shè)置這個屬性,否則跑出NSInvalidArgumentException異常锭部。
@property (nullable, assign /* actually retain */) dispatch_queue_t underlyingQueue NS_AVAILABLE(10_10, 8_0);
//取消隊列中的所有操作暂论,正在執(zhí)行的操作不取消
- (void)cancelAllOperations;
//和Operations的waitUntilFinished方法一樣,這個是等待隊列中所有的操作完成
- (void)waitUntilAllOperationsAreFinished;
//獲取當(dāng)前的操作隊列拌禾,只讀屬性
@property (class, readonly, strong, nullable) NSOperationQueue *currentQueue NS_AVAILABLE(10_6, 4_0);
//獲取主隊列取胎,只讀屬性
@property (class, readonly, strong) NSOperationQueue *mainQueue NS_AVAILABLE(10_6, 4_0);
前面說了系統(tǒng)幫我們封裝好了NSOperation的兩個子類,下面我們就用這兩個子類實現(xiàn)一下NSOperation的常用方法湃窍。
終于說到了NSOperation的子類闻蛀,看一下NSInvocationOperation和NSBlockOperation兩個的基本用法。
首先是不配合NSOperationQueue坝咐,看一下什么效果
NSLog(@"----");
NSInvocationOperation *invocation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(funcation) object:nil]; //創(chuàng)建一個NSInvocationOperation 類型的Operation循榆,綁定funcation方法
[invocation start];
NSLog(@"----");
NSBlockOperation *block = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:1]; //線程睡眠
NSLog(@"---NSBlockOperation--%@", [NSThread currentThread]);
}]; //創(chuàng)建一個NSBlockOperation 類型的Operation,添加塊方法
[block start];
看一下輸出結(jié)果
2017-08-25 15:27:25.785 NSOperation[2944:455806] ----
2017-08-25 15:27:26.787 NSOperation[2944:455806] ---invocation---<NSThread: 0x60800007bf40>{number = 1, name = main}
2017-08-25 15:27:26.788 NSOperation[2944:455806] ----
2017-08-25 15:27:27.789 NSOperation[2944:455806] ---NSBlockOperation--<NSThread: 0x60800007bf40>{number = 1, name = main}
從輸出結(jié)果看都是在主線程執(zhí)行中同步執(zhí)行的墨坚,所以不配合NSOperationQueue并沒有什么意義秧饮。
NSInvocationOperation
- (void)invocation {
NSInvocationOperation *invocation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(funcation) object:nil];
NSInvocationOperation *invocation2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(funcation) object:nil];
NSInvocationOperation *invocation3 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(funcation) object:nil];
//1.創(chuàng)建隊列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[queue addOperation:invocation];
[queue addOperation:invocation2];
[queue addOperation:invocation3];
}
看一下輸出結(jié)果
2017-08-25 15:41:34.088 NSOperation[2989:469234] ---invocation---<NSThread: 0x60000007d440>{number = 4, name = (null)}
2017-08-25 15:41:34.088 NSOperation[2989:469216] ---invocation---<NSThread: 0x608000260800>{number = 5, name = (null)}
2017-08-25 15:41:34.088 NSOperation[2989:469218] ---invocation---<NSThread: 0x6080002607c0>{number = 3, name = (null)}
輸出結(jié)果顯示每個Operation都開辟了新線程,并且異步執(zhí)行泽篮,那NSBlockOperation會不會是一樣的呢盗尸,來看一下吧
- (void)block {
NSBlockOperation *block1 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:1];
NSLog(@"1----%@",[NSThread currentThread]);
}];
NSBlockOperation *block2 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:1];
NSLog(@"2----%@",[NSThread currentThread]);
}];
NSBlockOperation *block3 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:1];
NSLog(@"3----%@",[NSThread currentThread]);
}];
//創(chuàng)建隊列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[queue addOperation:block1];
[queue addOperation:block2];
[queue addOperation:block3];
}
一起看一下輸出結(jié)果
2017-08-25 15:49:41.484 NSOperation[3013:475949] 1----<NSThread: 0x60000007ea80>{number = 5, name = (null)}
2017-08-25 15:49:41.484 NSOperation[3013:475951] 2----<NSThread: 0x608000070740>{number = 4, name = (null)}
2017-08-25 15:49:41.484 NSOperation[3013:475975] 3----<NSThread: 0x60000007ea00>{number = 3, name = (null)}
和NSInvocationOperation的結(jié)果一樣呢,那接下來我們看一下還有什么好用的方法吧帽撑。
我們看到NSBlockOperation還有個addExecutionBlock方法泼各,這是干嘛的呢?
- (void)block {
NSBlockOperation *block1 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:1];
NSLog(@"1----%@",[NSThread currentThread]);
}];
NSBlockOperation *block2 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:1];
NSLog(@"2----%@",[NSThread currentThread]);
}];
[block2 addExecutionBlock:^{
[NSThread sleepForTimeInterval:1];
NSLog(@"4---%@", [NSThread currentThread]);
}];
NSBlockOperation *block3 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:1];
NSLog(@"3----%@",[NSThread currentThread]);
}];
//創(chuàng)建隊列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[queue addOperation:block1];
[queue addOperation:block2];
[queue addOperation:block3];
}
先來看下打印結(jié)果再說
2017-08-25 16:10:35.735 NSOperation[3114:492996] 3----<NSThread: 0x608000071180>{number = 5, name = (null)}
2017-08-25 16:10:35.735 NSOperation[3114:493009] 2----<NSThread: 0x6000000711c0>{number = 4, name = (null)}
2017-08-25 16:10:35.735 NSOperation[3114:493008] 1----<NSThread: 0x60800006a500>{number = 3, name = (null)}
2017-08-25 16:10:35.735 NSOperation[3114:492993] 4---<NSThread: 0x600000071280>{number = 6, name = (null)}
從結(jié)果來看似乎和新建一個NSBlockOperation效果一樣啊亏拉,都是異步執(zhí)行扣蜻。
看看隊列有中有什么好用的方法吧
//創(chuàng)建隊列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[queue setMaxConcurrentOperationCount:2];
[queue addOperation:block1];
[queue addOperation:block2];
[queue addOperation:block3];
輸出結(jié)果:
2017-08-25 15:53:02.280 NSOperation[3034:479336] 1----<NSThread: 0x60800007b7c0>{number = 3, name = (null)}
2017-08-25 15:53:02.280 NSOperation[3034:479338] 2----<NSThread: 0x600000263d00>{number = 4, name = (null)}
2017-08-25 15:53:03.353 NSOperation[3034:479335] 3----<NSThread: 0x60800007b740>{number = 5, name = (null)}
我們在剛剛的NSBlockOperation測試的基礎(chǔ)上加上了一句[queue setMaxConcurrentOperationCount:2]逆巍,結(jié)果變得不一樣了呢,3要比1和2晚一秒鐘哦莽使,剛好是3的睡眠時間锐极,那就說明3是在1和2完成之后開始的,這就是最大并發(fā)量的作用芳肌,設(shè)置的數(shù)字就是它允許開辟的最大操作數(shù)數(shù)灵再。
最后,我們發(fā)現(xiàn)除了剛開始在線程中任務(wù)順序執(zhí)行外亿笤,我們一直沒講串行翎迁,因為NSOperationQueue是并行隊列,我們想要串行就把最大并發(fā)量設(shè)置為1就可以了净薛,是不是簡單多了汪榔,不需要再怕不小心把串行并行寫錯了。
前面有句話看清楚哦肃拜,是最大任務(wù)數(shù)揍异,不是線程數(shù),看看下面這種情況你就清楚其中的區(qū)別了
//創(chuàng)建隊列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[queue setMaxConcurrentOperationCount:1];//設(shè)置最大并發(fā)量
// [block1 addDependency:block3];//設(shè)置block1依賴block3
block1.queuePriority = NSOperationQueuePriorityHigh;
[queue addOperation:block1];
[queue addOperation:block2];
[queue addOperation:block3];
輸出結(jié)果:
2017-08-25 16:44:21.361 NSOperation[3366:527290] 1----<NSThread: 0x6000002644c0>{number = 3, name = (null)}
2017-08-25 16:44:22.435 NSOperation[3366:527290] 4---<NSThread: 0x6000002644c0>{number = 3, name = (null)}
2017-08-25 16:44:22.435 NSOperation[3366:527274] 2----<NSThread: 0x600000267c40>{number = 4, name = (null)}
2017-08-25 16:44:23.482 NSOperation[3366:527274] 3----<NSThread: 0x600000267c40>{number = 4, name = (null)}
所以看時間我們能看到2和4是同一時間執(zhí)行的爆班,所以setMaxConcurrentOperationCount只能控制人物數(shù)量。
前面說了好多遍依賴關(guān)系辱姨,看一下什么是依賴關(guān)系吧
//創(chuàng)建隊列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[queue setMaxConcurrentOperationCount:2];//設(shè)置最大并發(fā)量
[block1 addDependency:block3];//設(shè)置block1依賴block3
[queue addOperation:block1];
[queue addOperation:block2];
[queue addOperation:block3];
輸出結(jié)果:
2017-08-25 16:00:12.378 NSOperation[3058:484669] 2----<NSThread: 0x608000267d40>{number = 3, name = (null)}
2017-08-25 16:00:12.378 NSOperation[3058:484671] 3----<NSThread: 0x608000267d80>{number = 4, name = (null)}
2017-08-25 16:00:13.380 NSOperation[3058:484668] 1----<NSThread: 0x60000007ae80>{number = 5, name = (null)}
誒柿菩,隊列不是先進(jìn)先出的嗎?為什么線程不夠的時候不是1和2先執(zhí)行呢雨涛?反而3先執(zhí)行了枢舶。噢,我們設(shè)置了1依賴3替久,所以當(dāng)cpu要殺1的時候凉泄,1說“不行,要殺我蚯根,先殺3”后众,所以先執(zhí)行了2和3,最后執(zhí)行1颅拦。
也可以不初始化NSBlockOperation蒂誉,直接在隊列中添加block操作哦。
//創(chuàng)建隊列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
// [queue setMaxConcurrentOperationCount:1];//設(shè)置最大并發(fā)量
// [block1 addDependency:block3];//設(shè)置block1依賴block3
block1.queuePriority = NSOperationQueuePriorityHigh;
[queue addOperation:block1];
[queue addOperation:block2];
[queue addOperation:block3];
[queue addOperationWithBlock:^{
sleep(1);
NSLog(@"5----%@",[NSThread currentThread]);
}];
輸出結(jié)果:
2017-08-25 16:41:30.151 NSOperation[3346:524342] 5----<NSThread: 0x60000007c980>{number = 3, name = (null)}
2017-08-25 16:41:30.222 NSOperation[3346:524343] 1----<NSThread: 0x60000007cb80>{number = 4, name = (null)}
2017-08-25 16:41:30.222 NSOperation[3346:524345] 2----<NSThread: 0x608000071340>{number = 5, name = (null)}
2017-08-25 16:41:30.222 NSOperation[3346:524360] 3----<NSThread: 0x600000075c00>{number = 6, name = (null)}
2017-08-25 16:41:30.222 NSOperation[3346:524359] 4---<NSThread: 0x60800007c780>{number = 7, name = (null)}
看結(jié)果和用NSBlockOperation沒什么區(qū)別哦
最后的最后距帅,關(guān)于iOS多線程就到這里了右锨,希望對后學(xué)者能有一些幫助,那將是我的榮幸碌秸。