你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發綜合 >> [精通Objective-C]三種實現並發編程的方式

[精通Objective-C]三種實現並發編程的方式

編輯:IOS開發綜合

線程

隱式創建並啟動線程

使用NSObject類中的performSelectorInBackground: withObject:方法可以隱式地創建和啟動用於執行對象中方法的新線程。該線程會作為後台次要進程立刻啟動,而當前進程會立刻返回。下面是一個使用該方法的實例:

首先創建一個繼承於NSObject類的,含有將由獨立線程執行的方法的類:

#import 

@interface ConcurrentProcessor : NSObject

@property(readwrite) BOOL isFinished;
@property(readonly) NSInteger computeResult;

-(void)computeTask:(id)data;

@end
#import "ConcurrentProcessor.h"

@interface ConcurrentProcessor()
@property(readwrite)NSInteger computeResult;
@end

@implementation ConcurrentProcessor
{
    NSString *computeID;     // @synchronized指令鎖定的唯一對象
    NSUInteger computeTasks; // 並行計算任務的計數
    NSLock *computeLock;     // 鎖對象
}

-(id)init{
    if ((self = [super init])) {
        _isFinished = NO;
        _computeResult = 0;
        computeLock = [NSLock new];
        computeID = @"1";
        computeTasks = 0;
    }
    return self;
}

-(void)computeTask:(id)data{
    NSAssert(([data isKindOfClass:[NSNumber class]]), @"Not an NSNumber instance");
    NSUInteger computations = [data unsignedIntegerValue];
    // 配置線程環境時應在線程入口點創建自動釋放池與異常處理程序
    @autoreleasepool {
        @try {
            if ([[NSThread currentThread] isCancelled]) {
                return;
            }
            // @synchronized指令括號內為唯一標識符,標識符保護的代碼塊對象只能同時被最多一個線程訪問
            @synchronized (computeID) {
                // 增加活動任務的計數
                computeTasks++;
            }

            // 獲取鎖並執行關鍵代碼部分中的計算操作
            [computeLock lock];
            if ([[NSThread currentThread] isCancelled]) {
                [computeLock unlock];
                return;
            }
            NSLog(@"Performing computations %lu",computations);
            for (int ii = 0; ii < computations; ii++) {
                self.computeResult = self.computeResult + 1;
            }
            // 完成計算並解除鎖
            [computeLock unlock];
            // 模擬額外的處理時間
            [NSThread sleepForTimeInterval:1.0];

            // 減少活動任務數,如果數量為0,則更新標志位
            @synchronized (computeID) {
                computeTasks--;
                if (!computeTasks) {
                    self.isFinished = YES;
                }
            }
        } @catch (NSException *exception) {}
    }
}
@end

最後在main.m中進行測試:

#import 
#import "ConcurrentProcessor.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        ConcurrentProcessor *processor = [ConcurrentProcessor new];
        // 隱式地創建並啟動3個線程
        [processor performSelectorInBackground:@selector(computeTask:) withObject:[NSNumber numberWithInt:5]];
        [processor performSelectorInBackground:@selector(computeTask:) withObject:[NSNumber numberWithInt:10]];
        [processor performSelectorInBackground:@selector(computeTask:) withObject:[NSNumber numberWithInt:20]];

        while (!processor.isFinished);
        NSLog(@"Computation result = %ld",processor.computeResult);
    }
    return 0;
}

運行結果:

2016-07-19 15:15:47.700 ConcurrentThreads[17142:161246] Performing computations 10
2016-07-19 15:15:47.700 ConcurrentThreads[17142:161247] Performing computations 20
2016-07-19 15:15:47.701 ConcurrentThreads[17142:161245] Performing computations 5
2016-07-19 15:15:48.774 ConcurrentThreads[17142:161222] Computation result = 35

顯示創建並啟動線程

可以直接使用NSThread類中的API顯示地創建並管理線程,與隱式方法等價。

#import 
#import "ConcurrentProcessor.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        ConcurrentProcessor *processor = [ConcurrentProcessor new];
        // 隱式地創建並啟動1個線程
        [processor performSelectorInBackground:@selector(computeTask:) withObject:[NSNumber numberWithInt:5]];

        // 顯示創建並啟動1個線程
        [NSThread detachNewThreadSelector:@selector(computeTask:) toTarget:processor withObject:[NSNumber numberWithInt:10]];

        // 顯示創建1個線程,需要用start方法手動啟動
        NSThread *computeThread = [[NSThread alloc] initWithTarget:processor selector:@selector(computeTask:) object:[NSNumber numberWithInt:20]];
        [computeThread setThreadPriority:0.5];
        [computeThread start];

        while (!processor.isFinished);
        NSLog(@"Computation result = %ld",processor.computeResult);
    }
    return 0;
}

運行結果:

2016-07-19 15:18:12.607 ConcurrentThreads[17269:162710] Performing computations 10
2016-07-19 15:18:12.608 ConcurrentThreads[17269:162711] Performing computations 20
2016-07-19 15:18:12.608 ConcurrentThreads[17269:162709] Performing computations 5
2016-07-19 15:18:13.681 ConcurrentThreads[17269:162677] Computation result = 35

操作和操作隊列

用操作類實現並發

NSOperation類(及其子類)的實例可以為單個任務封裝代碼,在處理非並發任務時,具體子類通常只需要重寫main方法。而在處理並發任務時,至少必須重寫start、isConcurrent、isExecuting、isFinishied方法。後面3個方法必須返回與操作狀態有關的值,而且這3個方法必須具備線程安全性。當這些值改變時,這些方法還必須生成適當的鍵值觀察(KVO)通知。下面是用NSOperation類的子類實現並發的示例。

首先創建NSOperation類的子類GreetingOperation類:

#import 

@interface GreetingOperation : NSOperation

@end
#import "GreetingOperation.h"

@implementation GreetingOperation
{
    BOOL finished;
    BOOL executing;
}

-(id)init{
    if ((self = [super init])) {
        executing = NO;
        finished = NO;
    }
    return self;
}

-(void)start{
    // 如果操作被取消了就返回結果
    if ([self isCancelled]) {
        // 修改isFinished方法返回值時必須發送KVO通知
        [self willChangeValueForKey:@"isFinished"];
        finished = YES;
        [self didChangeValueForKey:@"isFinished"];
        return;
    }

    // 修改isExecuting方法返回值時必須發送KVO通知
    [self willChangeValueForKey:@"isExecuting"];
    // 使用獨立線程執行main方法中的操作
    [NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
    executing = YES;
    [self didChangeValueForKey:@"isExecuting"];
}

-(void)main{
    @autoreleasepool {
        @try {
            if (![self isCancelled]) {
                NSLog(@"Hello,World");
                // 暫停,以便模擬執行任務的過程
                [NSThread sleepForTimeInterval:3.0];
                NSLog(@"Goodbye,World");
                [self willChangeValueForKey:@"isFinished"];
                [self willChangeValueForKey:@"isExecuting"];
                executing = NO;
                finished = YES;
                [self didChangeValueForKey:@"isExecuting"];
                [self didChangeValueForKey:@"isFinished"];
            }
        } @catch (NSException *exception) {
        }
    }
}

-(BOOL)isConcurrent{
    return YES;
}

-(BOOL)isExecuting{
    return executing;
}

-(BOOL)isFinished{
    return finished;
}
@end

最後在main.m中進行測試:

#import 
#import "GreetingOperation.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        GreetingOperation *greetingOp = [GreetingOperation new];
        [greetingOp start];
        GreetingOperation *greetingOp2 = [GreetingOperation new];
        [greetingOp2 start];
        // 如果沒有這行代碼,main函數會在操作中的main方法執行完成之前就返回
        while (![greetingOp isFinished] || ![greetingOp2 isFinished]);
    }
    return 0;
}

運行結果:

2016-07-19 16:15:45.642 GreetingOperation[20255:195915] Hello,World
2016-07-19 16:15:45.642 GreetingOperation[20255:195914] Hello,World
2016-07-19 16:15:48.647 GreetingOperation[20255:195914] Goodbye,World
2016-07-19 16:15:48.647 GreetingOperation[20255:195915] Goodbye,World

用操作隊列實現並發

操作隊列是一種提供並發執行操作能力的機制。可以將NSOperation類或其子類的對象添加到NSOperationQueue對象中。下面是操作隊列的使用示例。

首先創建一個NSOperation類的子類ConcurrentProcessor類:

#import 

@interface ConcurrentProcessor : NSOperation

@property(readonly) NSUInteger computations;

-(id)initWithData:(NSInteger *)result computations:(NSUInteger)computations;

@end
#import "ConcurrentProcessor.h"

@implementation ConcurrentProcessor
{
    NSInteger *computeResult;
}

-(id)initWithData:(NSInteger *)result computations:(NSUInteger)computations{
    if ((self = [super init])) {
        _computations = computations;
        computeResult = result;
    }
    return self;
}

-(void)main{
    @autoreleasepool {
        @try {
            if (![self isCancelled]) {
                NSLog(@"Performing %ld computations", self.computations);
                [NSThread sleepForTimeInterval:1.0];
                for (int ii = 0; ii < self.computations; ii++) {
                    // computeResult為操作數的地址
                    *computeResult = *computeResult + 1;
                }
            }
        } @catch (NSException *exception) {}
    }
}
@end

最後在main.m中進行測試:

#import 
#import "ConcurrentProcessor.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 創建操作隊列類對象
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        NSInteger result = 0;

        // 創建3個操作類對象
        ConcurrentProcessor *proc1 = [[ConcurrentProcessor alloc] initWithData:&result computations:5];
        ConcurrentProcessor *proc2 = [[ConcurrentProcessor alloc] initWithData:&result computations:10];
        ConcurrentProcessor *proc3 = [[ConcurrentProcessor alloc] initWithData:&result computations:20];

        NSArray *operations = @[proc1, proc2, proc3];

        // 添加操作間的依賴性,proc1必須先於proc2
        [proc2 addDependency:proc1];

        // 將操作對象添加到操作隊列中執行
        [queue addOperations:operations waitUntilFinished:NO];

        // 等待,當所有操作完成時顯示結果
        [queue waitUntilAllOperationsAreFinished];
        NSLog(@"Computation result = %ld", result);
    }
    return 0;
}

運行結果:

2016-07-19 16:20:28.006 ConcurrentOperations[20508:198875] Performing 5 computations
2016-07-19 16:20:28.006 ConcurrentOperations[20508:198877] Performing 20 computations
2016-07-19 16:20:29.011 ConcurrentOperations[20508:198877] Performing 10 computations
2016-07-19 16:20:30.016 ConcurrentOperations[20508:198815] Computation result = 32

可以看到,proc1與proc3是並發執行的,而proc2必須等proc1執行完成後才能執行。

分派隊列GCD

Grand Central Dispatch(GCD)是一個集合,它含有語言特性、基於C語言API,以及支持使用分派隊列執行任務的系統增強功能。下面是使用GCD的示例。

GCD的使用非常方便,不需要創建其他類:

#import 
typedef void (^ComputeTask)(void);

// 函數返回用於累加的語句塊
ComputeTask getComputeTask(NSInteger *result, NSUInteger computations){
    NSInteger *computeResult = result;
    return ^{
        [NSThread sleepForTimeInterval:1.0];
        NSLog(@"Performing %ld computations", computations);
        for (int ii = 0; ii < computations; ii++) {
            *computeResult = *computeResult + 1;
        }
    };
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSInteger computeResult;
        NSInteger computeResult2;

        // 創建順序隊列和分組
        dispatch_queue_t serialQueue = dispatch_queue_create("MySerialQueue", DISPATCH_QUEUE_SERIAL);
        dispatch_queue_t serialQueue2 = dispatch_queue_create("MySerialQueue2", DISPATCH_QUEUE_SERIAL);
        dispatch_group_t group = dispatch_group_create();

        // 分別向兩個隊列中添加任務
        dispatch_group_async(group, serialQueue, getComputeTask(&computeResult, 1));
        dispatch_group_async(group, serialQueue, getComputeTask(&computeResult, 3));
        dispatch_group_async(group, serialQueue, getComputeTask(&computeResult, 5));

        dispatch_group_async(group, serialQueue2, getComputeTask(&computeResult2, 2));
        dispatch_group_async(group, serialQueue2, getComputeTask(&computeResult2, 4));
        dispatch_group_async(group, serialQueue2, getComputeTask(&computeResult2, 6));

        // 等待,當分組中的所有任務都完成時顯示結果
        dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
        NSLog(@"Computation result = %ld", computeResult);
        NSLog(@"Computation result2 = %ld", computeResult2);
    }
    return 0;
}

運行結果:

2016-07-19 16:38:35.074 ConcurrentDispatch[21460:209103] Performing 1 computations
2016-07-19 16:38:35.074 ConcurrentDispatch[21460:209104] Performing 2 computations
2016-07-19 16:38:36.080 ConcurrentDispatch[21460:209103] Performing 3 computations
2016-07-19 16:38:36.080 ConcurrentDispatch[21460:209104] Performing 4 computations
2016-07-19 16:38:37.083 ConcurrentDispatch[21460:209104] Performing 6 computations
2016-07-19 16:38:37.083 ConcurrentDispatch[21460:209103] Performing 5 computations
2016-07-19 16:38:37.083 ConcurrentDispatch[21460:209079] Computation result = 9
2016-07-19 16:38:37.083 ConcurrentDispatch[21460:209079] Computation result2 = 12

分析結果可以看出,2個隊列是並發執行的,而同一個隊列中的任務是異步執行的。

三種方式的比較

操作隊列和GCD提供了異步、基於隊列的機制,不僅替代了低級線程管理,並且與基於線程的編程方式相比,最大化了系統利用率和效率。操作隊列是基於對象的,與GCD相比,會有更多的系統開銷,占用更多的資源,不過它的面向對象的高級API與Objective-C平台一致,因而更易於使用,而且支持復雜的操作間的依賴關系、基於約束的執行和操作對象管理。

GCD提供了低級API(基於C語言)並且是輕量級的。所以性能更好,且需要使用的代碼更少。

由於操作隊列和GCD都不處理實時約束,所以在實時系統中,線程仍舊是合適的並發編程機制。

除此之外,Foundation框架還提供了多種執行異步處理的API,如NSFileHandle類和NSPort類,在可以使用異步API時應該盡量使用。

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved