进程是指在系统中正在运行的一个应用程序
每一个进程之间是独立的,每一个进程均运行在其专用且受保护的内存空间内程序员
1个进程要想执行任务,必须得有线程(每1个进程至少要有1条线程,称为主线程)
一个进程(程序)的全部任务都在线程中执行安全
1.线程是CPU调用(执行任务)的最小单位。
2.进程是CPU分配资源的最小单位。
3.一个进程中至少要有一个线程。
4.同一个进程内的线程共享进程的资源。数据结构
1个线程中任务的执行是串行的
若是要在1个线程中执行多个任务,那么只能一个一个地按顺序执行这些任务
也就是说,在同一时间内,1个线程只能执行1个任务多线程
1个进程中能够开启多条线程,每条线程能够并行(同时)执行不一样的任务
多线程技术能够提升程序的执行效率并发
同一时间,CPU只能处理1条线程,只有1条线程在工做(执行),多线程并发(同时)执行,实际上是CPU快速地在多条线程之间调度(切换),若是CPU调度线程的时间足够快,就形成了多线程并发执行的假象。
那么若是线程很是很是多,会发生什么状况?
CPU会在N多线程之间调度,CPU会累死,消耗大量的CPU资源,同时每条线程被调度执行的频次也会会下降(线程的执行效率下降)。
所以咱们通常只开3-5条线程。app
多线程的优势
能适当提升程序的执行效率
能适当提升资源利用率(CPU、内存利用率)
多线程的缺点
建立线程是有开销的,iOS下主要成本包括:内核数据结构(大约1KB)、栈空间(子线程512KB、主线程1MB,也可使用-setStackSize:设置,但必须是4K的倍数,并且最小是16K),建立线程大约须要90毫秒的建立时间
若是开启大量的线程,会下降程序的性能,线程越多,CPU在调度线程上的开销就越大。
程序设计更加复杂:好比线程之间的通讯、多线程的数据共享等问题。异步
主线程的主要做用
显示\刷新UI界面
处理UI事件(好比点击事件、滚动事件、拖拽事件等)
主线程的使用注意
别将比较耗时的操做放到主线程中
耗时操做会卡住主线程,严重影响UI的流畅度,给用户一种“卡”的坏体验
将耗时操做放在子线程中执行,提升程序的执行效率async
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { //建立线程 pthread_t thread; /* 第一个参数pthread_t *restrict:线程对象 第二个参数const pthread_attr_t *restrict:线程属性 第三个参数void *(*)(void *) :指向函数的指针 第四个参数void *restrict:函数的参数 */ pthread_create(&thread, NULL,run ,NULL); } //void *(*)(void *) void *run(void *param) { for (NSInteger i =0 ; i<10000; i++) { NSLog(@"%zd--%@-",i,[NSThread currentThread]); } return NULL; }
// 方法一:建立线程,须要本身开启线程 NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(run) object:nil]; // 开启线程 [thread start]; // 方法二:建立线程后自动启动线程 [NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil]; // 方法三:隐式建立并启动线程 [self performSelectorInBackground:@selector(run) withObject:nil];
后面两种方法都不用咱们开启线程,相对方便快捷,可是没有办法拿到子线程对象,没有办法对子线程进行更详细的设置,例如线程名字和优先级等。函数
// 获取当前线程 + (NSThread *)currentThread; // 建立启动线程 + (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument; // 判断是不是多线程 + (BOOL)isMultiThreaded; // 线程休眠 NSDate 休眠到何时 + (void)sleepUntilDate:(NSDate *)date; // 线程休眠时间 + (void)sleepForTimeInterval:(NSTimeInterval)ti; // 结束/退出当前线程 + (void)exit; // 获取当前线程优先级 + (double)threadPriority; // 设置线程优先级 默认为0.5 取值范围为0.0 - 1.0 // 1.0优先级最高 // 设置优先级 + (BOOL)setThreadPriority:(double)p; // 获取指定线程的优先级 - (double)threadPriority NS_AVAILABLE(10_6, 4_0); - (void)setThreadPriority:(double)p NS_AVAILABLE(10_6, 4_0); // 设置线程的名字 - (void)setName:(NSString *)n NS_AVAILABLE(10_5, 2_0); - (NSString *)name NS_AVAILABLE(10_5, 2_0); // 判断指定的线程是不是 主线程 - (BOOL)isMainThread NS_AVAILABLE(10_5, 2_0); // 判断当前线程是不是主线程 + (BOOL)isMainThread NS_AVAILABLE(10_5, 2_0); // reports whether current thread is main // 获取主线程 + (NSThread *)mainThread NS_AVAILABLE(10_5, 2_0); - (id)init NS_AVAILABLE(10_5, 2_0); // designated initializer // 建立线程 - (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument NS_AVAILABLE(10_5, 2_0); // 指定线程是否在执行 - (BOOL)isExecuting NS_AVAILABLE(10_5, 2_0); // 线程是否完成 - (BOOL)isFinished NS_AVAILABLE(10_5, 2_0); // 线程是否被取消 (是否给当前线程发过取消信号) - (BOOL)isCancelled NS_AVAILABLE(10_5, 2_0); // 发送线程取消信号的 最终线程是否结束 由 线程自己决定 - (void)cancel NS_AVAILABLE(10_5, 2_0); // 启动线程 - (void)start NS_AVAILABLE(10_5, 2_0); // 线程主函数 在线程中执行的函数 都要在-main函数中调用,自定义线程中重写-main方法 - (void)main NS_AVAILABLE(10_5, 2_0); // thread body metho
启动线程 - (void)start; // 进入就绪状态 -> 运行状态。当线程任务执行完毕,自动进入死亡状态 阻塞(暂停)线程 + (void)sleepUntilDate:(NSDate *)date; + (void)sleepForTimeInterval:(NSTimeInterval)ti; // 进入阻塞状态 强制中止线程 + (void)exit; // 进入死亡状态
多线程安全隐患的缘由:1块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源,好比多个线程访问同一个对象、同一个变量、同一个文件。
那么当多个线程访问同一块资源时,很容易引起数据错乱和数据安全问题。性能
@synchronized(锁对象) { // 须要锁定的代码 }
互斥锁的使用前提:多条线程抢夺同一块资源时
注意:锁定1份代码只用1把锁,用多把锁是无效的
互斥锁的优缺点
优势:能有效防止因多线程抢夺资源形成的数据安全问题
缺点:须要消耗大量的CPU资源
下面经过一个售票实例来看一下线程安全的重要性
#import "ViewController.h" @interface ViewController () @property(nonatomic,strong)NSThread *thread01; @property(nonatomic,strong)NSThread *thread02; @property(nonatomic,strong)NSThread *thread03; @property(nonatomic,assign)NSInteger numTicket; //@property(nonatomic,strong)NSObject *obj; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // 总票数为30 self.numTicket = 30; self.thread01 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil]; self.thread01.name = @"售票员01"; self.thread02 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil]; self.thread02.name = @"售票员02"; self.thread03 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil]; self.thread03.name = @"售票员03"; } -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { [self.thread01 start]; [self.thread02 start]; [self.thread03 start]; } // 售票 -(void)saleTicket { while (1) { // 建立对象 // self.obj = [[NSObject alloc]init]; // 锁对象,自己就是一个对象,因此self就能够了 // 锁定的时候,其余线程没有办法访问这段代码 @synchronized (self) { // 模拟售票时间,咱们让线程休息0.05s [NSThread sleepForTimeInterval:0.05]; if (self.numTicket > 0) { self.numTicket -= 1; NSLog(@"%@卖出了一张票,还剩下%zd张票",[NSThread currentThread].name,self.numTicket); }else{ NSLog(@"票已经卖完了"); break; } } } } @end
当没有加互斥锁的时候咱们看一下输出
咱们发现第29张,第27张都被销售了3次,这显然是不容许的,这就是数据错乱,那么当咱们加上互斥锁时,其锁定的时候其余线程没有办法访问锁定的内容,等其访问完毕以后,其余线程才能够访问,咱们爱来看一下输出
此时就不会出现同一张票被屡次出售的数据错乱的状况了。
什么叫作线程间通讯
在1个进程中,线程每每不是孤立存在的,多个线程之间须要常常进行通讯,例如咱们在子线程完成下载图片后,回到主线程刷新UI显示图片
线程间通讯的体现
1个线程传递数据给另1个线程
在1个线程中执行完特定任务后,转到另1个线程继续执行任务
线程间通讯经常使用的方法
// 返回主线程 - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait; // 返回指定线程 - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;
下面咱们经过一个实例看一下线程之间的通讯
#import "ViewController.h" @interface ViewController () @property (weak, nonatomic) IBOutlet UIImageView *imageView; @end @implementation ViewController -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { [NSThread detachNewThreadSelector:@selector(donwLoadImage) toTarget:self withObject:nil]; } -(void)donwLoadImage { // 获取图片url地址 http://www.itunes123.com/uploadfile/2016/0421/20160421014340186.jpg NSURL *url = [NSURL URLWithString:@"http://www.itunes123.com/uploadfile/2016/0421/20160421014340186.jpg"]; // 下载图片二进制文件 NSData *data = [NSData dataWithContentsOfURL:url]; // 将图片二进制文件转化为image; UIImage *image = [UIImage imageWithData:data]; // 参数 waitUntilDone 是否等@selector(showImage:) 执行完毕之后再执行下面的操做 YES :等 NO:不等 // 返回主线程显示图片 // [self performSelectorOnMainThread:@selector(showImage:) withObject:image waitUntilDone:YES]; // self.imageView 也能够直接调用这个方法 直接选择 setImage方法,传入参数image便可 // [self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:YES]; // 返回特定的线程,[NSThread mainThread] 得到主线程 [self performSelector:@selector(showImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES]; } -(void)showImage:(UIImage *)image { self.imageView.image = image; } @end
GCD的全称是Grand Central Dispatch,是纯C语言,提供了很是多强大的函数
GCD的优点
GCD是苹果公司为多核的并行运算提出的解决方案
GCD会自动利用更多的CPU内核(好比双核、四核)
GCD会自动管理线程的生命周期(建立线程、调度任务、销毁线程)
程序员只须要告诉GCD想要执行什么任务,不须要编写任何线程管理代码
GCD中有2个核心概念:任务和队列
任务:执行什么操做,任务有两种执行方式: 同步函数 和 异步函数,他们之间的区别是
同步:只能在当前线程中执行任务,不具有开启新线程的能力,任务马上立刻执行,会阻塞当前线程并等待 Block中的任务执行完毕,而后当前线程才会继续往下运行
异步:能够在新的线程中执行任务,具有开启新线程的能力,但不必定会开新线程,当前线程会直接往下执行,不会阻塞当前线程
队列:用来存听任务,分为串行队列 和 并行队列
串行队列(Serial Dispatch Queue)
让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)
并发队列(Concurrent Dispatch Queue)
可让多个任务并发(同时)执行(自动开启多个线程同时执行任务)
并发功能只有在异步(dispatch_async)函数下才有效
GCD的使用就2个步骤
定制任务
肯定想作的事情
将任务添加到队列中
GCD会自动将队列中的任务取出,放到对应的线程中执行
任务的取出遵循队列的FIFO原则:先进先出,后进后出
// 第一个参数const char *label : C语言字符串,用来标识 // 第二个参数dispatch_queue_attr_t attr : 队列的类型 // 并发队列:DISPATCH_QUEUE_CONCURRENT // 串行队列:DISPATCH_QUEUE_SERIAL 或者 NULL dispatch_queue_t queue = dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);
建立并发队列
dispatch_queue_t queue = dispatch_queue_create("com.xxcc", DISPATCH_QUEUE_CONCURRENT);
建立串行队列
dispatch_queue_t queue = dispatch_queue_create("com.xxcc", DISPATCH_QUEUE_SERIAL);
GCD默认已经提供了全局并发队列,供整个应用使用,能够无需手动建立
/** 第一个参数:优先级 也可直接填后面的数字 #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 // 后台 第二个参数: 预留参数 0 */ dispatch_queue_t quque1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
得到主队列
dispatch_queue_t queue = dispatch_get_main_queue();
/* 第一个参数:队列 第二个参数:block,在里面封装任务 */ dispatch_sync(queue, ^{ });
开启异步函数 异步函数 :等主线程执行完毕以后,回过头开线程执行任务
dispatch_async(queue, ^{
});
dispatch_queue_t queue = dispatch_get_global_queue(0, 0); dispatch_async(queue, ^{ NSLog(@"---download1---%@",[NSThread currentThread]); });
异步函数+串行队列:会开启一条线程,任务串行执行
dispatch_queue_t queue = dispatch_queue_create("com.xxcc", DISPATCH_QUEUE_SERIAL); dispatch_async(queue, ^{ NSLog(@"---download1---%@",[NSThread currentThread]); });
同步函数+并发队列:不会开线程,任务串行执行
dispatch_queue_t queue = dispatch_get_global_queue(0, 0); dispatch_sync(queue, ^{ NSLog(@"---download1---%@",[NSThread currentThread]); });
同步函数+串行队列:不会开线程,任务串行执行
dispatch_queue_t queue = dispatch_queue_create("com.xxcc", DISPATCH_QUEUE_SERIAL); dispatch_sync(queue, ^{ NSLog(@"---download1---%@",[NSThread currentThread]); });
异步函数+主队列:不会开线程,任务串行执行
使用主队列(跟主线程相关联的队列)
主队列是GCD自带的一种特殊的串行队列,放在主队列中的任务,都会放到主线程中执行
//1.得到主队列 dispatch_queue_t queue = dispatch_get_main_queue(); //2.异步函数 dispatch_async(queue, ^{ NSLog(@"---download1---%@",[NSThread currentThread]); });
同步函数+主队列:死锁
//1.得到主队列 dispatch_queue_t queue = dispatch_get_main_queue(); //2.同步函数 dispatch_sync(queue, ^{ NSLog(@"---download1---%@",[NSThread currentThread]); });
由于这个方法在主线程中,给主线程中添加任务,而同步函数要求马上立刻执行,所以就会相互等待而发生死锁。将这个方法放入子线程中,则不会发生死锁,任务串行执行。
总结:
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { [self syncConcurrent]; } //同步函数+并发队列:不会开线程,任务串行执行 -(void)syncConcurrent { dispatch_queue_t queue = dispatch_get_global_queue(0, 0); NSLog(@"--syncConcurrent--start-"); dispatch_sync(queue, ^{ NSLog(@"---download1---%@",[NSThread currentThread]); }); dispatch_sync(queue, ^{ NSLog(@"---download2---%@",[NSThread currentThread]); }); dispatch_sync(queue, ^{ NSLog(@"---download3---%@",[NSThread currentThread]); }); dispatch_sync(queue, ^{ NSLog(@"---download4---%@",[NSThread currentThread]); }); NSLog(@"--syncConcurrent--end-"); }
咱们看一下输出
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { [self syncConcurrent]; }
//异步函数+并发队列:会开启新的线程,并发执行
-(void)asyncCONCURRENT { NSLog(@"--asyncCONCURRENT--start-"); dispatch_queue_t queue = dispatch_get_global_queue(0, 0); dispatch_async(queue, ^{ NSLog(@"---download1---%@",[NSThread currentThread]); }); dispatch_async(queue, ^{ NSLog(@"---download2---%@",[NSThread currentThread]); }); dispatch_async(queue, ^{ NSLog(@"---download3---%@",[NSThread currentThread]); }); dispatch_async(queue, ^{ NSLog(@"---download4---%@",[NSThread currentThread]); }); NSLog(@"--asyncCONCURRENT--end-"); }
咱们来看一下输出
注意:GCD中开多少条线程是由系统根据CUP繁忙程度决定的,若是任务不少,GCD会开启适当的子线程,并不会让全部任务同时执行。
咱们一样经过一个实例来看
#import "ViewController.h" @interface ViewController () @property (weak, nonatomic) IBOutlet UIImageView *imageView; @end @implementation ViewController -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { dispatch_queue_t queue = dispatch_get_global_queue(0, 0); dispatch_async(queue, ^{ // 得到图片URL NSURL *url = [NSURL URLWithString:@"//upload-images.jianshu.io/upload_images/2301429-d5cc0a007447e469.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"]; // 将图片URL下载为二进制文件 NSData *data = [NSData dataWithContentsOfURL:url]; // 将二进制文件转化为image UIImage *image = [UIImage imageWithData:data]; NSLog(@"%@",[NSThread currentThread]); // 返回主线程 这里用同步函数不会发生死锁,由于这个方法在子线程中被调用。 // 也可使用异步函数 dispatch_async(dispatch_get_main_queue(), ^{ self.imageView.image = image; NSLog(@"%@",[NSThread currentThread]); }); }); } @end
GCD线程间的通讯很是简单,使用同步或异步函数,传入主队列便可。
dispatch_barrier_async(queue, ^{ NSLog(@"--dispatch_barrier_async-"); });
咱们来看一下栅栏函数的做用
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { [self barrier]; } -(void)barrier { //1.建立队列(并发队列) dispatch_queue_t queue = dispatch_queue_create("com.xxccqueue", DISPATCH_QUEUE_CONCURRENT); dispatch_async(queue, ^{ for (NSInteger i = 0; i<3; i++) { NSLog(@"%zd-download1--%@",i,[NSThread currentThread]); } }); dispatch_async(queue, ^{ for (NSInteger i = 0; i<3; i++) { NSLog(@"%zd-download2--%@",i,[NSThread currentThread]); } }); //栅栏函数 dispatch_barrier_async(queue, ^{ NSLog(@"我是一个栅栏函数"); }); dispatch_async(queue, ^{ for (NSInteger i = 0; i<3; i++) { NSLog(@"%zd-download3--%@",i,[NSThread currentThread]); } }); dispatch_async(queue, ^{ for (NSInteger i = 0; i<3; i++) { NSLog(@"%zd-download4--%@",i,[NSThread currentThread]); } }); }
咱们来看一下输出
栅栏函数能够控制任务执行的顺序,栅栏函数以前的执行完毕以后,执行栅栏函数,而后在执行栅栏函数以后的
/* 第一个参数:延迟时间 第二个参数:要执行的代码 若是想让延迟的代码在子线程中执行,也能够更改在哪一个队列中执行 dispatch_get_main_queue() -> dispatch_get_global_queue(0, 0) */ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ NSLog(@"---%@",[NSThread currentThread]); });
延迟执行的其余方法:
// 2s中以后调用run方法 [self performSelector:@selector(run) withObject:nil afterDelay:2.0]; // repeats:YES 是否重复 [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
-(void)once { //整个程序运行过程当中只会执行一次 //onceToken用来记录该部分的代码是否被执行过 static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSLog(@"-----"); }); }
一次性代码主要应用在单例模式中,关于单例模式详解你们能够去看iOS-单例模式写一次就够了这里不在赘述。
/* 第一个参数:迭代的次数 第二个参数:在哪一个队列中执行 第三个参数:block要执行的任务 */ dispatch_apply(10, queue, ^(size_t index) { });
快速迭代:开启多条线程,并发执行,相比于for循环在耗时操做中极大的提升效率和速度
// 建立队列组 dispatch_group_t group = dispatch_group_create(); // 建立并行队列 dispatch_queue_t queue = dispatch_get_global_queue(0, 0); // 执行队列组任务 dispatch_group_async(group, queue, ^{ }); //队列组中的任务执行完毕以后,执行该函数 dispatch_group_notify(group, queue, ^{ });
下面看一了实例使用group下载两张图片而后合成在一块儿
#import "ViewController.h" @interface ViewController () @property (weak, nonatomic) IBOutlet UIImageView *imageView; @property (nonatomic, strong) UIImage *image1; /**< 图片1 */ @property (nonatomic, strong) UIImage *image2; /**< 图片2 */ @end @implementation ViewController -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { [self group]; } -(void)group { //下载图片1 //建立队列组 dispatch_group_t group = dispatch_group_create(); //1.开子线程下载图片 //建立队列(并发) dispatch_queue_t queue = dispatch_get_global_queue(0, 0); dispatch_group_async(group, queue, ^{ //1.获取url地址 NSURL *url = [NSURL URLWithString:@"http://www.huabian.com/uploadfile/2015/0914/20150914014032274.jpg"]; //2.下载图片 NSData *data = [NSData dataWithContentsOfURL:url]; //3.把二进制数据转换成图片 self.image1 = [UIImage imageWithData:data]; NSLog(@"1---%@",self.image1); }); //下载图片2 dispatch_group_async(group, queue, ^{ //1.获取url地址 NSURL *url = [NSURL URLWithString:@"http://img1.3lian.com/img2011/w12/1202/19/d/88.jpg"]; //2.下载图片 NSData *data = [NSData dataWithContentsOfURL:url]; //3.把二进制数据转换成图片 self.image2 = [UIImage imageWithData:data]; NSLog(@"2---%@",self.image2); }); //合成,队列组执行完毕以后执行 dispatch_group_notify(group, queue, ^{ //开启图形上下文 UIGraphicsBeginImageContext(CGSizeMake(200, 200)); //画1 [self.image1 drawInRect:CGRectMake(0, 0, 200, 100)]; //画2 [self.image2 drawInRect:CGRectMake(0, 100, 200, 100)]; //根据图形上下文拿到图片 UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); //关闭上下文 UIGraphicsEndImageContext(); //回到主线程刷新UI dispatch_async(dispatch_get_main_queue(), ^{ self.imageView.image = image; NSLog(@"%@--刷新UI",[NSThread currentThread]); }); }); }
4. NSOperation的使用(重点)
NSOperation 是苹果公司对 GCD 的封装,彻底面向对象,并比GCD多了一些更简单实用的功能,因此使用起来更加方便易于理解。NSOperation 和NSOperationQueue 分别对应 GCD 的 任务 和 队列。
NSOperation和NSOperationQueue实现多线程的具体步骤
1.将须要执行的操做封装到一个NSOperation对象中
2.将NSOperation对象添加到NSOperationQueue中
系统会自动将NSOperationQueue中的NSOperation取出来,并将取出的NSOperation封装的操做放到一条新线程中执行
NSOperation是个抽象类,并不具有封装操做的能力,必须使用它的子类
使用NSOperation子类的方式有3种
/* 第一个参数:目标对象 第二个参数:选择器,要调用的方法 第三个参数:方法要传递的参数 */ NSInvocationOperation *op = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download) object:nil]; //启动操做 [op start];
//1.封装操做 NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{ //要执行的操做,在主线程中执行 NSLog(@"1------%@",[NSThread currentThread]); }]; //2.追加操做,追加的操做在子线程中执行,能够追加多条操做 [op addExecutionBlock:^{ NSLog(@"---download2--%@",[NSThread currentThread]); }]; [op start];
// 重写自定义类的main方法实现封装操做
-(void)main { // 要执行的操做 } // 实例化一个自定义对象,并执行操做 CLOperation *op = [[CLOperation alloc]init]; [op start];
自定义类封装性高,复用性高。
NSOperation中的两种队列
主队列:经过mainQueue得到,凡是放到主队列中的任务都将在主线程执行
非主队列:直接alloc init出来的队列。非主队列同时具有了并发和串行的功能,经过设置最大并发数属性来控制任务是并发执行仍是串行执行
NSOperationQueue的做用
NSOperation能够调用start方法来执行任务,但默认是同步执行的
若是将NSOperation添加到NSOperationQueue(操做队列)中,系统会自动异步执行NSOperation中的操做
添加操做到NSOperationQueue中
- (void)addOperation:(NSOperation *)op; - (void)addOperationWithBlock:(void (^)(void))block;
注意:将操做添加到NSOperationQueue中,就会自动启动,不须要再本身启动了addOperation 内部调用 start方法
start方法 内部调用 main方法
注:这里使用NSBlockOperation示例,其余两种方法同样 // 1. 建立非主队列 同时具有并发和串行的功能,默认是并发队列 NSOperationQueue *queue =[[NSOperationQueue alloc]init]; //NSBlockOperation 不论封装操做仍是追加操做都是异步并发执行 // 2. 封装操做 NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"download1 -- %@",[NSThread currentThread]); }]; // 3. 将封装操做加入主队列 // 也能够不获取封装操做对象 直接添加操做到队列中 //[queue addOperationWithBlock:^{ // 操做 //}]; [queue addOperation:op1];
NSOperation
- (void)addDependency:(NSOperation *)op;
// 操做op1依赖op5,即op1必须等op5执行完毕以后才会执行 // 添加操做依赖,注意不能循环依赖,若是循环依赖会形成两个任务都不会执行 // 也能够夸队列依赖,依赖别的队列的操做 [op1 addDependency:op5];
void (^completionBlock)(void)
// 监听操做的完成 // 当op1线程完成以后,马上就会执行block块中的代码 // block中的代码与op1不必定在一个线程中执行,可是必定在子线程中执行 op1.completionBlock = ^{ NSLog(@"op1已经完成了---%@",[NSThread currentThread]); };
NSOperationQueue
//1.建立队列 NSOperationQueue *queue = [[NSOperationQueue alloc]init]; /* 默认是并发队列,若是最大并发数>1,并发 若是最大并发数==1,串行队列 系统的默认是最大并发数-1 ,表示不限制 设置成0则不会执行任何操做 */ queue.maxConcurrentOperationCount = 1;
//当值为YES的时候暂停,为NO的时候是恢复 queue.suspended = YES;
//取消全部的任务,再也不执行,不可逆 [queue cancelAllOperations];
注意:暂停和取消只能暂停或取消处于等待状态的任务,不能暂停或取消正在执行中的任务,必须等正在执行的任务执行完毕以后才会暂停,若是想要暂停或者取消正在执行的任务,能够在每一个任务之间即每当执行完一段耗时操做以后,判断是否任务是否被取消或者暂停。若是想要精确的控制,则须要将判断代码放在任务之中,可是不建议这么作,频繁的判断会消耗太多时间
NSOperation
// 开启线程 - (void)start; - (void)main; // 判断线程是否被取消 @property (readonly, getter=isCancelled) BOOL cancelled; // 取消当前线程 - (void)cancel; //NSOperation任务是否在运行 @property (readonly, getter=isExecuting) BOOL executing; //NSOperation任务是否已结束 @property (readonly, getter=isFinished) BOOL finished; // 添加依赖 - (void)addDependency:(NSOperation *)op; // 移除依赖 - (void)removeDependency:(NSOperation *)op; // 优先级 typedef NS_ENUM(NSInteger, NSOperationQueuePriority) { NSOperationQueuePriorityVeryLow = -8L, NSOperationQueuePriorityLow = -4L, NSOperationQueuePriorityNormal = 0, NSOperationQueuePriorityHigh = 4, NSOperationQueuePriorityVeryHigh = 8 }; // 操做监听 @property (nullable, copy) void (^completionBlock)(void) NS_AVAILABLE(10_6, 4_0); // 阻塞当前线程,直到该NSOperation结束。可用于线程执行顺序的同步 - (void)waitUntilFinished NS_AVAILABLE(10_6, 4_0); // 获取线程的优先级 @property double threadPriority NS_DEPRECATED(10_6, 10_10, 4_0, 8_0); // 线程名称 @property (nullable, copy) NSString *name NS_AVAILABLE(10_10, 8_0); @end
NSOperationQueue
// 获取队列中的操做 @property (readonly, copy) NSArray<__kindof NSOperation *> *operations; // 队列中的操做数 @property (readonly) NSUInteger operationCount NS_AVAILABLE(10_6, 4_0); // 最大并发数,同一时间最多只能执行三个操做 @property NSInteger maxConcurrentOperationCount; // 暂停 YES:暂停 NO:继续 @property (getter=isSuspended) BOOL suspended; // 取消全部操做 - (void)cancelAllOperations; // 阻塞当前线程直到此队列中的全部任务执行完毕 - (void)waitUntilAllOperationsAreFinished;
NSOperation线程之间的通讯方法
// 回到主线程刷新UI [[NSOperationQueue mainQueue]addOperationWithBlock:^{ self.imageView.image = image; }];
咱们一样使用下载多张图片合成综合案例
#import "ViewController.h" @interface ViewController () @property (weak, nonatomic) IBOutlet UIImageView *imageView; @property(nonatomic,strong)UIImage *image1; @property(nonatomic,strong)UIImage *image2; @end @implementation ViewController -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { // 建立非住队列 NSOperationQueue *queue = [[NSOperationQueue alloc]init]; // 下载第一张图片 NSBlockOperation *download1 = [NSBlockOperation blockOperationWithBlock:^{ NSURL *url = [NSURL URLWithString:@"http://img2.3lian.com/2014/c7/12/d/77.jpg"]; NSData *data = [NSData dataWithContentsOfURL:url]; self.image1 = [UIImage imageWithData:data]; }]; // 下载第二张图片 NSBlockOperation *download2 = [NSBlockOperation blockOperationWithBlock:^{ NSURL *url = [NSURL URLWithString:@"http://img2.3lian.com/2014/c7/12/d/77.jpg"]; NSData *data = [NSData dataWithContentsOfURL:url]; self.image2 = [UIImage imageWithData:data]; }]; // 合成操做 NSBlockOperation *combie = [NSBlockOperation blockOperationWithBlock:^{ // 开启图形上下文 UIGraphicsBeginImageContext(CGSizeMake(375, 667)); // 绘制图片1 [self.image1 drawInRect:CGRectMake(0, 0, 375, 333)]; // 绘制图片2 [self.image2 drawInRect:CGRectMake(0, 334, 375, 333)]; // 获取合成图片 UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); // 关闭图形上下文 UIGraphicsEndImageContext(); // 回到主线程刷新UI [[NSOperationQueue mainQueue]addOperationWithBlock:^{ self.imageView.image = image; }]; }]; // 添加依赖,合成图片须要等图片1,图片2都下载完毕以后合成 [combie addDependency:download1]; [combie addDependency:download2]; // 添加操做到队列 [queue addOperation:download1]; [queue addOperation:download2]; [queue addOperation:combie]; } @end
注意:子线程执行完操做以后就会当即释放,即便咱们使用强引用引用子线程使子线程不被释放,也不能给子线程再次添加操做,或者再次开启。
声明:本文非原创,仅仅整理一些开发技能知识文章,以做存档学习用
参考
http://www.jianshu.com/p/6e6f4e005a0b
http://www.jianshu.com/p/f28a50f72bb1
http://www.jianshu.com/p/6e74f5438f2c