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