iOS_多线程(二)
1.串行队列
GCD中获得串行有2种途径
(1)使用dispatch_queue_create函数创建串行队列
dispatch_queue_t dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);
示例:
dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
使用dispatch_get_global_queue函数获得全局的并发队列
dispatch_queue_t dispatch_get_global_queue(dispatch_queue_priority_t priority,unsigned long flags);
示例:
七、案例分析
案例1:使用GCD模拟售票线程。
#import "ViewController.h" @interface ViewController () { NSInteger _tickets; } @property (weak, nonatomic) IBOutlet UITextView *textView; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; _tickets = 20; self.textView.text = @""; self.textView.layoutManager.allowsNonContiguousLayout = NO; //用GCD创建售票线程 /** * 队列的名称:队列的标识符 队列的方式: DISPATCH_QUEUE_SERIAL 串行 DISPATCH_QUEUE_CONCURRENT 并行 */ //1.创建自定义队列 dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT); //2.往队列中添加任务 dispatch_async(queue, ^{ [self gcdSaleMethod:@"售票GCD-1"]; }); dispatch_async(queue, ^{ [self gcdSaleMethod:@"售票GCD-2"]; }); //如果采用同步方式,会将所有线程添加到主线程。 } -(void)appendTextView:(NSString *)text { //获取现有的内容 NSMutableString *string = [NSMutableString stringWithString:self.textView.text]; NSRange range = NSMakeRange(string.length, 1); //设置TextView [string appendString:[NSString stringWithFormat:@"%@\n",text]]; [self.textView setText:string]; //滚动视图 [self.textView scrollRangeToVisible:range]; } -(void)gcdSaleMethod:(NSString *)name { while (YES) { if (_tickets > 0) { //在主队列都是串行执行的 dispatch_async(dispatch_get_main_queue(), ^{ NSString *info = [NSString stringWithFormat:@"当前票数:%ld,当前线程:%@",_tickets,name]; [self appendTextView:info]; _tickets--; }); if ([name isEqualToString:@"售票GCD-1"]) { [NSThread sleepForTimeInterval:0.3f]; } else { [NSThread sleepForTimeInterval:0.2f]; } } else { dispatch_async(dispatch_get_main_queue(), ^{ NSString *info = [NSString stringWithFormat:@"已无剩余票数,当前线程:%@",name]; [self appendTextView:info]; }); break; } } } @end
接下来对代码中深蓝色部分进行分析:
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
这是GCD中的一个用来执行任务的函数实质:把右边的参数(任务)提交给左边的参数(队列)进行执行。采用的是异步的方式。
案例2:使用GCD模拟售票线程,与案例1稍有不同,案例2中使用了线程组。
#import "ViewController.h" @interface ViewController () { NSInteger _tickets; } @property (weak, nonatomic) IBOutlet UITextView *textView; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; _tickets = 20; self.textView.text = @""; self.textView.layoutManager.allowsNonContiguousLayout = NO; //用GCD创建售票线程 /** * 队列的名称:队列的标识符 队列的方式: DISPATCH_QUEUE_SERIAL 串行 DISPATCH_QUEUE_CONCURRENT 并行 */ //1.自定义队列 dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT); //创建线程组 dispatch_group_t group = dispatch_group_create(); //2.创建线程组往队列中添加任务 dispatch_group_async(group, queue, ^{ [self gcdSaleMethod:@"售票GCD-1"]; });
//调度群组异步任务 dispatch_group_async(group, queue, ^{ [self gcdSaleMethod:@"售票GCD-2"]; }); //等线程组中的所有任务完成后,会接收到通知 dispatch_group_notify(group, queue, ^{ dispatch_async(dispatch_get_main_queue(), ^{ [self appendTextView:@"已无剩余票啦!"]; }); }); } -(void)appendTextView:(NSString *)text { NSMutableString *string = [NSMutableString stringWithString:self.textView.text]; NSRange range = NSMakeRange(string.length, 1); [string appendString:[NSString stringWithFormat:@"%@\n",text]]; [self.textView setText:string]; [self.textView scrollRangeToVisible:range]; } -(void)gcdSaleMethod:(NSString *)name { while (YES) { if (_tickets > 0) { //在主队列都是串行执行的 dispatch_async(dispatch_get_main_queue(), ^{ NSString *info = [NSString stringWithFormat:@"当前票数:%ld,当前线程:%@",_tickets,name]; [self appendTextView:info]; _tickets--; }); if ([name isEqualToString:@"售票GCD-1"]) { [NSThread sleepForTimeInterval:0.3f]; } else { [NSThread sleepForTimeInterval:0.2f]; } } else { break; } } } @end
以上两个案例,实现的都是模拟售票,运行结果如下图:
八、延时任务的执行
在软件开发过程中,偶尔会出现,不希望某个线程立马执行,而是在经过一段时间之后再去调度执行,这就会出现任务的延时调度问题。接下来通过3种方法来实现任务的延时执行,在执行3秒之后再输出@“hello world”。
方式1:使用NSObject的方法
- (void)viewDidLoad { [super viewDidLoad]; // 延迟执行 //1.NSObject中的方式 [self performSelector:@selector(printString:) withObject:@"hello world" afterDelay:3.0]; } -(void)printString:(NSString *)str { NSLog(@"%@",str); }
方式2:使用GCD的方式
- (void)viewDidLoad { [super viewDidLoad]; //2.GCD中的方法 /** 参数解析 1.基准时间:程序运行时的时间 2.偏移时间(单位:纳秒)以当前运行时间为基准在多久之后进行执行 */ dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, 3.0*NSEC_PER_SEC); //将准备输出的字符串添加到队列中去,采用延迟的方式进行等待执行输出 dispatch_after(delay, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [self printString:@"hello world"]; }); } -(void)printString:(NSString *)str { NSLog(@"%@",str); }
方式3:使用NSTimer定时器的方式(此处给大家分享使用定时器实现延时任务执行的两种方式)
- (void)viewDidLoad { [super viewDidLoad]; //使用定时器 //第一种 self.timer = [NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:@selector(printString) userInfo:nil repeats:YES]; [self.timer fire]; } -(void)printString { NSLog(@"hello world"); }
- (void)viewDidLoad { [super viewDidLoad]; //使用定时器 //第二种 self.timer = [NSTimer timerWithTimeInterval:3.0 target:self selector:@selector(printString:) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop]addTimer:self.timer forMode:NSDefaultRunLoopMode]; } -(void)printString { NSLog(@"hello world"); }
在以上两段代码中,有两处蓝色标明的部分,参数repeats,它的作用是:如果参数值为YES,每隔3秒就会输出“hello world”一次。如果参数值为NO,仅输出一次。现在知道了,当repeats的参数值为YES时,会一直执行下去,但又该怎样将它结束掉呢?在NSTimer中给出了停止的方法,看以下代码。
//停止定时器 [self.timer invalidate];
九、仅一次任务执行、多次重复任务执行
在ios中,给出了仅一次任务执行、多次重复任务执行的方法。废话不多说,直接看代码。
案例1:仅一次任务执行的方法
//dispatch_once()函数执行时需要传入一个dispatch_once_t类型(本质就是long型整数)的指针(即predicate参数),该指针用于变量用于判断代码块是否已经执行过。 static dispatch_once_t onceToken; dispatch_once(&onceToken,^{ NSLog(@"====执行代码块==="); [NSThread sleepForTimeInterval:3.0]; });
案例2:多次重复任务执行的方法
//dispatch_apply()函数将控制提交的代码块重复执行多次,如果该代码块被提交给并发队列,系统可以使用多个线程并发执行同一个代码块。 //控制代码块执行5次 dispatch_apply(5, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t time) { //time形参代表当前正在执行第几次 NSLog(@"===执行【%lu】次===%@",time,[NSThread currentThread]); });
经过两天的学习,多线程也告一段落了,现在来总结一下。首先先来看:NSThread、NSOperation、GCD三种多线程技术的流程对比