将来跳槽用

异步实现的记录总结

2016-06-03  本文已影响111人  懒得起名的伊凡

GCD

Grand Central Dispatch(GCD)是异步执行任务的技术之一。一般讲应用程序中记述的线程管理用的代码在系统级中实现,也就是基于iOS的UNX内核。多线程编程会导致很多问题,比如 资源竞争、死锁和太多线程导致消耗大量内存 等。

开发者要做的只是定义想执行的任务并追加到适当的Dispatch Queue中

Dispatch Queue

介绍

“Dispatch Queue”就是执行处理的等待队列,他会按照追加的顺序(先进先出 FIFO)执行处理。

两种Dispatch Queue

根据是否有数据竞争来判定使用哪种类型。

得到Dispatch Queue
第一种方法

使用dispatch_queue_create来得到,自己的。

关于内存:会自动处理
在dispatch_async函数中追加Block到Dispatch Queue后,Block会通过dispatch_retain 函数持有Dispatch Queue,所以即使Dispatch Queue被立即释放,他也不会被销毁。

第二种方法

获取系统标准提供的Dispatch Queue

变更生成的Dispatch Queue的执行优先级

dispatch_queue_create生成的 Dispatch Queue,不管是Serial Dispatch Queue还是 Concurrent Dispatch Queue,都使用与默认优先级Global Dispatch Queue相同执行优先级的线程。变更优先级使用dispatch_set_target_queue函数。
例如

dispatch_queue_t serialDispatchQueue = dispatch_queue_create("com.jiayoufang.queue", NULL);
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_set_target_queue(serialDispatchQueue, globalQueue);

指定时间后执行处理

使用dispatch_after来实现。

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC);
dispatch_after(time, dispatch_get_main_queue(), ^{
    
});

需要注意的是:dispatch_after并不是在指定时间后执行处理,而是在指定时间追加处理到Dispatch Queue。

dispatch_after的底层其实是用dispatch_source实现的,不依赖runloop。

dispatch_time_t的创建

“ull”是C语言的数值字面量,是显式表明类型时使用的字符创(表示“unsigned long long)。

Dispatch Group

在追加到Dispatch Queue的多个处理全部结束后想执行结束处理。

 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, ^{
    NSLog(@"Block0");
});
dispatch_group_async(group, queue, ^{
    NSLog(@"Block1");
});
dispatch_group_async(group, queue, ^{
    NSLog(@"Block2");
});

dispatch_group_notify(group, queue, ^{
    NSLog(@"Done");
});

如果仅仅只是验证是否结束,也可以使用dispatch_group_wait

dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

第二个参数表示等待的时间(超时时间)。他属于dispatch_time_t的值。dispatch_group_wait是有返回值的,如果返回值不为0,说明虽然经过了指定的时间,但是属于Dispatch Queue的某一个处理还在进行中。如果返回值为0,说明全部处理执行结束。
时间指定为DISPATCH_TIME_FOREVER,返回值肯定为0。
指定为DISPATCH_TIME_NOW,不用等待,直接判定属于Dispatch Group的处理是否执行结束。

需要注意的是,dispatch_group_wait,他会阻塞当前线程,如有必要,可以使用dispatch_async将整个方法放入后台队列以避免线程阻塞。

或者使用dispatch_group_notify来观察Dispatch Group中的任务是否执行完。

多个异步请求判断都结束了

参考了叶大神的在这里

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_enter(group);
    dispatch_async(dispatch_get_main_queue(), ^{
        int i = 0;
        while (i < 1000) {
            NSLog(@"Group1 : %d",i++);
        }
        dispatch_group_leave(group);
    });
    
    
    dispatch_group_enter(group);
    dispatch_async(dispatch_get_main_queue(), ^{
        int i = 0;
        while (i < 1000) {
            NSLog(@"AAAAAA  Group : %d",i++);
        }
        dispatch_group_leave(group);
    });
    
    dispatch_group_notify(group, queue, ^{
        NSLog(@"Done");
    });

dispatch_barrier_async

可以达到 等待追加到Concurrent Dispatch Queue上的并行执行的处理全部结束之后,再将指定的处理追加到Concurrent Dispatch Queue中。然后在由dispatch_barrier_async函数追加的处理执行完毕之后,Concurrent Dispatch Queue再恢复一般的动作。

dispatch_queue_t queue = dispatch_queue_create("com.example.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
    NSLog(@"A");
});
dispatch_async(queue, ^{
    NSLog(@"B");
});
dispatch_async(queue, ^{
    NSLog(@"C");
});
dispatch_barrier_async(queue, ^{
    NSLog(@"A、B、C 先执行完");
});
dispatch_async(queue, ^{
    NSLog(@"D");
});
dispatch_async(queue, ^{
    NSLog(@"E");
});
dispatch_barrier_async(queue, ^{
    NSLog(@"D、E 先执行完");
});
dispatch_async(queue, ^{
    NSLog(@"F");
});
dispatch_async(queue, ^{
    NSLog(@"G");
});

dispatch_apply

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

dispatch_queue_t queue = dispatch_queue_create("com.example.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_apply(10, queue, ^(size_t index) {
    NSLog(@"index : %zd",index);
});

例如对NSArray类对象的所有元素执行处理,不必一个一个编写for循环部分。

Note:由于dispatch_apply会和dispatch_sync函数相同,会等待处理结束。因此,推荐在dispatch_async函数中非同步的执行dispatch_apply函数。

线程挂起和恢复

挂起指定的Dispatch Queue

dispatch_suspend(queue);

恢复指定的Dispatch Queue

dispatch_resume(queue);

这些函数对已经执行过的处理没有影响。挂起后,尚未执行的处理会停止执行,恢复之后这些处理能够继续执行。

dispatch_semaphore_t

创建一个信号量。参数指定信号量的起始值,这个数字是可以访问的信号量。需要注意的是,如果初始化为0,那么在使用信号量时必然会被阻塞,也是使用这个方式来解决测试用例时的异步代码问题。

加锁的实现

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t sempaphore = dispatch_semaphore_create(1);
NSMutableArray *array = [[NSMutableArray alloc]init];
for (NSInteger i = 0 ; i < 10000 ; i++) {
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(sempaphore, DISPATCH_TIME_FOREVER);
        
        [array addObject:[NSNumber numberWithInteger:i]];
        
        dispatch_semaphore_signal(sempaphore);
    });
}

在使用Xcode中的 Product/Test 运行测试时,测试是在主线程运行的,所以可以假设所有的测试都是串行发生的。在一个给定的测试方法运行完成,XCTest方法将考虑此次测试已结束,并进入下一个测试,这就意味着任何来自目前一个测试的异步代码会在下一个测试运行时继续发生。解决这个问题:

- (void)testExample {
    // This is an example of a functional test case.
    // Use XCTAssert and related functions to verify your tests produce the correct results.
    
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    
    dispatch_queue_t queue = dispatch_queue_create("com.ivan.queue", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"执行了测试");
    dispatch_async(queue, ^{
        for (NSUInteger i = 0; i < 888; i++) {
            NSLog(@"********* : %zd",i);
        }
        dispatch_semaphore_signal(semaphore);
    });
    dispatch_time_t timeoutTime = dispatch_time(DISPATCH_TIME_NOW, 1);
    if(dispatch_semaphore_wait(semaphore, timeoutTime)){
        XCTFail("Time out");
    }
}

Dispatch Source

GCD 除了主要的Dispatch Queue外,还有Dispatch Source。
实现一个简单的定时器,说明用法

    - (void)test7{
    //实现一个定时器的例子
    __block NSInteger count = 0;
    //指定 DISPATCH_SOURCE_TYPE_TIMER,作成 Dispatch Source。在定时器经过指定时间时设定Main Dispatch 为追歼处理的Dispatch Queue。
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
    //将定时器设置为2秒后,允许延迟1秒
    dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 2ull * NSEC_PER_SEC, 1ull * NSEC_PER_SEC);
    //指定定时器指定时间内执行的处理
    dispatch_source_set_event_handler(timer, ^{
        
        NSLog(@"执行");
        if (count >= 4) {
            NSLog(@"Count >= 4");
            //执行完,之后,取消Dispatch Source
            dispatch_source_cancel(timer);
        }else{
            NSLog(@"Count < 4");
            count++;
        }
    });
    
    //指定取消Dispatch Source时的处理
    dispatch_source_set_cancel_handler(timer, ^{
        NSLog(@"cancel");
        //释放自身
     //        dispatch_release(timer);
    });
    
    //启动Dispatch Source
    dispatch_resume(timer);
    }

另外,dispatch_source_t的本质其实是一个OC对象,不要被骗了,可以打印验证(这是个方法,要记住

//打印出来的内容是类名+内存地址
    NSLog(@"%@",timer);

如果声明全局变量,需要强引用

@property(nonatomic,strong) dispatch_source_t timer;

NSOperation

NSOperation是基于GCD实现的,由于是面向对象的,可能看着会更舒服一些。

实现步骤

NSOperation说明

NSOperation是一个抽象类,并不具备封装操作的能力,必须使用它的子类

Note:默认情况下,调用了start方法之后并不会开一条新线程去执行操作,而是在当前线程同步执行操作。只有当NSOperation添加到一个NSOperationQueue中,才会异步执行操作。但NSBlockOperation只要封装数大于1,就会异步执行操作。

使用NSInvocationOperation

- (void)testInvocationOperation
{
    NSInvocationOperation *op = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(operation) object:nil];
    [op start];
}

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

最后的打印结果是: ---<NSThread: 0x7fb5e9707b20>{number = 1, name = main}
说明这样操作的话他并没有开启新的线程。

使用NSBlockOperation

如果只是添加一个block的话,也是不会开启新的线程来进行操作的

- (void)testBlockOperation
{
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"1---%@",[NSThread currentThread]);
    }];
    
    [op start];
}

但是如果添加多个block则会开启新的线程,但不是一定开启

- (void)testBlockOperation
{
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"1---%@",[NSThread currentThread]);
    }];
    
    
    [op addExecutionBlock:^{
        NSLog(@"2---%@",[NSThread currentThread]);
    }];
    
    [op addExecutionBlock:^{
        NSLog(@"3---%@",[NSThread currentThread]);
    }];
    [op start];
}

NSOperationQueue

类型:

使用suspend属性来暂停,已经启动的线程是无法停止的,只能停止接下来要执行的操作。
但是使用cancelAllOperaion也不会停止,所以在自定义NSOperation的时候,我们可以采取一种方法来增强体验,就是没执行完一个长时间的操作,就判定一下是否取消。

#import "CustomOperation.h"

@implementation CustomOperation

- (void)main
{
    for (NSInteger i = 0; i < 1000; i++) {
        NSLog(@"操作1 %zd",i);
    }
    
    //就是这样
    if (self.isCancelled) {
        return;
    }
    
    for (NSInteger i = 0; i < 1000; i++) {
        NSLog(@"操作2 %zd",i);
    }
    
    if (self.isCancelled) {
        return;
    }
    
    for (NSInteger i = 0; i < 1000; i++) {
        NSLog(@"操作3 %zd",i);
    }
}

@end
依赖和监听

依赖就是执行其中一个之后才可以执行。
Note : 实现很简单,主要是要理解是什么意思,还有就是千万不要循环了。但是可以在不同队列之间设置依赖,还是比较牛逼的

监听需要注意的是不一定是在同一条线程中执行,但是是在子线程中的

- (void)testDependency
{
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"operation1");
    }];
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"operation2");
    }];
    NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"operation3");
    }];
    
    operation3.completionBlock = ^(){
        NSLog(@"监听执行完成");
    };
    [operation3 addDependency:operation1];
    [operation2 addDependency:operation3];
    
    [queue addOperation:operation1];
    [queue addOperation:operation2];
    [queue addOperation:operation3];
}
上一篇下一篇

猜你喜欢

热点阅读