1 自定義非并行的 NSOperation
前文介紹過 NSInvocationOperation 和 NSBlockOperation 都繼承自NSOperation類。
我們亦可以通過繼承 NSOperation 類倔监,來自定義非并行的 Operation。
@interface VinnyOperation : NSOperation
@end
頭文件很簡單,只需要繼承 NSOperation ,可根據(jù)實際需要決定是否需要自定義init
方法够话。而且僅僅需要自定義main
方法禁熏,將需要執(zhí)行的操作寫在main
方法中。
#import "VinnyOperation.h"
@implementation VinnyOperation
- (void)main
{
NSLog(@"main begin");
@try {
// 提供一個變量標識明也,來表示需要執(zhí)行的操作是否完成了,當然惯裕,沒開始執(zhí)行之前温数,為NO
BOOL taskIsFinished = NO;
// while 保證:只有當沒有執(zhí)行完成和沒有被取消,才執(zhí)行自定義的相應(yīng)操作
while (taskIsFinished == NO && [self isCancelled] == NO){
// 自定義的操作
sleep(10); // 睡眠模擬耗時操作
NSLog(@"currentThread = %@", [NSThread currentThread]);
NSLog(@"mainThread = %@", [NSThread mainThread]);
// 這里相應(yīng)的操作都已經(jīng)完成蜻势,后面就是要通知KVO我們的操作完成了撑刺。
taskIsFinished = YES;
}
}
@catch (NSException * e) {
NSLog(@"Exception %@", e);
}
NSLog(@"main end");
}
@end
使用的時候也非常簡單
VinnyOperation *op = [[VinnyOperation alloc] init];
NSLog(@"start before");
[op start];
NSLog(@"start after");
看一下控制臺打印的結(jié)果
2015-12-29 19:21:56.895 test[67010:50712745] start before
2015-12-29 19:21:56.896 test[67010:50712745] main begin
2015-12-29 19:22:06.900 test[67010:50712745] currentThread = <NSThread: 0x7fc560d044d0>{number = 1, name = main}
2015-12-29 19:22:06.900 test[67010:50712745] mainThread = <NSThread: 0x7fc560d044d0>{number = 1, name = main}
2015-12-29 19:22:06.900 test[67010:50712745] main end
2015-12-29 19:22:06.900 test[67010:50712745] start after
可以看出是main
方法是非并行的,而且執(zhí)行的操作與調(diào)用start
是在同一個線程中握玛。
2 自定義并行的 NSOperation
自定義并行的 NSOperation 則要復(fù)雜一點够傍,首先必須重寫以下幾個方法:
-
start
: 所有并行的 Operations 都必須重寫這個方法,然后在你想要執(zhí)行的線程中手動調(diào)用這個方法挠铲。注意:任何時候都不能調(diào)用父類的start
方法王带。 -
main
: 在start
方法中調(diào)用,但是注意要定義獨立的自動釋放池與別的線程區(qū)分開市殷。 -
isExecuting
: 是否執(zhí)行中愕撰,需要實現(xiàn)KVO通知機制。 -
isFinished
: 是否已完成醋寝,需要實現(xiàn)KVO通知機制搞挣。 -
isConcurrent
: 該方法現(xiàn)在已經(jīng)由isAsynchronous
方法代替,并且 NSOperationQueue 也已經(jīng)忽略這個方法的值音羞。 -
isAsynchronous
: 該方法默認返回 NO 囱桨,表示非并發(fā)執(zhí)行。并發(fā)執(zhí)行需要自定義并且返回 YES嗅绰。后面會根據(jù)這個返回值來決定是否并發(fā)舍肠。
與非并發(fā)操作不同的是搀继,需要另外自定義一個方法來執(zhí)行操作而不是直接調(diào)用start
方法
@interface MyOperation : NSOperation
- (BOOL)performOperation:(NSOperation*)anOp; // 執(zhí)行操作調(diào)用這個方法
@end
實現(xiàn)其中的必要方法:
#import "MyOperation.h"
@interface MyOperation () {
BOOL executing; // 執(zhí)行中
BOOL finished; // 已完成
}
@end
@implementation MyOperation
- (id)init {
self = [super init];
if (self) {
executing = NO;
finished = NO;
}
return self;
}
- (void)start {
// Always check for cancellation before launching the task.
if ([self isCancelled])
{
// Must move the operation to the finished state if it is canceled.
[self willChangeValueForKey:@"isFinished"];
finished = YES;
[self didChangeValueForKey:@"isFinished"];
return;
}
// If the operation is not canceled, begin executing the task.
[self willChangeValueForKey:@"isExecuting"];
[NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
executing = YES;
[self didChangeValueForKey:@"isExecuting"];
}
- (void)main {
NSLog(@"main begin");
@try {
// 必須為自定義的 operation 提供 autorelease pool,因為 operation 完成后需要銷毀翠语。
@autoreleasepool {
// 提供一個變量標識叽躯,來表示需要執(zhí)行的操作是否完成了,當然肌括,沒開始執(zhí)行之前点骑,為NO
BOOL taskIsFinished = NO;
// while 保證:只有當沒有執(zhí)行完成和沒有被取消,才執(zhí)行自定義的相應(yīng)操作
while (taskIsFinished == NO && [self isCancelled] == NO){
// 自定義的操作
//sleep(10); // 睡眠模擬耗時操作
NSLog(@"currentThread = %@", [NSThread currentThread]);
NSLog(@"mainThread = %@", [NSThread mainThread]);
// 這里相應(yīng)的操作都已經(jīng)完成谍夭,后面就是要通知KVO我們的操作完成了黑滴。
taskIsFinished = YES;
}
[self completeOperation];
}
}
@catch (NSException * e) {
NSLog(@"Exception %@", e);
}
NSLog(@"main end");
}
- (void)completeOperation {
[self willChangeValueForKey:@"isFinished"];
[self willChangeValueForKey:@"isExecuting"];
executing = NO;
finished = YES;
[self didChangeValueForKey:@"isExecuting"];
[self didChangeValueForKey:@"isFinished"];
}
// 已經(jīng)棄用,使用 isAsynchronous 代替
//- (BOOL)isConcurrent {
// return NO;
//}
- (BOOL)isAsynchronous {
return YES;
}
- (BOOL)isExecuting {
return executing;
}
- (BOOL)isFinished {
return finished;
}
// 執(zhí)行操作
- (BOOL)performOperation:(NSOperation*)anOp
{
BOOL ranIt = NO;
if ([anOp isReady] && ![anOp isCancelled])
{
if (![anOp isAsynchronous]) {
[anOp start];
}
else {
[NSThread detachNewThreadSelector:@selector(start)
toTarget:anOp withObject:nil];
}
ranIt = YES;
}
else if ([anOp isCancelled])
{
// If it was canceled before it was started,
// move the operation to the finished state.
[self willChangeValueForKey:@"isFinished"];
[self willChangeValueForKey:@"isExecuting"];
executing = NO;
finished = YES;
[self didChangeValueForKey:@"isExecuting"];
[self didChangeValueForKey:@"isFinished"];
// Set ranIt to YES to prevent the operation from
// being passed to this method again in the future.
ranIt = YES;
}
return ranIt;
}
使用這個 Operation 如下:
MyOperation *op = [[MyOperation alloc] init];
NSLog(@"start before");
[op performOperation:op];
NSLog(@"start after");
看一下控制臺打印的結(jié)果
2015-12-29 20:01:53.130 test[27083:51105353] start before
2015-12-29 20:01:53.131 test[27083:51105353] start after
2015-12-29 20:01:53.131 test[27083:51105608] main begin
2015-12-29 20:01:53.131 test[27083:51105608] currentThread = <NSThread: 0x7ff148d976d0>{number = 3, name = (null)}
2015-12-29 20:01:53.131 test[27083:51105608] mainThread = <NSThread: 0x7ff148e01250>{number = 1, name = (null)}
2015-12-29 20:01:53.131 test[27083:51105608] main end
可以看到這個操作是并發(fā)執(zhí)行紧索,并且是一個獨立的線程袁辈。
3 總結(jié)
- 如果需要自定義并發(fā)執(zhí)行的 Operation,必須重寫
start
珠漂、main
晚缩、isExecuting
、isFinished
甘磨、isAsynchronous
方法。 - 在 operation 的 main 方法里面眯停,必須提供 autorelease pool,因為你的 operation 完成后需要銷毀济舆。
- 一旦你的 operation 開始了,必須通過 KVO莺债,告訴所有的監(jiān)聽者滋觉,現(xiàn)在該operation的執(zhí)行狀態(tài)。
- 調(diào)用時齐邦,如果需要并發(fā)執(zhí)行 Operation椎侠,必須調(diào)用
performOperation:
方法,當然措拇,也可以改為自定義其他方法或者直接在start
方法添加多線程調(diào)用我纪。 - 對于自定義的 Operation 類,如果不需要并發(fā)執(zhí)行丐吓,可以直接調(diào)用
start
方法浅悉。
4 尾巴
剛開始看 NSOperation 的文檔沒搞明白怎么自定義多線程的操作,文檔里只是說需要自定義 isExecuting
券犁、isFinished
术健、isConcurrent
、isAsynchronous
這四個方法粘衬,然后說根據(jù) isAsynchronous
的返回值來判斷是否多線程荞估,我以為只要重寫這個方法的時候返回 YES 就行了咳促,NSOperation 就會自動多線程執(zhí)行了,但是測試發(fā)現(xiàn)卻不是這樣的勘伺,多線程還得自己去創(chuàng)建再使用跪腹。
再有就是自定義多線程的 NSOperation 時,還必須自己管理其中表示狀態(tài)的成員娇昙,而且需要實現(xiàn) KVO 機制尺迂,使得這個過程復(fù)雜化了。
其實在大多數(shù)時候我們并不會直接去使用自定義的 NSOperation 冒掌,如果操作不復(fù)雜噪裕,可以直接使用 NSInvocationOperation 和 NSBlockOperation 這兩個子類,那就直接用了股毫,如果復(fù)雜一些膳音,必須自定義又需要多線程,通常都會用 NSOperationQueue 來包裝铃诬,使用起來更加簡潔祭陷,下一篇會詳細介紹 NSOperationQueue 的使用。