美文网首页iOS相关技术原理
OC底层原理 - 21 NSThread&GCD& NSOper

OC底层原理 - 21 NSThread&GCD& NSOper

作者: 卡布奇诺_95d2 | 来源:发表于2021-04-20 15:39 被阅读0次

    本文的主要目的是介绍NSThreadGCDNSOperation常见的使用方式。

    NSThread

    NSthread是苹果官方提供面向对象的线程操作技术,是对thread的上层封装,比较偏向于底层。简单方便,可以直接操作线程对象,使用频率较少。它需要程序员自己管理线程的生命周期。

    NSThread的使用如下

    • 通过init初始化方式创建
    //方式1
    - (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument API_AVAILABLE(macos(10.5), iOS(2.0), watchos(2.0), tvos(9.0));
    //示例
    NSThread* thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(doSomething:) object:@"方式1"];
    [thread1 start];
    
    //方式2
    - (instancetype)initWithBlock:(void (^)(void))block API_AVAILABLE(macosx(10.12), iOS(10.0), watchos(3.0), tvos(10.0));
    //示例
    self.thread1 = [[NSThread alloc] initWithBlock:^{
        NSLog(@"%@",[NSThread currentThread]);
    }];
    [self.thread1 start];
    
    • 通过detachNewThread(xxx)构造器方式创建
    //方式1
    + (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;
    //示例
    [NSThread detachNewThreadSelector:@selector(doSomething:) toTarget:self withObject:@"方式1"];
    
    //方式2
    + (void)detachNewThreadWithBlock:(void (^)(void))block API_AVAILABLE(macosx(10.12), iOS(10.0), watchos(3.0), tvos(10.0));
    //示例
    [NSThread detachNewThreadWithBlock:^{
        NSLog(@"%@", [NSThread currentThread]);
    }];
    
    • 通过performSelector(xxx)方式创建
    //在主线程执行
    - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
    - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
    //示例
    [self performSelectorOnMainThread:@selector(doSomething:) withObject:@"方式3" waitUntilDone:YES modes:@[NSRunLoopCommonModes]];
    [self performSelectorOnMainThread:@selector(doSomething:) withObject:@"方式3" waitUntilDone:YES];
    
    //在指定线程执行
    - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array API_AVAILABLE(macos(10.5), iOS(2.0), watchos(2.0), tvos(9.0));
    - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait API_AVAILABLE(macos(10.5), iOS(2.0), watchos(2.0), tvos(9.0));
    //示例
    [self performSelectorOnMainThread:@selector(doSomething:) withObject:@"方式3" waitUntilDone:YES];
    [self performSelector:@selector(doSomething:) onThread:[NSThread currentThread] withObject:@"方式3" waitUntilDone:YES modes:@[NSRunLoopCommonModes]];
    
    //创建一个子线程执行
    - (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg API_AVAILABLE(macos(10.5), iOS(2.0), watchos(2.0), tvos(9.0));
    //示例
    [self performSelectorInBackground:@selector(doSomething:) withObject:@"方式3"];
    

    NSThread的属性

    • 以下属性用于表示当前线程的状态
    //表示线程是否正在执行
    @property (readonly, getter=isExecuting) BOOL executing;
    //表示线程是否执行完成
    @property (readonly, getter=isFinished) BOOL finished;
    //线程是否被取消
    @property (readonly, getter=isCancelled) BOOL cancelled;
    
    • 线程优先级
    /** NSQualityOfService:
      NSQualityOfServiceUserInteractive:最高优先级,主要用于提供交互UI的操作,比如处理点击事件,绘制图像到屏幕上
      NSQualityOfServiceUserInitiated:次高优先级,主要用于执行需要立即返回的任务
      NSQualityOfServiceDefault:默认优先级,当没有设置优先级的时候,线程默认优先级
      NSQualityOfServiceUtility:普通优先级,主要用于不需要立即返回的任务
      NSQualityOfServiceBackground:后台优先级,用于完全不紧急的任务
    */
    @property NSQualityOfService qualityOfService; // read-only after the thread is started
    
    + (double)threadPriority;
    
    • 线程名称
    @property (nullable, copy) NSString *name;
    
    • 线程使用栈区大小,默认是512K
    @property NSUInteger stackSize;
    
    • 线程调用栈信息
    //该线程中函数调用的虚拟地址的数组
    @property (class, readonly, copy) NSArray<NSNumber *> *callStackReturnAddresses;
    //线程调用函数的名字数字
    @property (class, readonly, copy) NSArray<NSString *> *callStackSymbols;
    
    • 判断是否主线程或者获取当前
    //是否是主线程
    @property (readonly) BOOL isMainThread;
    @property (class, readonly) BOOL isMainThread; // reports whether current thread is main
    
    //获取主线程
    @property (class, readonly, strong) NSThread *mainThread;
    
    //获取当前线程
    @property (class, readonly, strong) NSThread *currentThread;
    

    实例方法

    • 线程启动,实例化的线程需要手动启动,此时线程进入就绪状态
    - (void)start;
    
    NSThread* thread = [[NSThread alloc] initWithTarget:self selector:@selector(doSomething:) object:@"方式1"];
    [thread start];
    
    • 线程取消。调用-cancel方法并不会立刻取消线程,它仅仅是将cancelled属性设置为YES。cancelled也仅仅是一个用于记录状态的属性。线程取消的功能需要我们在main函数中自己实现
    - (void)cancel;
    
    [thread cancel];
    
    • 如果创建NSThread时不指定target或者selector,那么main函数就会直接推出,如果都指定了,main函数会调用[target selector:argument],执行完成后退出。当调用start时,start方法内部调用的就是main方法
    - (void)main;   // thread body method
    
    [thread main];
    

    类方法

    • 线程阻塞(暂停)
    + (void)sleepUntilDate:(NSDate *)date;
    + (void)sleepForTimeInterval:(NSTimeInterval)ti;
    
    • 强制停止线程,线程进入死亡状态。该方法属于核弹级别终极API,调用之后会立即终止线程,即使任务还没有执行完成也会中断。这就非常有可能导致内存泄露等严重问题,所以一般不推荐使用
    + (void)exit;
    
    • 设置线程调度优先级 priority(优先级)。调度优先级的取值范围是0.0 - 1.0默认0.5,值越大,优先级越高。
    + (double)threadPriority;
    + (BOOL)setThreadPriority:(double)p;
    

    GCD

    dispatch_after函数

    dispatch_after能让我们添加进队列的任务延时执行,该函数并不是在指定时间后执行处理,而只是在指定时间后追加处理到dispatch_queue

    /*
    参数1 when:任务在何时开始执行
    参数2 queue:执行任务的队列
    参数3 block:需要执行的任务
    */
    void
    dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);
            
    //示例
    dispatch_queue_t queue = dispatch_queue_create("hqThread", DISPATCH_QUEUE_SERIAL);
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2*NSEC_PER_SEC), queue, ^{
        NSLog(@"test dispatch_after");
    });
    

    dispatch_once函数

    dispatch_once保证在App运行期间,block中的代码只执行一次。多用于创建单例的情况。

    重点:dispatch_once是线程安全的。

    /*
    参数1 predicate:用于检查该代码块是否已经被调度的谓词,虽然它是一个长整形,但是它是作为BOOL使用,且当值超过
    参数2 block:需要执行的任务
    */
    void
    dispatch_once(dispatch_once_t *predicate, DISPATCH_NOESCAPE dispatch_block_t block);
    
    //示例
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSLog(@"创建单例");
    });
    

    dispatch_apply函数

    该函数按指定的次数将指定的Block追加到指定的Dispatch Queue中,并等待全部处理执行结束。

    注意:该函数是需要等待全部任务执行完成之后再继续函数后面的操作,效果与dispatch_sync类似,因此,在指定执行任务的线程时,需要防止死锁。

    /*
    参数1 iterations:指定任务执行的次数
    参数2 queue:执行任务的线程
    参数3 block:需要执行的任务,该block有个入参,表示当前是第几次执行
    */
    void
    dispatch_apply(size_t iterations,
            dispatch_queue_t DISPATCH_APPLY_QUEUE_ARG_NULLABILITY queue,
            DISPATCH_NOESCAPE void (^block)(size_t));
    
    //示例
    dispatch_apply(3, dispatch_queue_create("hqThread", DISPATCH_QUEUE_CONCURRENT), ^(size_t a) {
        NSLog(@"[%zu] current thread:%@", a, [NSThread currentThread]);
    });
    

    dispatch_group_t 任务组

    它的主要用于监听管理任务组中任务完成情况。与dispatch_group_notify函数配合,可以在任务完成后做一些操作处理。

    常用方法如下:

    // 创建一个任务组
    dispatch_group_t dispatch_group_create(void);
    
    // 将任务添加到任务组里,并且异步执行
    void dispatch_group_async(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);
    
    // 表示接下来手动向任务组添加任务,即任务组中的任务数+1
    void dispatch_group_enter(dispatch_group_t group);
    
    // 任务组中任务数-1与dispatch_group_enter必须成对出现
    void dispatch_group_leave(dispatch_group_t group);
    
    // 等待之前任务执行完成后才继续执行
    intptr_t dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout);
    
    // 当任务组中任务完成,会出发出发此方法的block
    void dispatch_group_notify(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);
    

    接下来分别通过示例来说任务组的使用进行说明。

    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_queue_create("hqThread", DISPATCH_QUEUE_SERIAL);
        
    dispatch_group_async(group, queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"[任务1] %@", [NSThread currentThread]);
    });
        
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"[任务2] %@", [NSThread currentThread]);
    });
        
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"[任务3] %@", [NSThread currentThread]);
        dispatch_group_leave(group);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"wait start");
        dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC*6));
        NSLog(@"wait end");
    });
        
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:4];
        NSLog(@"[任务4] %@", [NSThread currentThread]);
    });
        
    dispatch_group_notify(group, queue, ^{
        NSLog(@"所有任务执行完成");
    });
        
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:4];
        NSLog(@"[任务5] %@", [NSThread currentThread]);
    });
    
    //当任务的队列为DISPATCH_QUEUE_SERIAL时,执行结果:
    2021-04-16 16:27:21.101260+0800 Demo[87930:20756012] [任务1] <NSThread: 0x600003759880>{number = 7, name = (null)}
    2021-04-16 16:27:23.106189+0800 Demo[87930:20756012] [任务2] <NSThread: 0x600003759880>{number = 7, name = (null)}
    2021-04-16 16:27:25.111495+0800 Demo[87930:20756012] [任务3] <NSThread: 0x600003759880>{number = 7, name = (null)}
    2021-04-16 16:27:25.111771+0800 Demo[87930:20756012] wait start
    2021-04-16 16:27:31.112103+0800 Demo[87930:20756012] wait end
    2021-04-16 16:27:35.116521+0800 Demo[87930:20756012] [任务4] <NSThread: 0x600003759880>{number = 7, name = (null)}
    2021-04-16 16:27:39.119116+0800 Demo[87930:20756012] [任务5] <NSThread: 0x600003759880>{number = 7, name = (null)}
    2021-04-16 16:27:39.119395+0800 Demo[87930:20756012] 所有任务执行完成
    
    //当任务的队列为DISPATCH_QUEUE_CONCURRENT时,执行结果:
    2021-04-16 16:48:48.220717+0800 Demo[88208:20774077] wait start
    2021-04-16 16:48:50.223659+0800 Demo[88208:20774082] [任务1] <NSThread: 0x60000139d900>{number = 3, name = (null)}
    2021-04-16 16:48:50.223659+0800 Demo[88208:20774080] [任务2] <NSThread: 0x6000013e10c0>{number = 6, name = (null)}
    2021-04-16 16:48:50.223659+0800 Demo[88208:20774083] [任务3] <NSThread: 0x6000013e5f00>{number = 7, name = (null)}
    2021-04-16 16:48:50.223970+0800 Demo[88208:20774077] wait end
    2021-04-16 16:48:50.223979+0800 Demo[88208:20774082] 所有任务执行完成
    2021-04-16 16:48:52.222427+0800 Demo[88208:20774078] [任务5] <NSThread: 0x6000013e06c0>{number = 9, name = (null)}
    2021-04-16 16:48:52.222427+0800 Demo[88208:20774079] [任务4] <NSThread: 0x6000013e0a80>{number = 4, name = (null)}
    

    总结:

    1. dispatch_group_async函数是自动将任务添加到任务组中。
    2. dispatch_group_enter函数是手动将任务添加到任务组中。
    3. 当通过dispatch_group_enter函数被调用后,dispatch_group_leave函数调用前,其间的所有任务都被添加到任务组中。
    4. 当任务所在的队列为串行队列时,会等待group关联队列中所有任务执行完成之后才会发出notify信号,调用dispatch_group_notify函数,但是,当group组中的任务执行完成之后,会执行dispatch_group_wait函数。

    dispatch_barrier_sync & dispatch_barrier_async

    栅栏函数,主要有两种使用场景:串行队列、并发队列

    • 栅栏函数 + 串行队列
    dispatch_queue_t queue = dispatch_queue_create("hqThread", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"[任务1] %@", [NSThread currentThread]);
    });
        
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"[任务2] %@", [NSThread currentThread]);
    });
        
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"[任务3] %@", [NSThread currentThread]);
    });
        
    dispatch_barrier_async(queue, ^{
        NSLog(@"------------栅栏任务------------%@", [NSThread currentThread]);
    });
        
    dispatch_async(queue, ^{
        NSLog(@"[任务4] %@", [NSThread currentThread]);
    });
        
    dispatch_async(queue, ^{
        NSLog(@"[任务5] %@", [NSThread currentThread]);
    });
    
    //执行结果
    2021-04-19 09:46:18.453361+0800 Demo[13733:22451112] [任务1] <NSThread: 0x6000014b6c00>{number = 6, name = (null)}
    2021-04-19 09:46:20.458536+0800 Demo[13733:22451112] [任务2] <NSThread: 0x6000014b6c00>{number = 6, name = (null)}
    2021-04-19 09:46:22.463882+0800 Demo[13733:22451112] [任务3] <NSThread: 0x6000014b6c00>{number = 6, name = (null)}
    2021-04-19 09:46:22.464409+0800 Demo[13733:22451112] ------------栅栏任务------------<NSThread: 0x6000014b6c00>{number = 6, name = (null)}
    2021-04-19 09:46:22.464688+0800 Demo[13733:22451112] [任务4] <NSThread: 0x6000014b6c00>{number = 6, name = (null)}
    2021-04-19 09:46:22.464940+0800 Demo[13733:22451112] [任务5] <NSThread: 0x6000014b6c00>{number = 6, name = (null)}
    
    • 栅栏函数 + 并发队列
    dispatch_queue_t queue = dispatch_queue_create("hqThread", DISPATCH_QUEUE_CONCURRENT);//DISPATCH_QUEUE_CONCURRENT
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"[任务1] %@", [NSThread currentThread]);
    });
        
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"[任务2] %@", [NSThread currentThread]);
    });
        
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"[任务3] %@", [NSThread currentThread]);
    });
        
    dispatch_barrier_async(queue, ^{
        NSLog(@"------------栅栏任务------------%@", [NSThread currentThread]);
    });
        
    dispatch_async(queue, ^{
        NSLog(@"[任务4] %@", [NSThread currentThread]);
    });
        
    dispatch_async(queue, ^{
        NSLog(@"[任务5] %@", [NSThread currentThread]);
    });
    
    //执行结果
    2021-04-19 09:47:09.266069+0800 Demo[13753:22452474] [任务2] <NSThread: 0x60000192c940>{number = 5, name = (null)}
    2021-04-19 09:47:09.266121+0800 Demo[13753:22452477] [任务1] <NSThread: 0x60000192c240>{number = 8, name = (null)}
    2021-04-19 09:47:09.266191+0800 Demo[13753:22452478] [任务3] <NSThread: 0x6000019212c0>{number = 3, name = (null)}
    2021-04-19 09:47:09.266493+0800 Demo[13753:22452478] ------------栅栏任务------------<NSThread: 0x6000019212c0>{number = 3, name = (null)}
    2021-04-19 09:47:09.266699+0800 Demo[13753:22452478] [任务4] <NSThread: 0x6000019212c0>{number = 3, name = (null)}
    2021-04-19 09:47:09.266762+0800 Demo[13753:22452474] [任务5] <NSThread: 0x60000192c940>{number = 5, name = (null)}
    

    从上面代码和结果分析可知,dispatch_barrier_async函数的主要作用是等待队列中,位于dispatch_barrier_async函数前的任务完成之后,再执行dispatch_barrier_async函数后的任务。

    在串行队列中,任务本身就是按顺序执行,因此,栅栏函数的作用不明显。而在并发队列中,使用栅栏函数可以很好的控制队列内任务执行的顺序。

    重点:dispatch_barrier_asyncdispatch_barrier_sync
    相同点:都是起到栅栏的作用,必须等待栅栏前面的任务执行完成之后再执行栅栏之后的任务。
    不同点:dispatch_barrier_sync函数不仅会阻塞队列任务的执行,也会阻塞线程的执行,阻塞线程执行同dispatch_sync函数类似,也可能会引起死锁。所以dispatch_barrier_sync函数需要慎用。

    栅栏函数中尽量使用自定义队列,因为使用全局队列是无效的,因为使用全局队列时由于对全局队列造成堵塞,可能致使系统其他调用全局队列的地方也堵塞从而导致崩溃。

    dispatch_semaphore_t 信号量

    信号量主要用作同步锁,用于控制GCD最大并发数。常见的函数有:

    dispatch_semaphore_create():创建信号量dispatch_semaphore_t
    
    dispatch_semaphore_wait():信号量减1.当信号量< 0时会阻塞当前线程,根据传入的等待时间决定接下来的操作——如果永久等待将等到信号(signal)才执行下去
    
    dispatch_semaphore_signal():信号量加1.当信号量>= 0 会执行wait之后的代码.
    

    示例代码如下:

    dispatch_queue_t queue = dispatch_queue_create("hqThread", DISPATCH_QUEUE_CONCURRENT);
            
    for (int i = 0; i < 10; i++) {
        dispatch_async(queue, ^{
            NSLog(@"当前 - %d, 线程 - %@", i, [NSThread currentThread]);
        });
    }
    
    //利用信号量来改写
    dispatch_semaphore_t sem = dispatch_semaphore_create(0);
    for (int i = 0; i < 10; i++) {
        dispatch_async(queue, ^{
            NSLog(@"当前 - %d, 线程 - %@", i, [NSThread currentThread]);
    
            dispatch_semaphore_signal(sem);
        });
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
    }
    

    如果当创建信号量时传入值为1又会怎么样呢?
    答:当i=0时,开启线程去执行任务后,wait信号量-1,但是此时信号量为0不会阻塞线程,所以进入i=1;
    当i=1时,开启线程去执行任务后,wait信号量-1,此时信号量为-1阻塞线程,它将等待signal(即信号量+1)后再执行下去;
    因此,若是并发队列时,当创建信号量为1时,任务1和任务2执行顺序不固定。

    dispatch_source_t

    dispatch_source_t主要用于计时操作,其原因是因为它创建的timer不依赖于RunLoop,且计时精准度比NSTimer高。

    在iOS开发中一般使用NSTimer来处理定时逻辑,但NSTimer是依赖Runloop的,而Runloop可以运行在不同的模式下。如果NSTimer添加在一种模式下,当Runloop运行在其他模式下的时候,定时器就挂机了;假如当前Runloop处于阻塞状态,NSTimer触发时间就会推迟到下一个Runloop周期。因此NSTimer在计时上会有误差,并不是特别精确,而GCD定时器不依赖Runloop,计时精度要高很多。

    dispatch_source是一种基本的数据类型,可以用来监听一些底层的系统事件,如:

    • Timer Dispatch Source:定时器事件源,用来生成周期性的通知或回调

    • Signal Dispatch Source:监听信号事件源,当有UNIX信号发生时会通知

    • Descriptor Dispatch Source:监听文件或socket事件源,当文件或socket数据发生变化时会通知

    • Process Dispatch Source:监听进程事件源,与进程相关的事件通知

    • Mach port Dispatch Source:监听Mach端口事件源

    • Custom Dispatch Source:监听自定义事件源

    Dispatch Source一共可以监听六类事件,分为11个类型:

    • DISPATCH_SOURCE_TYPE_DATA_ADD:属于自定义事件,可以通过dispatch_source_get_data函数获取事件变量数据,在我们自定义的方法中可以调用dispatch_source_merge_data函数向Dispatch Source设置数据,下文中会有详细的演示。

    • DISPATCH_SOURCE_TYPE_DATA_OR:属于自定义事件,用法同上面的类型一样。

    • DISPATCH_SOURCE_TYPE_MACH_SEND:Mach端口发送事件。

    • DISPATCH_SOURCE_TYPE_MACH_RECV:Mach端口接收事件。

    • DISPATCH_SOURCE_TYPE_PROC:与进程相关的事件。

    • DISPATCH_SOURCE_TYPE_READ:读文件事件。

    • DISPATCH_SOURCE_TYPE_WRITE:写文件事件。

    • DISPATCH_SOURCE_TYPE_VNODE:文件属性更改事件。

    • DISPATCH_SOURCE_TYPE_SIGNAL:接收信号事件。

    • DISPATCH_SOURCE_TYPE_TIMER:定时器事件。

    • DISPATCH_SOURCE_TYPE_MEMORYPRESSURE:内存压力事件

    主要使用的API:

    • dispatch_source_create: 创建事件源
    /*
    * 参数1(type):第一个参数用于标识Dispatch Source要监听的事件类型,共有11个类型
    * 参数2(handle):取决于要监听事件的类型,比如监听的是Mach端口相关的事件,那该参数就是mach_port_t类型的Mach端口号;
    * 参数3(mask):取决于要监听事件的类型,比如监听文件属性更改的事件,那么该参数就是标识文件的哪个属性,如:DISPATCH_VNODE_RENAME
    * 参数4(queue):回调函数所在的队列
    */
    dispatch_source_t dispatch_source_create(dispatch_source_type_t type, uintptr_t handle, uintptr_t mask, dispatch_queue_t _Nullable queue);
        
    //示例
    dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    
    • dispatch_source_set_timer:给监听事件类型为DISPATCH_SOURCE_TYPE_TIMER的Dispatch Source设置相关属性
    /*
    * 参数1(source):该参数为目标Dispatch Source
    * 参数2(start):定时器的起始时间
    * 参数3(interval):定时器的间隔时间,单位为纳秒
    * 参数4(leeway):间隔时间的精度,单位为纳秒
    */
    void dispatch_source_set_timer(dispatch_source_t source, dispatch_time_t start, uint64_t interval, uint64_t leeway);
    
    //示例
    dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1.0*NSEC_PER_SEC, 0.1*NSEC_PER_SEC);
    
    • dispatch_source_set_event_handler: 设置数据源回调。
      当Dispatch Source监听到事件时,会调用指定的回调函数或者闭包,该回调函数和闭包就是Dispatch Source的事件处理器,我们可以通过dispatch_source_set_event_handler或者dispatch_source_set_event_handler_f给创建好的Dispatch Source设置处理器,其中前者是设置闭包形式的处理器,后者是设置函数形式的处理器。
    /*
    * 参数1(source):目标Dispatch Source
    * 参数2(handler):事件处理器
    */
    void dispatch_source_set_event_handler(dispatch_source_t source, dispatch_block_t _Nullable handler);
    
    //示例
    dispatch_source_set_event_handler(self.timer, ^{
        NSLog(@"GCDTimer");
    });
    
    • dispatch_source_merge_data: 设置事件源数据。
      将数据合并到Dispatch Source中,并将handler提交到目标队列queue再执行。
    /*
    * 参数1(source):目标Dispatch Source
    * 参数2(value):需要合并的数据
    */
    void
    dispatch_source_merge_data(dispatch_source_t source, uintptr_t value);
    
    //示例
    dispatch_source_merge_data(source, 100)
    
    • dispatch_source_get_data: 获取事件源数据
    /*
    * 参数1(source):目标Dispatch Source
    * 返回值:Dispatch Source待处理的数据
    */
    uintptr_t dispatch_source_get_data(dispatch_source_t source);
    
    //示例
    dispatch_source_get_data(source)
    
    • dispatch_resume: 继续
      刚创建好的Dispatch Source是处于暂停状态的,需要手动调用dispatch_resume函数将其启动。
    /*
    * 参数1(object):非活动的、未挂起的调度源对象
    */
    void dispatch_resume(dispatch_object_t object);
    
    //示例
    dispatch_resume(timer)
    
    • dispatch_suspend: 挂起,将Dispatch Source暂时挂起,挂起后可通过dispatch_resume重新激活。
    /*
    * 参数1(object):调度源对象
    */
    void dispatch_suspend(dispatch_object_t object);
    
    //示例
    dispatch_suspend(timer)
    

    NSOperation

    NSOperation基于GCD的更高一层的封装,NSOperation需要配合NSOperationQueue来实现多线程。
    NSOperation实现多线程步骤如下:

    1. 创建任务,将需要执行的任务封装到NSOperation对象中
    2. 创建队列,创建NSOperationQueue队列
    3. 将任务添加到队列中。将NSOperation对象添加到NSOperationQueue中。

    基本使用:

    NSBlockOperation* operation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"NSBlockOperation");
    }];
    NSOperationQueue* queue = [[NSOperationQueue alloc] init];
    [queue addOperation:operation];
    

    NSOperation类是一个抽象类,实际使用中使用的是NSOperation的子类。

    • NSInvocationOperation类
    NSOperationQueue* queue = [[NSOperationQueue alloc] init];
    NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(doSomething:) object:@"NSInvocationOperation"];
    [queue addOperation:invocationOperation];
    
    • NSBlockOperation类
    NSOperationQueue* queue = [[NSOperationQueue alloc] init];
    NSBlockOperation* operation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"NSBlockOperation1 - %@", [NSThread currentThread]);
    }];
        
    [operation addExecutionBlock:^{
        NSLog(@"NSBlockOperation2 - %@", [NSThread currentThread]);
    }];
        
    [operation addExecutionBlock:^{
        NSLog(@"NSBlockOperation3 - %@", [NSThread currentThread]);
    }];
        
    [queue addOperation:operation];
    
    • 自定义NSOperation类
      自定义NSOperation类可以通过实现内部相应的方法来封装任务。
    @interface HQOperation : NSOperation
    @end
    
    @implementation HQOperation
    - (void)main{
        for(int i = 0; i < 3; i++){
            NSLog(@"[%d] %@", i, [NSThread currentThread]);
        }
    }
    @end
    
    HQOperation* operation = [[HQOperation alloc] init];
    [operation start];
    

    NSOperation设置优先级

    NSOperationQueue* queue = [[NSOperationQueue alloc] init];
    NSBlockOperation* operation1 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:3];
        NSLog(@"NSBlockOperation1 - %@", [NSThread currentThread]);
    }];
        
    NSBlockOperation* operation2 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:3];
        NSLog(@"NSBlockOperation2 - %@", [NSThread currentThread]);
    }];
    
    //设置了最高优先级
    operation2.qualityOfService = NSQualityOfServiceUserInteractive;
        
    [queue addOperation:operation1];
    [queue addOperation:operation2];
    
    //执行结果
    2021-04-20 15:21:42.698211+0800 Demo[24004:23167490] NSBlockOperation2 - <NSThread: 0x6000010df780>{number = 7, name = (null)}
    2021-04-20 15:21:42.701567+0800 Demo[24004:23167487] NSBlockOperation1 - <NSThread: 0x600001088200>{number = 8, name = (null)}
    

    设置并发数,在GCD的使用中,可以通过信号量的方式来设置并发数,同样,在NSOperation中,也可以轻松设置并发数。

    NSOperationQueue* queue = [[NSOperationQueue alloc] init];
    //设置最大并发数为3,即同时有3条线程执行任务
    queue.maxConcurrentOperationCount = 3;
    for(int i = 0; i<10; i++){
        [queue addOperationWithBlock:^{
            NSLog(@"[%d] -- %@", i, [NSThread currentThread]);
        }];
    }
    
    //执行结果
    2021-04-20 15:29:07.062726+0800 Demo[24082:23173786] [0] -- <NSThread: 0x600002dc1900>{number = 3, name = (null)}
    2021-04-20 15:29:07.062730+0800 Demo[24082:23173778] [1] -- <NSThread: 0x600002dcd280>{number = 7, name = (null)}
    2021-04-20 15:29:07.062780+0800 Demo[24082:23173787] [2] -- <NSThread: 0x600002dcc1c0>{number = 6, name = (null)}
    2021-04-20 15:29:07.062934+0800 Demo[24082:23173786] [3] -- <NSThread: 0x600002dc1900>{number = 3, name = (null)}
    2021-04-20 15:29:07.062988+0800 Demo[24082:23173787] [5] -- <NSThread: 0x600002dcc1c0>{number = 6, name = (null)}
    2021-04-20 15:29:07.062994+0800 Demo[24082:23173778] [4] -- <NSThread: 0x600002dcd280>{number = 7, name = (null)}
    2021-04-20 15:29:07.063117+0800 Demo[24082:23173786] [6] -- <NSThread: 0x600002dc1900>{number = 3, name = (null)}
    2021-04-20 15:29:07.063154+0800 Demo[24082:23173787] [7] -- <NSThread: 0x600002dcc1c0>{number = 6, name = (null)}
    2021-04-20 15:29:07.063269+0800 Demo[24082:23173778] [8] -- <NSThread: 0x600002dcd280>{number = 7, name = (null)}
    2021-04-20 15:29:07.064370+0800 Demo[24082:23173786] [9] -- <NSThread: 0x600002dc1900>{number = 3, name = (null)}
    

    在NSOperation中,还可以添加依赖关系

    NSOperationQueue* queue = [[NSOperationQueue alloc] init];
    NSBlockOperation* operation1 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:3];
        NSLog(@"请求数据1 - %@", [NSThread currentThread]);
    }];
        
    NSBlockOperation* operation2 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:3];
        NSLog(@"根据数据1,请求数据2 - %@", [NSThread currentThread]);
    }];
        
    NSBlockOperation* operation3 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:3];
        NSLog(@"根据数据2,请求数据3 - %@", [NSThread currentThread]);
    }];
        
    [operation2 addDependency:operation1];
    [operation3 addDependency:operation2];
    [queue addOperation:operation1];
    [queue addOperation:operation2];
    [queue addOperation:operation3];
    
    //执行结果
    2021-04-20 15:33:51.102025+0800 Demo[24165:23179016] 请求数据1 - <NSThread: 0x600002502380>{number = 7, name = (null)}
    2021-04-20 15:33:54.104030+0800 Demo[24165:23179022] 根据数据1,请求数据2 - <NSThread: 0x600002572740>{number = 6, name = (null)}
    2021-04-20 15:33:57.108527+0800 Demo[24165:23179016] 根据数据2,请求数据3 - <NSThread: 0x600002502380>{number = 7, name = (null)}
    

    相关文章

      网友评论

        本文标题:OC底层原理 - 21 NSThread&GCD& NSOper

        本文链接:https://www.haomeiwen.com/subject/sqoelltx.html