IOS_多線程_售票 – iPhone手機開發技術文章 iPhone軟體開發教學課程

H:/1007/01_多線程_大任務_MainViewController.m

//  MainViewController.m
//  多線程-01.大任務
//  Created by apple on 13-10-7.
#import "MainViewController.h"
@interface MainViewController ()
@property (weak, nonatomic) UIImageView *imageView;
@end
@implementation MainViewController
/*
 NSObject多線程方法
 
 1. [NSThread currentThread] 可以返回當前運行的線程
    num = 1 說明是主線程
    在任何多線程技術中(NSThread,NSOperation,GCD),
	均可以使用此方法,查看當前的線程情況。
 
 2. 新建後臺線程,調度任務
    [self performSelectorInBackground:@selector(bigTask)
										withObject:nil]
 
    使用performSelectorInBackground是可以修改UI的,
	但是,強烈不建議如此使用。
 
 3. 更新界面
    使用performSelectorOnMainThread可以在主線程上執行任務。
	絕大多數最後一個參數是YES,即等待,直到它執行完
    提示:NSObject對象均可以調用此方法。
 
 4. 內存管理
    線程任務要包在@autoreleasepool(自動釋放池)中,
				否則容易引起內存泄露,而且非常難發現。
 */
- (void)viewDidLoad
{
    [super viewDidLoad];
	// 創建第1個按鈕,並為其添加點擊事件
    UIButton *btn1 = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    [btn1 setFrame:CGRectMake(110, 100, 100, 40)];
    [btn1 setTitle:@"大任務" forState:UIControlStateNormal];
    [btn1 addTarget:self action:@selector(btnClick_1)
						  forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:btn1];
	// 創建第2個按鈕,並為其添加點擊事件
    UIButton *btn2 = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    [btn2 setFrame:CGRectMake(110, 200, 100, 40)];
    [btn2 setTitle:@"小任務" forState:UIControlStateNormal];
    [btn2 addTarget:self action:@selector(btnClick_2) 
						  forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:btn2];
    NSLog(@"%@", [NSThread currentThread]);
	// 當前主線程中設置頭像
    UIImageView *imageView = [[UIImageView alloc]initWithFrame:
										CGRectMake(110, 260, 100, 100)];
    UIImage *image = [UIImage imageNamed:@"頭像1.png"];
    [imageView setImage:image];
    [self.view addSubview:imageView];
    self.imageView = imageView;
}
// 響應按鈕1的點擊事件
- (void)btnClick_1
{
    // 在後臺調用耗時操作
    // performSelectorInBackground會新建一個後臺線程,
	// 並在該線程中執行調用的方法
    [self performSelectorInBackground:@selector(btn_1_bigTask)
										withObject:nil];
    NSLog(@"大任務按鈕: %@", [NSThread currentThread]);
}
#pragma mark 耗時操作
- (void)btn_1_bigTask
{
    @autoreleasepool {        
        for (NSInteger i = 0; i < 300; i++) {
            NSString *str = [NSString stringWithFormat:@"i = %i", i];
            NSLog(@"%@", str);
        }
        NSLog(@"大任務 - %@", [NSThread currentThread]);
        UIImage *image = [UIImage imageNamed:@"頭像2.png"];
        // 在主線程中修改self.imageView的image
		// 調用self即當前控制器的方法,在主線程中設置頭像
        [self performSelectorOnMainThread:@selector(changeImage:) 
							  withObject:image waitUntilDone:YES];
		// 直接調用self.imageView自己的setImage:方法,並傳遞參數image
        [self.imageView performSelectorOnMainThread:@selector(setImage:)
									withObject:image waitUntilDone:YES];
    }
}
// 自定義方法,設置頭像
- (void)changeImage:(UIImage *)image
{
    NSLog(@"修改頭像 %@", [NSThread currentThread]);
    [self.imageView setImage:image];
}

// 響應按鈕2的點擊,調用自定義方法
- (void)btnClick_2
{
    NSLog(@"小任務按鈕:%@", [NSThread currentThread]);
    [self btn_2_smallTask];
}
// 自定義方法
- (void)btn_2_smallTask
{
    NSString *str = nil;
    for (NSInteger i = 0; i < 30000; i++) {
        str = [NSString stringWithFormat:@"i = %i", i];
    }
    NSLog(@"%@", str);
    NSLog(@"小任務 - %@", [NSThread currentThread]);
}
@end

H:/1007/02_多線程_加載圖片_MainViewController.m

//  MainViewController.m
//  多線程-02.加載圖片
//  Created by apple on 13-10-7.
#import "MainViewController.h"
@interface MainViewController ()
// 圖像集合
@property (strong, nonatomic) NSSet *imageViewSet;
// 定義操作隊列,NSOperationQueue
@property (strong, nonatomic) NSOperationQueue *operationQueue;
@end
@implementation MainViewController
/*
 1. NSThread
    1> 類方法 detachNewThreadSelector
        直接啟動線程,調用選擇器方法
 
    2> 成員方法 initWithTarget
        需要使用start方法,才能啟動實例化出來的線程
 
    優點:簡單
    缺點:
        * 控制線程的生命周期比較困難
        * 控制並發線程數
		* 存在死鎖隱患
        * 先後順序困難
        例如:下載圖片(後臺線程) -> 濾鏡美化(後臺線程) -> 更新UI(主線程)
 
 2. NSOperation
    1> NSInvocationOperation
    2> NSBlockOperation
 
    定義完Operation之後,將操作添加到NSOperationQueue即可啟動線程,執行任務
 
    使用:
    1>  setMaxConcurrentOperationCount 可以控制同時並發的線程數量
    2>  addDependency 可以指定線程之間的依賴關系,
					  從而達到控制線程執行順序的目的,如先下載,再渲染圖片
 
    提示:
    要更新UI,需要使用[NSOperationQueue mainQueue]addOperationWithBlock:
    在主操作隊列中更新界面
 
 3. GCD
     1) 全局global隊列,如果是同步,則不開線程,在主隊列中執行
     方法:dispatch_get_global_queue(獲取全局隊列)
     優先級:DISPATCH_QUEUE_PRIORITY_DEFAULT
     所有任務是並發(異步)執行的
     
     2) 串行隊列  必須開一條新線程,全是順序執行的
     方法:dispatch_queue_create(創建串行隊列,串行隊列不能夠獲取)
     提示:隊列名稱可以隨意,不過不要使用@
     
     3) 主隊列
     主線程隊列
     方法:dispatch_get_main_queue(獲取主隊列)
 
    在gcd中,同步還是異步取決於任務執行所在的隊列,更方法名沒有關系
 
    具體同步、異步與三個隊列之間的關系,一定要反復測試,體會!
	
  4.全局隊列(可能會開啟多條線程,如果是同步方法,則不開線程,隻在主線程裡)
		dispatch_queue_t queue = dispatch_get_global_queue(
						DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
		串行隊列(隻可能會開啟一條線程)
		dispatch_queue_t queue = dispatch_queue_create("myQueue", 
										DISPATCH_QUEUE_SERIAL);
		主隊列
		dispatch_get_main_queue();

	異步操作
		dispatch_async 異步方法無法確定任務的執行順序
		

	同步操作
		dispatch_sync 同步方法會依次執行,能夠決定任務的執行順序
		同步操作與隊列無關,所有的隊列的同步,都是順序執行的
		更新界面UI時,最好使用同步方法

	GCD的優點:
		充分利用多核
		所有的多線程代碼集中在一起,便於維護
		GCD中無需使用@autoreleasepool

		如果要順序執行,可以使用dispatch_sync同步方法
		dispatch_async無法確定任務的執行順序
		調用主線程隊列任務更新UI時,最好使用同步方法
	單例:
		保證在內存中永遠隻有類的單個實例

		建立方法:
		1,聲明一個靜態成員變量,記錄唯一實例
		2,重寫allocWithZone方法
			allocWithZone方法是對象分配內存空間時,最終會調用的方法,
			重寫該方法,保證隻會分配一個內存空間
		3,建立sharedXXX類方法,便於其他類訪問
 */
- (void)viewDidLoad
{
    [super viewDidLoad];
	// 自定義方法,設置UI界面
    [self setupUI];
    // 實例化操作隊列,NSOperationQueue
    self.operationQueue = [[NSOperationQueue alloc]init];
}
// 自定義方法,設置UI界面
- (void)setupUI
{
    // 實例化圖像視圖集合
    NSMutableSet *imageSet = [NSMutableSet setWithCapacity:28];
    // 雖然隻有17張圖片,但是每行顯示4張,一共顯示7行(重復使用)
	// 每張小圖片寬 80,高 50
    NSInteger w = 80;
    NSInteger h = 50;
	// 弄出7行 X 4列圖片
    for (NSInteger row = 0; row < 7; row++) {
        for (NSInteger col = 0; col < 4; col++) {
            // 計算圖片的位置
			// 第幾列 即所在的列數 決定瞭x坐標
			// 第幾行 即所在的行數 決定瞭y坐標
            NSInteger x = col * w;
            NSInteger y = row * h;
            UIImageView *imageView = [[UIImageView alloc]initWithFrame:CGRectMake(x, y, w, h)];
            /* 下面是不使用多線程,設置圖片
            NSInteger index = (row * 4 + col) % 17 + 1;
            NSString *imageName = [NSString stringWithFormat:@"NatGeo%02d.png", index];
            UIImage *image = [UIImage imageNamed:imageName];
            [imageView setImage:image];
			*/
			// 因為要使用多線程設置圖片,所以這隻添加imageView,並不設置圖片
            [self.view addSubview:imageView];
            [imageSet addObject:imageView];
        }
    }
	// 成員變量 NSSet,記住所有的imageSet,方便多線程方法中為其設置圖片
    self.imageViewSet = imageSet;
    // 添加按鈕
    UIButton *btn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    [btn setFrame:CGRectMake(110, 385, 100, 40)];
    [btn setTitle:@"設置圖片" forState:UIControlStateNormal];
    [btn addTarget:self action:@selector(click) 
							forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:btn];
}
#pragma mark 按鈕的監聽方法,設置圖片
- (void)click
{
	// 調用GCD 設置圖片
    [self gcdLoad];
}
// 多線程之NSThread,類方法detach線程,或者alloc創建線程,並手動start線程
- (void)threadLoad
{
	// 遍歷成員變量NSSet 為每一個imageView設置圖片
    for (UIImageView *imageView in self.imageViewSet) {        
        // 方式1,類方法,detach分離,新建28條線程,並自動調用threadLoadImage方法
        [NSThread detachNewThreadSelector:@selector(threadLoadImage:)
									toTarget:self withObject:imageView];
        // 方式2,alloc創建線程,並需要手動開啟線程,才會執行threadLoadImage方法
        NSThread *thread = [[NSThread alloc]initWithTarget:self
					selector:@selector(threadLoadImage:) object:imageView];
		// alloc出來的線程,必須手動start才有效			
        [thread start];
    }
}
// NSThread線程的任務:加載圖片方法
- (void)threadLoadImage:(UIImageView *)imageView
{
	// 設置imageView的圖片
    // 線程方法一定要加autoreleasepool
    @autoreleasepool {
        // 28條線程,都在一開始睡眠1秒 
        [NSThread sleepForTimeInterval:1.0f];        
        NSInteger index = arc4random_uniform(17) + 1;
        NSString *imageName = [NSString stringWithFormat:@"NatGeo%02d.png",
															index];
        UIImage *image = [UIImage imageNamed:imageName];
        // imageView直接在主線程上執行setImage方法,更新UI,參數就是image
        [imageView performSelectorOnMainThread:@selector(setImage:)
										withObject:image waitUntilDone:YES];
    }
}
#pragma mark NSOperation方法
// 多線程之2,NSXxxOperation依賴關系演示
- (void)operationDemo
{
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"下載 %@", [NSThread currentThread]);
    }];
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"美化 %@", [NSThread currentThread]);
    }];
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"更新 %@", [NSThread currentThread]);
    }];
    // Dependency依賴,1完成,再執行2,最後執行3
    // 提示:依賴關系可以多重依賴
    // 註意:不要建立循環依賴,嵌套依賴
    [op2 addDependency:op1];
    [op3 addDependency:op2];
	// 將NSOperation添加到成員變量NSOperationQueue,操作隊列中 
    [self.operationQueue addOperation:op3];
    [self.operationQueue addOperation:op1];
    [self.operationQueue addOperation:op2];
}
// 多線程之2,NSBlockOperation
- (void)operationBlockLoad
{
	// 遍歷成員變量NSSet,為每一個imageView設置圖片
    for (UIImageView *imageView in self.imageViewSet) {
		// 創建一個操作 NSOperation
        NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
			// 執行 當前控制器的 自定義方法,,operationLoadImage
            [self operationLoadImage:imageView];
        }];
        // 必須將操作添加到操作隊列之中,才有效,註:會自動啟動~
        [self.operationQueue addOperation:op];
    }
}
// 多線程之2,NSInvocationOperation
-(void)operationLoad
{
    // NSOperationQueue優點:可以設置同時並發執行的線程的數量
	// 即使開瞭20條線程,同一時刻也隻會執行其中的4條線程
    [self.operationQueue setMaxConcurrentOperationCount:4];
	// 遍歷成員變量NSSet,為每一個imageView設置圖片
    for (UIImageView *imageView in self.imageViewSet) {
		// 創建一個操作 NSOperation
        NSInvocationOperation *op = [[NSInvocationOperation alloc]
				initWithTarget:self selector:@selector(operationLoadImage:) 
				object:imageView];
        // 如果直接調用operation的start方法,是在主線程隊列上運行的,不會開啟新的線程
        [op start];
        // 必須將操作Operation添加到操作隊列,才會開啟新的線程執行任務,註:自動開啟
        [self.operationQueue addOperation:op];
    }
}
// 多線程之2,NSXxxOperation任務的具體代碼,加載圖片方法
- (void)operationLoadImage:(UIImageView *)imageView
{
    // 線程方法一定要加autoreleasepool
    @autoreleasepool {
        // 設置imageView的內容
		// 模擬網絡延時
        [NSThread sleepForTimeInterval:1.0f];
        NSInteger index = arc4random_uniform(17) + 1;
        NSString *imageName = [NSString stringWithFormat:@"NatGeo%02d.png",
															index];
        UIImage *image = [UIImage imageNamed:imageName];
        // 必須在主線程隊列上更新UI,必須使用NSOperationQueue mainQueue
        [[NSOperationQueue mainQueue]addOperationWithBlock:^{
            [imageView setImage:image];
        }];
    }
}
// 多線程之3,GCD,抽象程度最高,用戶主要精力隻要放在業務上即可
- (void)gcdDemo
{
    /*
     1. 全局global隊列
     方法:dispatch_get_global_queue(獲取全局隊列)
     優先級:DISPATCH_QUEUE_PRIORITY_DEFAULT
     所有任務是並發(異步)執行的,會創建N條線程
     
     2. 串行隊列
     方法:dispatch_queue_create(創建串行隊列,串行隊列不能夠獲取)
     提示:隊列名稱可以隨意,不過不要使用@
	 任務是同步,隻會創建一個線程
     
     3. 主隊列
     主線程隊列
     方法:dispatch_get_main_queue(獲取主隊列)
     
     在gcd中,同步還是異步取決於任務執行所在的隊列,更方法名沒有關系
		如果是全局隊列就是異步,會創建多條線程
		如果是自創建的隊列,則是串行,同步的,且隻會創建一條線程
	 
	 派發dispatch_
     異步async不執行,並發執行
     優先級priority,使用默認優先級即可
     1. 在全局隊列中調用異步任務
     1) 全局隊列,全局調度隊列是有系統負責的,開發時不用考慮並發線程數量問題
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
     串行隊列,需要創建,不能夠get
     DISPATCH_QUEUE_SERIAL串行隊列
     
    dispatch_queue_t queue = dispatch_queue_create("myQueue",
									  DISPATCH_QUEUE_SERIAL);
	GCD是基於C語言的框架
	工作原理:
	讓程序平行排隊的特定任務,根據可用的處理資源,安排它們在任何可用的處理器上執行任務
	要執行的任務可以是一個函數或者一個block
	底層是通過線程實現的,不過程序員可以不必關註實現的細節
	GCD中的FIFO隊列稱為dispatch queue,可以保證先進來的任務先得到執行
	dispatch_notify 可以實現監聽一組任務是否完成,完成後得到通知
	GCD隊列:
	全局隊列:所有添加到全局隊列中的任務都是並發執行的
	串行隊列:所有添加到串行隊列中的任務都是順序執行的
	主隊列:所有添加到主隊列中的任務都是在主線程中執行的
*/
    // 主隊列,即在主線程上運行
    dispatch_queue_t queue = dispatch_get_main_queue();
    // 2) 在主隊列中沒有異步和同步,所有都是在主線程中完成的 
    dispatch_async(queue, ^{
        NSLog(@"任務1 %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"任務2 %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"任務3 %@", [NSThread currentThread]);
    });
}
// 多線程之3, GCD加載圖像,具體的核心業務,為NSSet中每個imageView設置圖片
- (void)gcdLoad
{
    // 1) 獲取全局隊列,可以執行異步
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
	// 遍歷成員變量NSSet,為每一個imageView設置圖片
    for (UIImageView *imageView in self.imageViewSet) {
        // 2) 在全局隊列上執行異步方法,開啟N條線程,加載並設置圖像
        dispatch_async(queue, ^{
            NSLog(@"GCD- %@", [NSThread currentThread]);
            NSInteger index = arc4random_uniform(17) + 1;
            NSString *imageName = [NSString stringWithFormat:@"NatGeo%02d.png",
																index];
            // 通常此異步方法裡面獲取的image是網絡上的
            UIImage *image = [UIImage imageNamed:imageName];
            // 3) 最後必須在主線程隊列中設置圖片,異步
            dispatch_async(dispatch_get_main_queue(), ^{
                NSLog(@"更新圖片- %@", [NSThread currentThread]);
                [imageView setImage:image];
            });
        });
    }
}
@end

H:/1007/03_多線程_賣票_MainViewController.m

//  MainViewController.m
//  多線程-03.賣票
//  Created by apple on 13-10-7.
/*
系統預設
	共有30張票可以銷售(開發時可以少一些,專註實現)
	售票工作由兩個線程並發進行
	沒有可出售票據時,線程工作停止
	兩個線程的執行時間不同,模擬售票人員效率不同
	使用一個多行文本框公告售票進度(主線程更新UI)
線程工作安排
	主線程:負責更新UI
	線程1:模擬第1名賣票員
	線程2:模擬第2名賣票員
	兩個線程幾乎同時開始賣票
*/
#import "MainViewController.h"
#import "Ticket.h"
@interface MainViewController ()
@property (weak, nonatomic) UITextView *textView;
@property (strong, nonatomic) NSOperationQueue *operationQueue;
@end
@implementation MainViewController
- (void)viewDidLoad
{
    [super viewDidLoad];
    // 建立多行文本框
    UITextView *textView = [[UITextView alloc]initWithFrame:self.view.bounds];
    // 禁止編輯
    [textView setEditable:NO];
    [self.view addSubview:textView];
    self.textView = textView;
    // 預設可以賣30張票
    [Ticket sharedTicket].tickets = 30;
    // 實例化操作隊列,NSOperationQueue
    self.operationQueue = [[NSOperationQueue alloc]init];
    // 調用自定義方法,開始賣票
    [self operationSales];
}
// 多線程賣票之一:NSOperation
- (void)operationSales
{
    // 提示,operation中沒有群組任務完成通知功能
    // 設置操作隊列,最大同時並發線程數:兩個線程賣票
    [self.operationQueue setMaxConcurrentOperationCount:2];
    [self.operationQueue addOperationWithBlock:^{
        [self operationSaleTicketWithName:@"op-1"];
    }];
    [self.operationQueue addOperationWithBlock:^{
        [self operationSaleTicketWithName:@"op-2"];
    }];
    [self.operationQueue addOperationWithBlock:^{
        [self operationSaleTicketWithName:@"op-3"];
    }];
}
// 多線程賣票之一:NSOperation 核心賣票代碼
- (void)operationSaleTicketWithName:(NSString *)name
{
    while (YES) {
        // 同步鎖synchronized要鎖的范圍,對被搶奪資源修改/讀取的代碼部分
        @synchronized(self) {
            // 判斷是否還有票
            if ([Ticket sharedTicket].tickets > 0) {
                [Ticket sharedTicket].tickets--;
                // 提示,涉及到被搶奪資源的內容定義方面的操作,千萬不要跨線程去處理
                NSString *str = [NSString stringWithFormat:
									@"剩餘票數 %d 線程名稱 %@", 
									[Ticket sharedTicket].tickets, name];
                
                // 在mainQueue主線程中更新UI
                [[NSOperationQueue mainQueue]addOperationWithBlock:^{
					// 調用自定義方法,更新編輯框的內容,並滾動至最後一行
                    [self appendContent:str];
                }];
            } else {
                NSLog(@"賣票完成 %@ %@", name, [NSThread currentThread]);
                break;
            }
        }
        // 模擬賣票休息,不同的窗口,工作效率不同
        if ([name isEqualToString:@"op-1"]) {
            [NSThread sleepForTimeInterval:0.6f];
        } else {
            [NSThread sleepForTimeInterval:0.4f];
        }
    }
}
#pragma mark 更新UI,追加當前餘票數,到多行文本框
- (void)appendContent:(NSString *)text
{
    // 1. 取出多行文本框裡面原來的內容
    NSMutableString *str = [NSMutableString 
							stringWithString:self.textView.text];
    // 2. 將text追加至textView內容的末尾
    [str appendFormat:@"%@\n", text];
    // 3. 使用追加後的文本,替換textView中的內容
    [self.textView setText:str];
    // 4. 將textView滾動至視圖底部,保證能夠及時看到新追加的內容
	// 參數1是index,參數2是截取的長度
    NSRange range = NSMakeRange(str.length - 1, 1);
	// 5.滾動到編輯框的最後一行
    [self.textView scrollRangeToVisible:range];
}
// 多線程賣票之二:NSThread
- (void)threadSales
{
	// 類方法創建新線程,並且直接運行
    [NSThread detachNewThreadSelector:@selector(threadSaleTicketWithName:)
									toTarget:self withObject:@"thread-1"];
	// 類方法創建新線程,並且直接運行								
    [NSThread detachNewThreadSelector:@selector(threadSaleTicketWithName:)
									toTarget:self withObject:@"thread-2"];
}
// 多線程賣票之二:NSThread 核心賣票代碼
- (void)threadSaleTicketWithName:(NSString *)name
{
    // 使用NSThread時,線程調用的方法千萬要使用@autoreleasepool
    @autoreleasepool {        
        while (YES) {
			// 同步鎖synchronized要鎖的范圍,對被搶奪資源修改/讀取的代碼部分
            @synchronized(self) {
                if ([Ticket sharedTicket].tickets > 0) {
                    [Ticket sharedTicket].tickets--;
                    NSString *str = [NSString stringWithFormat:
									@"剩餘票數 %d 線程名稱 %@", 
									[Ticket sharedTicket].tickets, name];
                    // 在MainThread主線程中更新UI
					// 調用自定義方法,更新編輯框的內容,並滾動至最後一行
                    [self performSelectorOnMainThread:
								@selector(appendContent:) withObject:str
								waitUntilDone:YES];
                } else {
                    break;
                }
            }
            // 模擬賣票休息,不同的窗口,工作效率不同
            if ([name isEqualToString:@"thread-1"]) {
                [NSThread sleepForTimeInterval:1.0f];
            } else {
                [NSThread sleepForTimeInterval:0.1f];
            }
        }
    }
}
// 多線程賣票之三:GCD,單純隻創建三個異步任務分別賣票
- (void)gcdSales_without_group
{
    // 1) 創建全局隊列
    dispatch_queue_t queue = dispatch_get_global_queue(
					DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    // 2) 創建三個異步任務分別賣票
    dispatch_async(queue, ^{
        [self gcdSaleTicketWithName:@"gcd-1"];
    });
    dispatch_async(queue, ^{
        [self gcdSaleTicketWithName:@"gcd-2"];
    });
    dispatch_async(queue, ^{
        [self gcdSaleTicketWithName:@"gcd-3"];
    });    
}
// 多線程賣票之三:GCD,創建組,將三個異步任務添加到組
- (void)gcdSales_with_group
{
    // 1) 創建全局隊列
    dispatch_queue_t queue = dispatch_get_global_queue(
					DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);    
    // 2. GCD中可以將一組相關聯的操作,定義到一個群組中
    // 定義到群組中之後,當所有線程完成時,可以獲得通知
    // 3. 定義群組
    dispatch_group_t group = dispatch_group_create();
    // 4. 定義群組的異步任務
    dispatch_group_async(group, queue, ^{
        [self gcdSaleTicketWithName:@"gcd-1"];
    });
    dispatch_group_async(group, queue, ^{
        [self gcdSaleTicketWithName:@"gcd-2"];
    });
    // 3) 群組任務完成通知,當前組中的三個線程全部完成的時候,會調用~
    dispatch_group_notify(group, queue, ^{
        NSLog(@"賣完瞭");
    });
}
// 多線程賣票之三:GCD 核心賣票代碼
- (void)gcdSaleTicketWithName:(NSString *)name
{
    while (YES) {
        // 同步鎖synchronized要鎖的范圍,對被搶奪資源修改/讀取的代碼部分
        @synchronized(self) {
            if ([Ticket sharedTicket].tickets > 0) {
                [Ticket sharedTicket].tickets--;
                // 提示內容
                NSString *str = [NSString stringWithFormat:@"剩餘票數 %d,
						線程名稱 %@", [Ticket sharedTicket].tickets, name];
                // 在dispatch_get_main_queue主線程中更新UI
                dispatch_sync(dispatch_get_main_queue(), ^{
					// 調用自定義方法,更新編輯框的內容,並滾動至最後一行
                    [self appendContent:str];
                });
            } else {
                break;
            }
        }
        // 模擬賣票休息,不同的窗口,工作效率不同
        if ([name isEqualToString:@"gcd-1"]) {
            [NSThread sleepForTimeInterval:1.0f];
        } else {
            [NSThread sleepForTimeInterval:0.2f];
        }
    }
}
@end

H:/1007/03_多線程_賣票_單例_Ticket.h

//  Ticket.h
//  多線程-03.賣票
//  Created by apple on 13-10-7.
//  Copyright (c) 2013年 itcast. All rights reserved.

#import 

@interface Ticket : NSObject

// 實例化票據的單例
+ (Ticket *)sharedTicket;

// 在多線程應用中,所有被搶奪資源的屬性需要設置為原子屬性
// 系統會在多線程搶奪時,保證該屬性有且僅有一個線程能夠訪問
// 註意:使用atomic屬性,會降低系統性能,在開發多線程應用時,盡量不要資源
// 另外,atomic屬性,必須與@synchronized(同步鎖)一起使用

// 票數 atomic
@property (assign, atomic) NSInteger tickets;

@end

H:/1007/03_多線程_賣票_單例_Ticket.m

//  Ticket.m
//  多線程-03.賣票
//  Created by apple on 13-10-7.
//  Copyright (c) 2013年 itcast. All rights reserved.
#import "Ticket.h"
static Ticket *SharedInstance;
@implementation Ticket
/**
 實現單例模型需要做三件事情
 1. 使用全局靜態變量記錄住第一個被實例化的對象
    static Ticket *SharedInstance
 2. 重寫allocWithZone方法,並使用dispatch_once_t,從而保證在多線程情況下,
    同樣隻能實例化一個對象副本
 3. 建立一個以shared開頭的類方法實例化單例對象,便於其他類調用,同時不容易引起歧義
    同樣用dispatch_once_t確保隻有一個副本被建立
	
 關於被搶奪資源使用的註意事項
 
 在多線程應用中,所有被搶奪資源的屬性需要設置為原子屬性
 系統會在多線程搶奪時,保證該屬性有且僅有一個線程能夠訪問
 
 註意:使用atomic屬性,會降低系統性能,在開發多線程應用時,盡量不要搶資源
 另外,atomic屬性,必須與@synchronized(同步鎖)一起使用
 */
// 使用內存地址實例化對象,所有實例化方法,最終都會調用此方法
// 要實例化出來唯一的對象,需要一個變量記錄住第一個實例化出來的對象
+ (id)allocWithZone:(NSZone *)zone
{
    // 解決多線程中,同樣隻能實例化出一個對象副本
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        SharedInstance = [super allocWithZone:zone];
    });
    return SharedInstance;
}
// 建立一個單例對象,便於其他類調用
+ (Ticket *)sharedTicket
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        SharedInstance = [[Ticket alloc]init];
    });
    return SharedInstance;
}
@end

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *