多线程编程是软件工程是必备的基础技能之一,对于iOS开发,多线程的技术有很多,有最原始的pthread,面向对象的NSThread,NSOperation,以及被广泛开发者喜爱的GCD,之所以这样,因为它相较于其他,更轻量级,以及简便的api接口等等。
今天这里对于GCD的API所有的接口没有过多的描述,主要选择dispatch_group_t(组队列)和dispatch_semaphore_t(信号量)的简单用法及运用他们做了一个简单实用的同时上传多个本地文件的工具进行论述。
一、dispatch_group_t:
1、概念:就是将多个任务同时提交到同一个队列中执行。
2、比如我们有一种需求,就是只有当任务1,2完成了,才能执行3任务,我们有很多方案,比如NSOperation的队列依赖,我们下面要介绍的dispatch_semaphore_t(信号量),还有dispatch_barrier(栈栏)等等,这里要将的是dispatch_group_notify:
这里需要注意的是, dispatch_group_enter()和 dispatch_group_leave()需要一一对应,否则会发生crash。
当然除了可以异步添加到组并发队列中,也可以同步添加,还可以添加到串行队列中,这里就不一一列举了,你们可以试试。
二、dispatch_semaphore_t信号量:
1、概念:信号量基于计数器的一种多线程同步机制。在多个线程访问共有资源时候,会因为多线程的特性而引发数据出错的问题。
2、原理:先设置一个信号总量,如果信号总量的整形参数是0 ,那么就是没有资源需要等待,我们如果下面执行dispatch_semaphore_wait() 操作,那么相当于线程拥堵,执行信号-1 操作,如果现在是绿灯通行状态,我们会设置 dispatch_semaphore_signal()信号,执行一个信号+1 操作,告诉当前线程,有一个信号可以释放了。
三、多个本地文件上传简易工具:
1、这个工具就是基于上述两个API的技术,将上传的任务添加到group中,然后通过dispatch_semaphore_t信号量来控制最大并发数,默认3,外面可以设置;
2、封装了一个fileModel类,里面包含本地路径、上传的状态、远端路径、上传任务;
3、可以多次添加任务,重复添加同一个本地文件,只会上传一次,上传缓存uploadDic是一个以本地文件路径为key,file model为value的;
4、上传失败后,回将对应的状态标记为上传失败,将是否需要重试上传标记为yes,并在第一次所有的图片上传完成后出发,最多重试3次;
5、可以中途取消上传任务,通过NSURLSessionUploadTask实现。
6、当所有的任务(包括重试任务)完成后,通过dispatch_group_notify通知所有文件上传完成后,block回调NSArray<UploadFileModel*>。
UploadFilesTool.m代码如下:
-(instancetype)init{
if(self= [superinit]) {
self.semo = dispatch_semaphore_create(self.maxQueueCount);
self.queue = dispatch_queue_create("HXWUPLOADQUEUE", DISPATCH_QUEUE_CONCURRENT);
self.group = dispatch_group_create();
self.needRestart=NO;
self.lock= [NSLocknew];
self.reUploadCount=3;
}
return self;
}
///第一次提交待上传的本地文件路径集合
- (void)startLocalFilePaths:(NSArray*)filePaths completion:(CompletionHandler)handler{
if(handler) {
self.handler= handler;
}
for(NSString* filePathinfilePaths) {
UploadFileModel* model = [self.uploadDicobjectForKey:filePath];
if(!model) {
model = [UploadFileModelnew];
model.state=UploadStateWaiting;
model.originFilePath= filePath;
model.remoteUrl=@"";
[self.uploadDicsetObject:modelforKey:filePath];
}
///取消了,再次添加到上传队列,设置为待上传状态
if([model.remoteUrlisEqualToString:@""] && model.state==UploadStateCancel) {
[self updateMode:model state:UploadStateWaiting];
}
}
[self startUpload];
}
///中途提交待上传的本地文件路径集合
- (void)addLocalFilePaths:(NSArray*)filePaths{
[self startLocalFilePaths:filePaths completion:nil];
}
///取消上传
- (void)cancelUploadLocalFilePaths:(NSArray*)filePaths{
for(NSString* filePathinfilePaths) {
UploadFileModel* model = [self.uploadDicobjectForKey:filePath];
if(model) {///置为取消状态,并取消任务
[self updateMode:model state:UploadStateCancel];
if(model.task) {
[model.taskcancel];
}
}
}
}
///开始上传
- (void)startUpload{
[self.locklock];
if (self.needRestart) {
self.needRestart=NO;
}
[self.lockunlock];
__weaktypeof(self) weakSelf =self;
[self.uploadDic.allValuesenumerateObjectsUsingBlock:^(UploadFileModel* _Nonnullobj,NSUIntegeridx,BOOL*_Nonnullstop) {
if (obj.state == UploadStateWaiting ||obj.state == UploadStateFailed) {
obj.state=UploadStateUploading;
///记得enter跟leave一一对应
dispatch_group_enter(weakSelf.group);
dispatch_group_async(weakSelf.group, weakSelf.queue, ^{
///信号量减1,小于0等待
dispatch_semaphore_wait(weakSelf.semo,DISPATCH_TIME_FOREVER);
obj.task= [weakSelf.delegateuploadFile:obj.originFilePathsuceed:^(NSString*remoteUrl) {
[weakSelfupdateMode:objstate:UploadStateSucessed];
obj.remoteUrl= remoteUrl;
obj.task=nil;///任务置空
///信号量加1
dispatch_semaphore_signal(weakSelf.semo);
///记得enter跟leave一一对应
dispatch_group_leave(weakSelf.group);
}failed:^(NSString*des,NSIntegercode) {
if(obj.state==UploadStateUploading) {
///不是主动取消,设置为failed
[weakSelfupdateMode:objstate:UploadStateFailed];
}
obj.task=nil;///任务置空
///信号量加1
dispatch_semaphore_signal(weakSelf.semo);
///记得enter跟leave一一对应
dispatch_group_leave(weakSelf.group);
}progress:^(doubleprogress) {
///备用
}];
});
}
}];
///全部上传完成通知结果
dispatch_group_notify(self.group, dispatch_get_main_queue(), ^{
///上传失败,需要重传,并且次数减1
if(weakSelf.needRestart&& weakSelf.reUploadCount>0) {
weakSelf.reUploadCount--;
[weakSelfstartUpload];
}else{
if(weakSelf.handler) {
weakSelf.handler(weakSelf.uploadDic.allValues);
}
}
});
}
- (void)updateMode:(UploadFileModel*)mode state:(UploadState)state{
[self.locklock];
mode.state= state;
if(state ==UploadStateFailed) {
self.needRestart=YES;
}
[self.lockunlock];
}
-(NSMutableDictionary *)uploadDic{
if(!_uploadDic) {
_uploadDic = [NSMutableDictionary new];
}
return _uploadDic;
}
-(NSUInteger)maxQueueCount{
if (_maxQueueCount == 0) {
_maxQueueCount = 1;
}
return _maxQueueCount;
}