// 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 <Foundation/Foundation.h> @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