iOS的三种多线程技术:
1. NSThread
1> 类方法 detachNewThreadSelector
直接启动线程,调用选择器方法
2> 成员方法 initWithTarget
需要使用start方法,才能启动实例化出来的线程
优点:简单
缺点:
1 控制线程的生命周期比较困难
2 控制并发线程数
3 先后顺序困难
例如:下载图片(后台线程) -> 滤镜美化(后台线程) -> 更新UI(主线程)
2. NSOperation
1> NSInvocationOperation
2> NSBlockOperation
定义完Operation之后,将操作添加到NSOperationQueue即可启动线程,执行任务
使用:
1> setMaxConcurrentOperationCount 可以控制同时并发的线程数量
2> addDependency 可以指定线程之间的依赖关系,从而达到控制线程执行顺序的目的
1 // Dependency依赖 2 // 提示:依赖关系可以多重依赖 3 // 注意:不要建立循环依赖 4 [op2 addDependency:op1]; 5 [op3 addDependency:op2];
提示:
要更新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中,同步还是异步取决于任务执行所在的队列,更方法名没有关系
获取队列的方法:
全局队列(可能会开启多条线程)
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();
多线程使用注意事项:
线程使用不是无节制的
iOS中的主线程的堆栈大小是1M
从第二个线程开始都是512KB
这些数值不能通过编译器开关或线程API函数更改
只有主线程有直接修改UI的能力
1 static Ticket *SharedInstance; 2 3 @implementation Ticket 4 5 /** 6 实现单例模型需要做三件事情 7 8 1. 使用全局静态变量记录住第一个被实例化的对象 9 static Ticket *SharedInstance 10 11 2. 重写allocWithZone方法,并使用dispatch_once_t,从而保证在多线程情况下, 12 同样只能实例化一个对象副本 13 14 3. 建立一个以shared开头的类方法实例化单例对象,便于其他类调用,同时不容易引起歧义 15 同样用dispatch_once_t确保只有一个副本被建立 16 17 18 关于被抢夺资源使用的注意事项 19 20 在多线程应用中,所有被抢夺资源的属性需要设置为原子属性 21 系统会在多线程抢夺时,保证该属性有且仅有一个线程能够访问 22 23 注意:使用atomic属性,会降低系统性能,在开发多线程应用时,尽量不要资源 24 另外,atomic属性,必须与@synchronized(同步锁)一起使用 25 26 */ 27 28 // 使用内存地址实例化对象,所有实例化方法,最终都会调用此方法 29 // 要实例化出来唯一的对象,需要一个变量记录住第一个实例化出来的对象 30 + (id)allocWithZone:(NSZone *)zone 31 { 32 // 解决多线程中,同样只能实例化出一个对象副本 33 static dispatch_once_t onceToken; 34 dispatch_once(&onceToken, ^{ 35 SharedInstance = [super allocWithZone:zone]; 36 }); 37 38 return SharedInstance; 39 } 40 41 // 建立一个单例对象,便于其他类调用 42 + (Ticket *)sharedTicket 43 { 44 static dispatch_once_t onceToken; 45 dispatch_once(&onceToken, ^{ 46 SharedInstance = [[Ticket alloc]init]; 47 }); 48 49 return SharedInstance; 50 } 51 52 @end
1 @property (weak, nonatomic) UITextView *textView; 2 3 @property (strong, nonatomic) NSOperationQueue *queue; 4 5 @end 6 7 @implementation MainViewController 8 9 #pragma mark 追加多行文本框内容 10 - (void)appendContent:(NSString *)text 11 { 12 // 1. 取出textView中的当前文本内容 13 NSMutableString *str = [NSMutableString stringWithString:self.textView.text]; 14 15 // 2. 将text追加至textView内容的末尾 16 [str appendFormat:@"%@\n", text]; 17 18 // 3. 使用追加后的文本,替换textView中的内容 19 [self.textView setText:str]; 20 21 // 4. 将textView滚动至视图底部,保证能够及时看到新追加的内容 22 NSRange range = NSMakeRange(str.length - 1, 1); 23 [self.textView scrollRangeToVisible:range]; 24 } 25 26 - (void)viewDidLoad 27 { 28 [super viewDidLoad]; 29 30 // 建立多行文本框 31 UITextView *textView = [[UITextView alloc]initWithFrame:self.view.bounds]; 32 // 不能编辑 33 [textView setEditable:NO]; 34 [self.view addSubview:textView]; 35 self.textView = textView; 36 37 // 预设可以卖30张票 38 [Ticket sharedTicket].tickets = 30; 39 40 // 实例化操作队列 41 self.queue = [[NSOperationQueue alloc]init]; 42 43 // 开始卖票 44 [self operationSales]; 45 } 46 #pragma mark - NSThread卖票 47 - (void)threadSaleTicketWithName:(NSString *)name 48 { 49 // 使用NSThread时,线程调用的方法千万要使用@autoreleasepool 50 @autoreleasepool { 51 while (YES) { 52 @synchronized(self) { 53 if ([Ticket sharedTicket].tickets > 0) { 54 [Ticket sharedTicket].tickets--; 55 56 NSString *str = [NSString stringWithFormat:@"剩余票数 %d 线程名称 %@", [Ticket sharedTicket].tickets, name]; 57 58 // 更新UI 59 [self performSelectorOnMainThread:@selector(appendContent:) withObject:str waitUntilDone:YES]; 60 } else { 61 break; 62 } 63 } 64 65 // 模拟休息 66 if ([name isEqualToString:@"thread-1"]) { 67 [NSThread sleepForTimeInterval:1.0f]; 68 } else { 69 [NSThread sleepForTimeInterval:0.1f]; 70 } 71 } 72 } 73 } 74 75 - (void)threadSales 76 { 77 [NSThread detachNewThreadSelector:@selector(threadSaleTicketWithName:) toTarget:self withObject:@"thread-1"]; 78 [NSThread detachNewThreadSelector:@selector(threadSaleTicketWithName:) toTarget:self withObject:@"thread-2"]; 79 } 80 81 #pragma mark - NSOperation卖票 82 - (void)operationSaleTicketWithName:(NSString *)name 83 { 84 while (YES) { 85 // 同步锁synchronized要锁的范围,对被抢夺资源修改/读取的代码部分 86 @synchronized(self) { 87 // 判断是否还有票 88 if ([Ticket sharedTicket].tickets > 0) { 89 [Ticket sharedTicket].tickets--; 90 91 // 提示,涉及到被抢夺资源的内容定义方面的操作,千万不要跨线程去处理 92 NSString *str = [NSString stringWithFormat:@"剩余票数 %d 线程名称 %@", [Ticket sharedTicket].tickets, name]; 93 94 // 更新UI 95 [[NSOperationQueue mainQueue]addOperationWithBlock:^{ 96 [self appendContent:str]; 97 }]; 98 } else { 99 NSLog(@"卖票完成 %@ %@", name, [NSThread currentThread]); 100 break; 101 } 102 } 103 // 模拟卖票休息 104 if ([name isEqualToString:@"op-1"]) { 105 [NSThread sleepForTimeInterval:0.6f]; 106 } else { 107 [NSThread sleepForTimeInterval:0.4f]; 108 } 109 } 110 } 111 112 - (void)operationSales 113 { 114 // 提示,operation中没有群组任务完成通知功能 115 // 两个线程卖票 116 [self.queue setMaxConcurrentOperationCount:2]; 117 118 [self.queue addOperationWithBlock:^{ 119 [self operationSaleTicketWithName:@"op-1"]; 120 }]; 121 [self.queue addOperationWithBlock:^{ 122 [self operationSaleTicketWithName:@"op-2"]; 123 }]; 124 [self.queue addOperationWithBlock:^{ 125 [self operationSaleTicketWithName:@"op-3"]; 126 }]; 127 } 128 129 #pragma mark - GCD卖票 130 - (void)gcdSaleTicketWithName:(NSString *)name 131 { 132 while (YES) { 133 // 同步锁synchronized要锁的范围,对被抢夺资源修改/读取的代码部分 134 @synchronized(self) { 135 if ([Ticket sharedTicket].tickets > 0) { 136 137 [Ticket sharedTicket].tickets--; 138 139 // 提示内容 140 NSString *str = [NSString stringWithFormat:@"剩余票数 %d, 线程名称 %@", [Ticket sharedTicket].tickets, name]; 141 142 // 更新界面 143 dispatch_sync(dispatch_get_main_queue(), ^{ 144 [self appendContent:str]; 145 }); 146 } else { 147 break; 148 } 149 } 150 151 // 模拟线程休眠 152 if ([name isEqualToString:@"gcd-1"]) { 153 [NSThread sleepForTimeInterval:1.0f]; 154 } else { 155 [NSThread sleepForTimeInterval:0.2f]; 156 } 157 } 158 } 159 160 - (void)gcdSales 161 { 162 // 1) 创建全局队列 163 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 164 165 // 2) 创建三个个异步任务分别卖票 166 // dispatch_async(queue, ^{ 167 // [self gcdSaleTicketWithName:@"gcd-1"]; 168 // }); 169 // dispatch_async(queue, ^{ 170 // [self gcdSaleTicketWithName:@"gcd-2"]; 171 // }); 172 // dispatch_async(queue, ^{ 173 // [self gcdSaleTicketWithName:@"gcd-3"]; 174 // }); 175 176 // 3. GCD中可以将一组相关联的操作,定义到一个群组中 177 // 定义到群组中之后,当所有线程完成时,可以获得通知 178 // 1) 定义群组 179 dispatch_group_t group = dispatch_group_create(); 180 181 // 2) 定义群组的异步任务 182 dispatch_group_async(group, queue, ^{ 183 [self gcdSaleTicketWithName:@"gcd-1"]; 184 }); 185 dispatch_group_async(group, queue, ^{ 186 [self gcdSaleTicketWithName:@"gcd-2"]; 187 }); 188 189 // 3) 群组任务完成通知 190 dispatch_group_notify(group, queue, ^{ 191 NSLog(@"卖完了"); 192 }); 193 } 194 195 @end