11.1_多线程基础(iOS)

进程的概念

  • 进程是操作系统上的概念,操作系统是直接驱动、管理计算机硬件的一款管理软件,它的运行帮助我们利用计算机硬件去完成我们的工作,它是硬件与应用软件结合的桥梁。
  • 进程可以分为系统进程和用户进程。凡是用于完成操作系统的各种功能的进程就是系统进程,它们就是处于运行状态下的操作系统本身;用户进程就是所有由你启动的进程。
  • 进程在系统当前运行的执行程序里包括:系统管理计算机个体和完成各种操作所必需的程序;用户开启、执行的额外程序,当然也包括用户不知道,而自动运行的非法程序(它们就有可能是病毒程序)。
  • 当你运行一个程序,你就启动了一个进程,程序(program)只能有一个进程,一个进程就是一个程序,程序是死的(静态的),进程是活的(动态的),也可以说进程是在系统中正在运行的一个程序,是程序的一次动态执行。有人说,我打开一个程序,比如chrome,有十多个进程呢,这是咋回事。那就是十多个程序,操作系统给他们分配了彼此独立的内存,相互执行不受彼此约束,都运行在其专用且受保护的内存空间内,但共享同样时间的CPU,硬盘,打印机,显示器,进程是 CPU 调度和分配资源的单位。对于用户而言,他们是一个整体,我们通常称之为应用程序(application)。对于计算机而言,一个进程就是一个程序,多个进程(比如一个浏览器的多个进程)对计算机而言就是多个不同的程序,它不会把它们理解为一个完整的“程序”。
  • 一个应用程序(application)通常由多个程序组成,也就是说一个应用程序可以同时启动多个进程,但是一个进程只能对应一个应用程序,进程和应用程序的关系犹如演出和剧本的关系。
  • 通过“活动监视器”可以查看Mac系统中所开启的进程。

线程的概念

  • 线程是指在进程中的一个执行流程,是CPU调用(执行任务)的最小单位 ,一个进程要想执行任务,必须得有线程,并且所有任务都是在线程中执行的。
  • 一个进程可以开启多条线程(包括主线程和子线程),但至少要有1条主线程,是程序启动时默认开启的,主要用来刷新UI界面。他们之间的关系就好比大脑与四肢和身体其他部分的关系一样。大脑就是主线程,其他部分就是子线程。子线程由主线程派生,而依附于主线程。主线程一旦over,进程就over了,其他子线程更是over了。即一个进程中可以同时运行多条不同的线程,他们分别执行不同的任务。进程相当于车间,线程相当于车间工人,同一个进程内的线程共享进程的资源。

多线程的原理

  • 当用户播放音频、下载资源、进行图像处理时往往希望做这些事情的时候其他操作不会被中断或者希望这些操作过程中更加顺畅。在单线程中一个线程只能做一件事情,一件事情处理不完另一件事就不能开始,这样势必影响用户体验。
  • 事实上同一时间,CPU只能处理1条线程,只有1条线程在工作(执行)。单核处理器时期的多线程,其实就是CPU快速地在多条线程之间调度(切换),如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象,这个时候多线程更多的用于解决线程阻塞造成的用户等待(通常是操作完UI后用户不再干涉,其他线程在等待队列中,CPU一旦空闲就继续执行,不影响用户其他UI操作),其处理能力并没有明显的变化,其本身运行效率并没有提高。
  • 如今无论是移动操作系统还是PC、服务器都是多核处理器,会真正实现多线程并发执行。一件事情我们可以分成多个步骤,在没有顺序要求的情况下使用多线程既能解决线程阻塞又能充分利用多核处理器运行能力,真正解决了运行效率问题。

多线程的优缺点

  • 多线程的优点:

  • 能适当提高程序的执行效率;

  • 能适当提高资源利用率(CPU、内存利用率);

  • 可以把占据时间长的程序中的任务放到后台去处理。

  • 多线程的缺点:

  • 创建线程是有开销的,iOS下主要成本包括:内核数据结构(大约1KB)、栈空间(子线程512KB、主线程1MB,也可以使用-setStackSize:设置,但必须是4K的倍数,而且最小是16K),创建线程大约需要90毫秒的创建时间,线程越多,CPU在调度线程上的开销就越大;

  • 如果开启大量的线程,会降低程序的性能,因为操作系统需要在它们之间切换;

  • 如果线程非常非常多,CPU会在N多线程之间调度,CPU会累死,消耗大量的CPU资源,每条线程被调度执行的频次会降低(线程的执行效率降低);

  • 程序设计更加复杂:比如线程之间的通信、多线程的数据共享

  • 是否需要创建多个线程取决于各种因素。在以下情况下,最适合采用多线程处理:

    • 耗时或大量占用处理器的任务阻塞用户界面操作;
    • 各个任务必须等待外部资源 (如远程文件或 Internet连接)。
  • 在ios程序中,一般开3到5条线程。

多线程在ios中的应用

在iOS中每个进程启动后都会建立一个主线程(UI线程),这个线程是其他线程的父线程。由于在iOS中除了主线程,其他子线程是独立于Cocoa Touch的,所以只有主线程可以更新UI界面,处理UI事件(比如点击事件、滚动事件、拖拽事件等)(新版iOS中,使用其他线程更新UI可能也能成功,但是不推荐),在使用中尽量别将比较耗时的操作放到主线程中,耗时操作会卡住主线程,严重影响UI的流畅度,影响用户体验。在 ios 中有以下四种实现多线程的方案。

一、pthread

pthread是基于 c 语言的一套通用的多线程 API,适用于Unix\Linux\Windows等系统,跨平台\可移植,由程序员管理线程的生命周期,由于是基于c语言的api,使用难度大,平时基本不用。

二、NSThread

(ios2.0推出)是一套OC框架,使用更加面向对象,简单易用,可直接操作线程对象,是由程序员管理线程的生命周期,偶尔使用。

+ (NSThread *)mainThread; // 获得主线程
- (BOOL)isMainThread; // 是否为主线程
+ (BOOL)isMainThread; // 是否为主线程
NSThread *current = [NSThread currentThread];//获得当前线程
//线程的名字
- (void)setName:(NSString *)n;
- (NSString *)name;
使用 NSThread 创建线程的方法:
//1、使用alloc] initWithTarget: 直接创建线程对象,需要调用 start 方法启动线程
 NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(run:) object:nil];
 [thread start];

//2、分离子线程,自动启动线程
 [NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];

//3、开启一条后台线程,并启动线程
[self performSelectorInBackground:@selector(run) withObject:nil];

//4、创建主线程的两种方法
 [self performSelectorOnMainThread: @selector(run) withObject:nil waitUntilDone:YES];
 [self performSelector:@selector(run) withObject:nil];
线程的状态:
  1. 先是在内存中分配一块存储空间,成为新建状态
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(run) object:nil];
  1. 调用start方法把线程对象放入可调度线程池中,等着 cpu调度,(只要线程在可调度线程池中,cpu就会去处理他),进入就绪状态.
 [thread start];
  1. 当cpu调度这条线程的时候,线程就进入运行状态,当cpu调用其他线程的时候,线程又会进入就绪状态,等待cpu 的调度.
  2. 如果调用了 sleep 方法或者被锁住了,线程就会进入阻塞状态,当睡眠时间到时了或者拿到了那个锁,线程又会重新回到可调度状态,等待cpu调度.
//让线程睡~秒
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
  // 让线程睡眠5秒(阻塞5秒)
  [NSThread sleepForTimeInterval:5];
//让线程睡到~时间
+ (void)sleepUntilDate:(NSDate *)date;
 //让线程睡到遥远的未来
  [NSThread sleepUntilDate:[NSDate distantFuture]];
  //让线程从现在开始睡5秒
  [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:5]];
  1. 如果线程任务正常执行完毕或者异常退出的话,线程就会处于死亡状态,从内存中销毁。
//直接退出线程,不能继续启动,进入死亡状态,会调用dealloc方法
[NSThread exit];
线程间通信

线程间的通讯的常用方法:

- (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 *iconView;
@end

@implementation ViewController

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    //开启一个后台线程调用downLoad方法执行下载图片的操作
    [self performSelectorInBackground:@selector(downLoad) withObject:nil];
 }

-(void)downLoad
{
    // 图片的网络路径
    NSURL *url = [NSURL URLWithString:@"http://img.pconline.com.cn/images/photoblog/9/9/8/1/9981681/200910/11/1255259355826.jpg"];
    
    // 根据图片的网络路径去下载图片数据
    NSData *data = [NSData dataWithContentsOfURL:url];

    UIImage *image = [UIImage imageWithData:data];
    
    //返回主线程刷新UI界面
    //调用self.iconView 的setImage:方法,传入image参数,并且等主线程 setImage:方法执行完毕再继续处理该子线程的程序
    [self.iconView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:YES];
}
@end

程序比较简单,当点击控制器后启动一个新的线程,在这个线程下载图片大概用了5s左右,在这5s内UI线程是不会阻塞的,用户可以进行其他操作,大约5s之后图片下载完成,此时调用UI线程将图片显示到界面中。

三、GCD(ios4推出)

GCD(Grand Central Dispatch)是基于C语言开发的一套多线程开发机制,也是目前苹果官方推荐的多线程开发方法。这种机制相比较于前面两种多线程开发方式最显著的优点就是充分利用了设备的多核,并且可以自动管理线程生命周期,经常使用。

使用 GCD 有两个核心步骤,定制任务和将任务添加到队列中,GCD统一管理整个队列中的任务,遵循先进先出的原则。

执行任务的常用函数有两种:

  • 同步函数:用同步的方式执行任务,只能在当前线程中执行任务,不具备开启新线程的能力。
dispatch_sync(dispatch_queue_t queue, //队列
                dispatch_block_t block//任务
);
  • 异步函数:用异步的方式执行任务,可以在新的线程中执行任务,具备开启新的线程的能力。
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

GCD中的队列可以分为两大类型:

  • 串行队列(Serial Dispatch Queue)
    只有一个线程,加入到队列中的操作按添加顺序依次执行(一个任务执行完毕后,再执行下一个任务
    )。GCD中获得串行有2种途径:

  • 使用dispatch_queue_create()方法创建串行队列,队列类型指定为DISPATCH_QUEUE_SERIAL或者NULL

    dispatch_queue_create(const char *label, // 队列名称 
    dispatch_queue_attr_t attr); // 队列的类型
    
  • 主队列:GCD自带的一种特殊的串行队列
    用来执行主线程上的操作任务,利用dispatch_get_main_queue()方法获得。

  • 并发队列(Concurrent Dispatch Queue)
    可以让多个任务并发执行(自动开启多个线程同时执行任务),操作进来之后它会将这些队列安排在可用的处理器上,同时保证先进来的任务优先处理。但是并发功能只有在异步函数下才有效。并发队列也分为两种:

  • 使用dispatch_queue_create()方法创建的并发队列,队列类型指定为DISPATCH_QUEUE_CONCURRENT

  • 全局并发队列,使用 dispatch_get_global_queue()方法获得一个全局并发队列,GCD默认已经提供了全局的并发队列,供整个应用使用,无需手动创建。

dispatch_get_global_queue(long identifier, unsigned long flags);
  /*
    long identifier(全局并发队列的优先级):
        #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 // 后台
    
    unsigned long flags:标记参数,目前没有用,一般传入0
*/

GCD死锁问题

#import "ViewController.h"

@interface ViewController ()
@end

@implementation ViewController

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
   // [self test1];
   // [self test2];
    [self syncSerial];
}

-(void)test
{
    dispatch_queue_t queue = dispatch_queue_create("com",  DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        dispatch_sync(queue, ^{ //程序卡死在这一行
            NSLog(@"--+++----");
        });
        NSLog(@"------");
    });
}

-(void)test1
{
   dispatch_sync(dispatch_queue_create(0, DISPATCH_QUEUE_SERIAL), ^{
        //  程序卡死在这一行
        dispatch_sync(dispatch_get_main_queue(), ^{ 
            NSLog(@"------");
        });
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"------");
        });
        NSLog(@"------");
    });
}

-(void)test2
{
   // 命令行工具: ——————,主队列内部打印不会执行
    //iOS项目:先打印——————,后打印------=======----
     dispatch_sync(dispatch_queue_create(0, DISPATCH_QUEUE_SERIAL), ^{
         dispatch_async(dispatch_get_main_queue(), ^{
             NSLog(@"------=======--------");
         });
        NSLog(@"-------------");
    });
}

/**
 * 同步函数 + 串行队列:不会开启新的线程,程序崩溃。
 */
-(void)syncSerial
{
    //1 创建串行队列
    dispatch_queue_t queue = dispatch_queue_create("com", DISPATCH_QUEUE_SERIAL);
    
    // 2.将任务加入队列
    dispatch_sync(queue, ^{
        NSLog(@"1-----%@",[NSThread currentThread]);
        
        dispatch_sync(queue, ^{  //执行到这行代码程序崩溃
            NSLog(@"1.1-----%@",[NSThread currentThread]);
        });
        
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"2-----%@",[NSThread currentThread]);
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"3-----%@",[NSThread currentThread]);
    });
}

/**
 * 同步函数 + 主队列:在主线程中执行会造成死锁,程序崩溃
 */
- (void)syncMain
{
    NSLog(@"syncMain ----- begin");
    
    // 1.获得主队列
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    // 2.将任务加入队列
    dispatch_sync(queue, ^{ //执行到这行代码程序崩溃
        NSLog(@"1-----%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"2-----%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"3-----%@", [NSThread currentThread]);
    });
    
    NSLog(@"syncMain ----- end");
}

/**
 * 同步+并发队列:不会开启新的线程,任务在主线程中顺序执行,不会阻塞,执行结果为:1、1.1、1--end、2、3、end
 */
-(void)syncConcurrent
{
    //1 获得全局的并发队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    // 2.将任务加入队列
    dispatch_sync(queue, ^{
        NSLog(@"1-----%@",[NSThread currentThread]);
        
        dispatch_sync(queue, ^{
            NSLog(@"1.1-----%@",[NSThread currentThread]);
            [NSThread sleepForTimeInterval:5];
        });
        NSLog(@"1---end");
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"2-----%@",[NSThread currentThread]);
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"3-----%@",[NSThread currentThread]);
    });
    
    NSLog(@"end"); //后打印
}

/**
 * 异步+串行队列:会开启新的线程,但是任务是串行的,不会阻塞,执行结果为:1、2、3、1.1、2.1、3.1
 */
-(void)asyncSerial
{
    //1 创建串行队列
    dispatch_queue_t queue = dispatch_queue_create("com.123.www", DISPATCH_QUEUE_SERIAL);
    
    // 2.将任务加入队列
    dispatch_async(queue, ^{
        NSLog(@"1-----%@",[NSThread currentThread]);
        
        dispatch_async(queue, ^{
            NSLog(@"1.1-----%@",[NSThread currentThread]);
        });
        
    });
    
    dispatch_async(queue, ^{
        NSLog(@"2-----%@",[NSThread currentThread]);
        
        dispatch_async(queue, ^{
            NSLog(@"2.1-----%@",[NSThread currentThread]);
        });
    });
    
    dispatch_async(queue, ^{
        NSLog(@"3-----%@",[NSThread currentThread]);
        
        dispatch_async(queue, ^{
            NSLog(@"3.1-----%@",[NSThread currentThread]);
        });
    });
    
}

/**
 * 异步函数 + 主队列:只在主线程中执行任务,不会阻塞。执行结果为:1、2、3、1.1、2.1、3.1
 */
- (void)asyncMain
{
    // 1.获得主队列
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    // 2.将任务加入队列
    dispatch_async(queue, ^{
        NSLog(@"1-----%@", [NSThread currentThread]);
        
        dispatch_async(queue, ^{
            NSLog(@"1.1-----%@", [NSThread currentThread]);
        });
    });
    dispatch_async(queue, ^{
        NSLog(@"2-----%@", [NSThread currentThread]);
        
        dispatch_async(queue, ^{
            NSLog(@"2.1-----%@", [NSThread currentThread]);
        });

    });
    dispatch_async(queue, ^{
        NSLog(@"3-----%@", [NSThread currentThread]);
        
        dispatch_async(queue, ^{
            NSLog(@"3.1-----%@", [NSThread currentThread]);
        });
    });
}

/**
 * 异步+并发队列:可以同时开启多条线程并发执行,不会阻塞
 */
-(void)asyncConcurrent
{
    //1 获得全局的并发队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    // 2.将任务加入队列
    dispatch_async(queue, ^{
        for (int i = 0; i<10; i++) {
            NSLog(@"%d--1--%@",i,[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i<10; i++) {
            NSLog(@"%d--2--%@",i,[NSThread currentThread]);
        }
    });
    
    dispatch_async(queue, ^{
        for (int i = 0; i<10; i++) {
           NSLog(@"%d--3--%@",i,[NSThread currentThread]);
        }
    });
}
@end

由此可以得出各种队列的执行结果:

  • 同步执行任务时,串行队列和并发队列都不会开启新的线程,并且都是串行执行任务。但是要注意死锁的情况。
  • 使用sync函数往当前串行队列中添加任务,会卡住当前的串行队列(产生死锁)
  • 异步执行任务时,并发队列可以开启新线程并且可以并发执行任务。手动创建的串行队列也可以开启新线程,但是任务是串行的。主队列不能开启新的线程,在主线程中执行任务,任务是串行的。
其他任务执行方法

GCD执行任务的方法并非只有简单的同步调用方法和异步调用方法,还有其他一些常用方法:

1、延时执行
  • 使用GCD函数
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    // 2秒后执行这里的代码...
});
  • 调用NSObject的方法
[self performSelector:@selector(run) withObject:nil afterDelay:2.0];
// 2秒后再调用self的run方法
  • 使用NSTimer
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:NO];
//2秒后调用self的run方法
2、快速迭代遍历
//使用dispatch_apply函数能进行快速迭代遍历(注意:这个方法没有办法异步执行(为了不阻塞线程可以使用dispatch_async()包装一下再执行)。)
dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t index){
    // 执行10次代码,index顺序不确定
});
3、一次性代码
//使用dispatch_once函数能保证某段代码在程序运行过程中只被执行1次(单例模式中常用次方法)
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    // 只执行1次的代码(这里面默认是线程安全的)
});
4、栅栏函数
dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
//使用此方法创建的任务首先会查看队列中有没有别的任务要执行,如果有,则会等待已有任务执行完毕再执行;同时在此方法后添加的任务必须等待此方法中任务执行后才能执行。
//注意:这个函数传入的并发队列必须是自己通过dispatch_queue_cretate创建的,如果传入的是一个串行或是一个全局的并发队列,那这个函数便等同于dispatch_async函数的效果
5、队列组

在 GCD 中还有队列组的概念,以实现对任务分组管理,如果需要分别异步执行两个耗时的操作,等两个异步操作都执行完毕后再回到主线程执行操作,这时候可以用队列组实现。

dispatch_group_t group =  dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 执行1个耗时的异步操作
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 执行1个耗时的异步操作
});
//如果一组任务全部完成可以通过dispatch_group_notify()方法获得完成通知
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    // 等前面的异步操作都执行完毕后,回到主线程...
});
6. dispatch_group_enter

个人理解:和内存管理的引用计数类似,我们可以认为group也维护了一个计数器,当调用enter时计数加1,调用leave时计数减1,当计数为0时会调用dispatch_group_notify。

dispatch_group_t dispatchGroup = dispatch_group_create();
    for (int i = 0; i<5; i++) {
        dispatch_group_enter(dispatchGroup);
        [XQHttpTool BlindDateThemeSubListWithPage:0 theme_id:arr[i] Success:^(NSDictionary * _Nonnull result) {
            dispatch_group_leave(dispatchGroup);
        } failure:^(NSError * _Nonnull error) {
            dispatch_group_leave(dispatchGroup);
        }];
    }
    
    dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^(){
    });

四、NSOperation

NSOperation是对 GCD 的封装,比 GCD 更加面向对象,不需要程序员管理线程生命周期,在开发中也经常使用。

使用NSOperationNSOperationQueue进行多线程开发只要将需要执行的操作封装到一个NSOperation对象中,然后将NSOperation对象添加到NSOperationQueue 中,系统会自动将NSOperationQueue中的NSOperation取出来,将取出的NSOperation封装的操作放到一条新的线程中去执行。NSOperationQueue负责管理、执行所有的NSOperation,在这个过程中可以更加容易的管理线程并发数和控制线程之间的依赖关系。

NSOperation是一个抽象类,实际开中需要使用其子类用于创建线程操作:NSInvocationOperationNSBlockOperation,也可以自定义子类继承自NSOperation

NSInvocationOperation

基本使用演示代码如下:

#import "ViewController.h"
@interface ViewController ()
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
 
    //创建一个调用操作
    NSInvocationOperation *invocationOperation=[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(run) object:nil];
    //创建完NSInvocationOperation对象并不会调用,它由一个start方法启动操作,但是注意如果直接调用start方法,则此操作会在当前线程中调用,不会开启新的线程,一般不会这么操作,而是添加到NSOperationQueue中
    // [invocationOperation start];
    
    //创建操作队列
    NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
    
    //添加任务到队列中,调用这个方法会自动调用[invocationOperation start]方法
    [operationQueue addOperation:invocationOperation];
    
    //简单写法
    // [[[NSOperationQueue alloc]init] addOperation:[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(run) object:nil]];
}
-(void)run{

    NSLog(@"----%@-----",[NSThread currentThread]);
}

@end
NSBlockOperation

基本使用演示代码如下:

#import "ViewController.h"

@interface ViewController ()
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
 
    [self blockOperation1];
    [self blockOperation2];
    [self blockOperation3];

}

-(void)blockOperation3
{

    [[[NSOperationQueue alloc]init] addOperationWithBlock:^{
        NSLog(@"---%@---",[NSThread currentThread]);//子线程
    }];
    [[[NSOperationQueue alloc]init] addOperationWithBlock:^{
        NSLog(@"---%@---",[NSThread currentThread]);//子线程
    }];
}

-(void)blockOperation2
{
    //创建一个调用操作
    NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"---%@---",[NSThread currentThread]);//子线程
    }];
    
    [blockOperation addExecutionBlock:^{
        NSLog(@"---%@----",[NSThread currentThread]); //子线程
    }];
    
    [blockOperation addExecutionBlock:^{
        NSLog(@"---%@----",[NSThread currentThread]);//子线程
    }];
    
    NSBlockOperation *blockOperation1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"---%@---",[NSThread currentThread]);//子线程
    }];
    
    //创建操作队列
    NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
    
    //添加任务到队列中,调用这个方法会自动调用[blockOperation start]方法
    [operationQueue addOperation:blockOperation];
    [operationQueue addOperation:blockOperation1];
    
    //添加多个任务到队列方法2
//    [operationQueue addOperations:@[blockOperation,blockOperation1] waitUntilFinished:NO];

}

-(void)blockOperation1
{
    //创建一个调用操作
    NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"---%@---",[NSThread currentThread]);//主线程
    }];
    
    //通过addExecutionBlock方法添加更多的操作
    //只要NSBlockOperation封装的操作数大于1,就会异步执行操作
    [blockOperation addExecutionBlock:^{
        NSLog(@"---%@----",[NSThread currentThread]); //子线程
    }];
    
    [blockOperation addExecutionBlock:^{
        NSLog(@"---%@----",[NSThread currentThread]);//子线程
    }];
    
    [blockOperation start];
}
@end
自定义NSOperation

基本使用演示代码如下:

//自定义ZLLOperation类继承自NSOperation类
//.m文件
#import "ZLLOperation.h"

@implementation ZLLOperation

//调用start方法会自动调用这个方法,自定义operation时,要重写 main 方法,在里面实现想执行的任务。
-(void)main
{
  NSLog(@"----%@---",[NSThread currentThread]);
}
@end

//在控制器中的调用方法如下
 NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[queue addOperation:[[ZLLOperation alloc]init]];
最大并发数

当最大并发数设置为1时(默认为-1,没有限制),就变成了串行。
最大并发数的相关方法如下:

- (NSInteger)maxConcurrentOperationCount;
- (void)setMaxConcurrentOperationCount:(NSInteger)cnt;
队列的取消、暂停、恢复
  • 取消
    一般在内存警告后取消队列中的操作
     //取消队列的所有操作
      - (void)cancelAllOperations;
     //取消单个操作
      - (void)cancel;
    
  • 暂停和恢复队列
    为了保证 scrollView 在滚动的时候流畅,通常在滚动开始时,暂停队列中的所有操作,滚动结束后恢复操作。
    //YES代表暂停队列,NO代表恢复队列
    - (void)setSuspended:(BOOL)b; 
    - (BOOL)isSuspended;
    

另:苹果官方建议最好在每当进行完一个耗时操作判断一下是否被取消或者暂停。

操作依赖

NSOperation之间可以设置依赖来保证执行顺序
比如一定要让操作A执行完后,才能执行操作B,可以这么写:

// 操作B依赖于操作A
[operationB addDependency:operationA]; 

可以在不同queue的NSOperation之间创建依赖关系,并且操作依赖关系可以设置多个,例如A依赖于B、B依赖于C…但是千万不要设置为循环依赖关系(例如A依赖于B,B依赖于A),否则是不会被执行的。

操作监听

可以监听一个操作的执行完毕

- (void (^)(void))completionBlock;
- (void)setCompletionBlock:(void (^)(void))block;

GCD和NSOperation两种多线程的实现方案对比

  • GCD是纯C语言的API,更接近底层,所以 GCD 在追求性能的底层操作来说,是速度最快的,而操作队列则是在 GCD 基础上的封装,是OC的对象。
  • 在GCD中,任务用块(block)来表示,而块是个轻量级的数据结构;相反操作队列中的 NSOperation则是个更加重量级的Object-C对象。
  • NSOperationQueue可以方便的调用cancel方法来取消某个操作,而GCD中的任务是无法被取消的(安排好任务之后就不管了)。
  • NSOperation可以方便的指定操作间的依赖关系。GCD 不支持异步操作之间的依赖关系设置,仅仅支持 FIFO 队列。
  • NSOperation可以通过KVO提供对NSOperation对象的精细控制(如监听当前操作是否被取消或是否已经完成等)
  • 通过自定义NSOperation的子类可以实现操作重用。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容