iOS中实现多线程的技术方案
pthread 实现多线程操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 代码实现: void * run(void *param){ for (NSInteger i = 0 ; i < 1000 ; i++) { NSLog (@"---buttonclick---%zd---%@" , i, [NSThread currentThread]); } return NULL ; } @implementation ViewController - (IBAction )clickButton:(id )sender { pthread_t thread; pthread_create(&thread, NULL , run, NULL ); pthread_t thread2; pthread_create(&thread2, NULL , run, NULL ); }
NSThread实现多线程 一个 NSThread 对象就代表一条线程
创建线程的多种方式
1 2 3 4 5 NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector (run:) object:@"jack" ];[thread start];
1 2 [NSThread detachNewThreadSelector:@selector (run:) toTarget:self withObject:@"jack" ];
1 2 3 4 5 6 7 8 9 10 11 12 13 14 [self performSelectorInBackground:@selector (run:) withObject:@"jack" ]; [NSThread sleepForTimeInterval:2.0 ]; #pragma mark - 执行run方法 - (void )run:(NSString *)param { for (NSInteger i = 0 ; i < 100 ; i++) { NSLog (@"---%@---%zd---%d" , [NSThread currentThread], i, [NSThread isMainThread]); } }
方法2和方法3的优点:快捷 方法1的优点:可以轻松拿到线程
线程间通信
线程间通信的体现
1个线程传递数据给另1个线程
在1个线程中执行完特定任务后,转到另1个线程继续执行任务
线程间通信的常用方法:小程序图片下载
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 - (void )touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { NSURL *url = [NSURL URLWithString:@"https://7xjanq.com1.z0.glb.clouddn.com/6478.jpg" ]; NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector (downLoadWithURL:) object:url]; [thread start]; } - (void )downLoadWithURL:(NSURL *)url { NSLog (@"%@" , [NSThread currentThread]); NSData *data = [NSData dataWithContentsOfURL:url]; UIImage *image = [UIImage imageWithData:data]; [self .imageView performSelectorOnMainThread:@selector (setImage:) withObject:image waitUntilDone:YES ]; }
以上两种方式使用线程已经过时了,开发中我们操作线程大多都使用 GCD 和 NSOperation 来实现多线程操作。
下面我就给大家系统的介绍一下 GCD 是如何实现多线程的
GCD 实现多线程 GCD 简介 GCD 全称是Grand Central Dispatch,可译为“超级厉害的中枢调度器”,GCD 是苹果公司为多核的并行运算提出的解决方案, GCD会自动利用更多的 CPU 内核(比如双核、四核)来开启线程执行任务,GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程),不需要我们程序员手动管理内存。
任务和队列 任务:在同步函数和异步函数中执行 队列:用来存放任务(并发 串行)
GCD会自动将队列中的任务取出,放到对应的线程,任务的取出遵循FIFO,即先入先出队列,First Input First Output 的缩写。先进入的任务先完成并结束,再执行后面的任务。
同步函数和异步函数,并发队列和串行队列
创建并发/串行队列代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 dispatch_queue_t queueConcurrent = dispatch_queue_create("520it.com" , DISPATCH_QUEUE_CONCURRENT); dispatch_queue_t queueSerial = dispatch_queue_create("520it.com" , DISPATCH_QUEUE_SERIAL); dispatch_queue_t queueGlobal = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ); #define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高 #define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默认(中) #define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低 #define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后台 dispatch_queue_t queueMain = dispatch_get_main_queue();
同步/异步函数代码表示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 dispatch_sync (queueSerial, ^{ for (NSInteger i = 0 ; i < 10 ; i++) { NSLog (@"~~~%@" , [NSThread currentThread]); } }); dispatch_sync (queueConcurrent, ^{ for (NSInteger i = 0 ; i < 10 ; i++) { NSLog (@"~~~%@" , [NSThread currentThread]); } }); dispatch_async (queueSerial, ^{ dispatch_async (queueSerial, ^{ for (NSInteger i = 0 ; i < 10 ; i++) { NSLog (@"~~~%@" , [NSThread currentThread]); } }); dispatch_async (queueSerial, ^{ for (NSInteger i = 0 ; i < 10 ; i++) { NSLog (@"~~~%@" , [NSThread currentThread]); } }); dispatch_async (queueSerial, ^{ for (NSInteger i = 0 ; i < 10 ; i++) { NSLog (@"~~~%@" , [NSThread currentThread]); } }); }); dispatch_async (queueConcurrent, ^{ dispatch_async (queueSerial, ^{ for (NSInteger i = 0 ; i < 10 ; i++) { NSLog (@"~~~%@" , [NSThread currentThread]); } }); dispatch_async (queueSerial, ^{ for (NSInteger i = 0 ; i < 10 ; i++) { NSLog (@"~~~%@" , [NSThread currentThread]); } }); dispatch_async (queueSerial, ^{ for (NSInteger i = 0 ; i < 10 ; i++) { NSLog (@"~~~%@" , [NSThread currentThread]); } }); });
注意 使用sync函数(同步函数)往当前串行队列中添加任务,会卡住当前的串行队列
解释:使用同步函数添加任务 A 到串行队列,说明要在当前串行队列立即执行任务 A ,任务 A 执行完后,才会执行任务 A 后面的代码。但当前队列是串行队列,也就是说任务 A 必须等到当前串行队列中正在执行的任务 B 完成之后才能执行,因此又必须先执行任务 A 中立即执行任务,又要必须等到任务 B 执行完以后才能执行下一个任务,所以就会卡死。你等我,我等你,谁也无法执行。
GCD实现线程通信 小项目:下载图片
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 NSURL *url = [NSURL URLWithString:@"https://7xjanq.com1.z0.glb.clouddn.com/6478.jpg" ]; dispatch_queue_t queue = dispatch_queue_create("111" , DISPATCH_QUEUE_CONCURRENT);dispatch_async (queue, ^{ NSData *data = [NSData dataWithContentsOfURL:url]; UIImage *image = [UIImage imageWithData:data]; dispatch_async (dispatch_get_main_queue(), ^{ self .imageView.image = image; }); });
GCD其他常用函数 dispatch_barrier 栅栏 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 dispatch_queue_t queue = dispatch_queue_create("11" , DISPATCH_QUEUE_CONCURRENT); dispatch_async (queue, ^{ for (int i = 0 ; i < 100 ; i++) { NSLog (@"%@--1" , [NSThread currentThread]); } }); dispatch_async (queue, ^{ for (int i = 0 ; i < 100 ; i++) { NSLog (@"%@--2" , [NSThread currentThread]); } }); dispatch_barrier_async(queue, ^{ for (int i = 0 ; i < 100 ; i++) { NSLog (@"%@--3" , [NSThread currentThread]); } }); dispatch_async (queue, ^{ for (int i = 0 ; i < 100 ; i++) { NSLog (@"%@--4" , [NSThread currentThread]); } });
dispatch_after 延迟执行 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 [self performSelector:@selector (run:) withObject:@"参数" afterDelay:2.0 ]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC )), dispatch_get_main_queue(), ^{ for (NSInteger i = 0 ; i < 100 ; i++) { NSLog (@"%@" , [NSThread currentThread]); } }); [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector (run) userInfo:nil repeats:NO ]; ``` #### dispatch_once 整个程序运行中执行一次 ```objc static dispatch_once_t onceToken;dispatch_once (&onceToken, ^{ });
作用:实现某个类的单粒对象
单例模式:在整个应用程序中,共享一份资源(这份资源只需要创建初始化1次)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 static id _person;+ (instancetype )sharePerson { static dispatch_once_t onceToken; dispatch_once (&onceToken, ^{ _person = [[super alloc] init]; }); return _person; } + (instancetype )allocWithZone:(struct _NSZone *)zone { static dispatch_once_t onceToken; dispatch_once (&onceToken, ^{ _person = [super allocWithZone:zone]; }); return _person; } - (id )copy { return _person; }
开发中一般自定义成宏,比较方便,一行代码搞定。
dispatch_apply 快速迭代 示例小程序:将一个文件夹中的图片剪切到另一个文件夹
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 NSString *from = @"/Users/Ammar/Pictures/壁纸" ;NSString *to = @"/Users/Ammar/Pictures/to" ;NSFileManager *manager = [NSFileManager defaultManager];NSArray *subPaths = [manager subpathsAtPath:from];dispatch_apply(subPaths.count, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^(size_t index) { NSLog (@"%@ - %zd" , [NSThread currentThread], index); NSString *subPath = subPaths[index]; NSString *fromPath = [from stringByAppendingPathComponent:subPath]; NSString *toPath = [to stringByAppendingPathComponent:subPath]; [manager moveItemAtPath:fromPath toPath:toPath error:nil ]; NSLog (@"%@---%zd" , [NSThread currentThread], index); });
dispatch_group 队列组 示例小程序:需求下载图片1 下载图片2 将图片1和图片2合成新的图片
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ); dispatch_group_t group = dispatch_group_create(); dispatch_group_async(group, queue, ^{ NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"https://7xjanq.com1.z0.glb.clouddn.com/6478.jpg" ]]; self .image1 = [UIImage imageWithData:data]; NSLog (@"1%@" , [NSThread currentThread]); }); dispatch_group_async(group, queue, ^{ NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"https://7xjanq.com1.z0.glb.clouddn.com/6478.jpg" ]]; self .image2 = [UIImage imageWithData:data]; NSLog (@"2%@" , [NSThread currentThread]); }); dispatch_group_notify(group, queue, ^{ CGFloat imageW = self .imageView.bounds.size.width; CGFloat imageH = self .imageView.bounds.size.height; UIGraphicsBeginImageContext (self .imageView.bounds.size); [self .image1 drawInRect:CGRectMake (0 , 0 , imageW * 0.5 , imageH)]; [self .image2 drawInRect:CGRectMake (imageW * 0.5 , 0 , imageW * 0.5 , imageH)]; UIImage *image = UIGraphicsGetImageFromCurrentImageContext (); UIGraphicsEndImageContext (); dispatch_async (dispatch_get_main_queue(), ^{ self .imageView.image = image; }); NSLog (@"3%@" , [NSThread currentThread]); });
GCD定时器 GCD定时器不受Mode影响因此比NSTimer要准确
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 static int count = 0 ;dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC )), dispatch_get_main_queue(), ^{ }); dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ); self .timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0 , 0 , queue); int64_t interval = (int64_t)(2.0 * NSEC_PER_SEC ); dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC )); dispatch_source_set_timer(self .timer, start, interval, 0 ); dispatch_source_set_event_handler(self .timer, ^{ NSLog (@"----------------%@" , [NSThread currentThread]); count++; if (count == 5 ) { dispatch_cancel(self .timer); self .timer = nil ; } }); dispatch_resume(self .timer);
讲完 GCD 就该讲讲 NSOperation,它是 GCD 的面向对象的封装,使用起来也更方便,
NSOperation实现多线程 NSOperation是个抽象类,并不具备封装操作的能力,必须使用它的子类
NSInvocationOperation
NSBlockOperation
自定义子类继承NSOperation,实现内部相应的方法
使用 NSOperation 实现多线程的步骤:
创建任务 NSOperation 对象
创建 NSOperationQueue 队列
将任务 NSOperation 对象 add 到 NSOperationQueue 队列中去
NSInvocationOperation 代码如下:
1 2 3 NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector (run) object:nil ];[op start];
注意:默认情况下,调用了start方法后并不会开一条新的线程去执行,而是在当前线程同步执行操作,只有将 NSOperation 放到一个 NSOperationQueue 中,才会异步执行操作
NSBlockOperation 1 2 3 4 5 6 7 8 9 10 11 NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{ NSLog (@"下载1------%@" , [NSThread currentThread]); }]; [op addExecutionBlock:^{ NSLog (@"下载2------%@" , [NSThread currentThread]); }]; 自定义Operation:需要实现- (void )main方法,需要做的事情放在mian方法中
NSOperationQueue 使用NSOperationQueue创建队列:主队列和全局队列
1 2 3 4 5 6 7 8 9 NSOperationQueue *queue = [[NSOperationQueue alloc] init];NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];queue.maxConcurrentOperationCount = 1 ;
队列的取消、暂停、恢复: 1 2 3 4 - (void )cancelAllOperations; - (void )setSuspended:(BOOL )b;
添加依赖 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 NSOperationQueue *queue = [[NSOperationQueue alloc] init];NSBlockOperation *block1 = [NSBlockOperation blockOperationWithBlock:^{ NSLog (@"download1 -------------- %@" , [NSThread currentThread]); }]; NSBlockOperation *block2 = [NSBlockOperation blockOperationWithBlock:^{ NSLog (@"download2 -------------- %@" , [NSThread currentThread]); }]; NSBlockOperation *block3 = [NSBlockOperation blockOperationWithBlock:^{ NSLog (@"download3 -------------- %@" , [NSThread currentThread]); }]; [block3 addDependency:block1]; [block3 addDependency:block2]; [queue addOperation:block1]; [queue addOperation:block2]; [queue addOperation:block3];
注意:不能循环依赖,但可以跨队列依赖,不管NSOperation对象在哪个队列。只要是两个NSOperation对象就可以依赖
线程间通信 示例:下载图片
1 2 3 4 5 6 7 8 9 10 11 [[[NSOperationQueue alloc] init] addOperation:[NSBlockOperation blockOperationWithBlock:^{ UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:@"https://7xjanq.com1.z0.glb.clouddn.com/6478.jpg" ]]]; [[NSOperationQueue mainQueue] addOperation:[NSBlockOperation blockOperationWithBlock:^{ self .imageView.image = image; }]]; }]];
示例:下载图片1和图片2 并合成图片
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; __block UIImage *image1 = nil ; NSBlockOperation *block1 = [NSBlockOperation blockOperationWithBlock:^{ image1 = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:@"https://7xjanq.com1.z0.glb.clouddn.com/6478.jpg" ]]]; }]; __block UIImage *image2 = nil ; NSBlockOperation *block2 = [NSBlockOperation blockOperationWithBlock:^{ image2 = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:@"https://7xjanq.com1.z0.glb.clouddn.com/6478.jpg" ]]]; }]; CGFloat imageW = self .imageView.bounds.size.width;CGFloat imageH = self .imageView.bounds.size.height; NSBlockOperation *block3 = [NSBlockOperation blockOperationWithBlock:^{ UIGraphicsBeginImageContext (CGSizeMake (imageW, imageH)); [image1 drawInRect:CGRectMake (0 , 0 , imageW * 0.5 , imageH)]; [image2 drawInRect:CGRectMake (0.5 * imageW, 0 , 0.5 * imageW, imageH)]; UIImage *image3 = UIGraphicsGetImageFromCurrentImageContext (); UIGraphicsEndImageContext (); [[NSOperationQueue mainQueue] addOperation:[NSBlockOperation blockOperationWithBlock:^{ self .imageView.image = image3; }]]; }]; [block3 addDependency:block1]; [block3 addDependency:block2]; [queue addOperation:block1]; [queue addOperation:block2]; [queue addOperation:block3];
应用 应用:SDWebImage 框架的底层主要功能实现就是基于多线程,使用多线程,我们可以实现小图片的多图片下载。这里的逻辑其实是比较复杂的
实现小图片的多图片下载思路:
代码实现见本文代码。
本文代码见:Multithreading