iOS 等待for循环里的异步任务完成再进行其他操作的一个解决办法 -- 信号量+串行队列卡for小循环

转自:https://blog.csdn.net/qq_34417314/article/details/80449484

for循环里的异步操作

开发中经常会遇到这样一些情况,比如:
1.登录失败后的多次登录重连场景。
2.在一个for循环遍历里,有多种异步操作,需要在所有的异步操作完成后,也就是for循环的遍历结束后,再去执行其他操作,但是不能卡主线程,这时候就需要用其他方法了。

我遇到的需求是,在一个for循环里有数据库的查询操作以及网络请求操作,然后将数据库的查询和网络请求的结果添加到一个临时数组中,最后等for循环结束后,将临时数组通过block回传出去。
解决办法如下:

  • 串行队列配合信号量

因为for循环不能卡主线程,所以我们首先要创建一个串行的队列(并发的不可以),然后通过信号量来控制for循环,下面是模拟的一个操作:

 1 printf("处理前创建信号量,开始循环异步请求操作
");
 2 
 3 // 1.创建一个串行队列,保证for循环依次执行
 4         dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
 5 
 6 // 2.异步执行任务
 7 dispatch_async(serialQueue, ^{
 8     // 3.创建一个数目为1的信号量,用于“卡”for循环,等上次循环结束在执行下一次的for循环
 9     dispatch_semaphore_t sema = dispatch_semaphore_create(0);
10    
11     for (int i = 0; i<5; i++) {
12         // 开始执行for循环,让信号量-1,这样下次操作须等信号量>=0才会继续,否则下次操作将永久停止
13     
14         printf("信号量等待中
");
15         // 模拟一个异步任务
16         NSMutableURLRequest *urlRequest = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@"https://github.com"]];
17         NSURLSession *session = [NSURLSession sharedSession];
18         NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:urlRequest completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
19         // 本次for循环的异步任务执行完毕,这时候要发一个信号,若不发,下次操作将永远不会触发
20             [tampArray addObject:@(i)];
21             NSLog(@"本次耗时操作完成,信号量+1 %@
",[NSThread currentThread]);
22             dispatch_semaphore_signal(sema);
23             
24         }];
25         [dataTask resume];
26         dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
27     }
28     
29     NSLog(@"其他操作");
30     for (NSNumber *num in tampArray) {
31         NSLog(@"所有操作完成后的操作--->   %@
",num);
32     }
33     
34 });

以上就是一个简单的卡for循环的实例

  • 实际场景中的应用

我遇到场景:
1.从相册里取若干原图,先获取到缩略图的数组,后根据缩略图信息取原图,这是一个可同步可异步的操作,鉴于卡顿考虑,采用异步取原图的操作,

 1  NSMutableArray *tmpPhotoes = [NSMutableArray array];
 2             // 这里用信号量卡 for 循环 来获取原图,等全部获取完成再刷新UI
 3             dispatch_queue_t serialQueue = dispatch_queue_create("getOriginalQueue", DISPATCH_QUEUE_SERIAL);
 4             
 5             dispatch_async(serialQueue, ^{
 6                 
 7                 dispatch_semaphore_t sema = dispatch_semaphore_create(0);
 8                 
 9                 [assets enumerateObjectsUsingBlock:^(PHAsset *tmpAsset, NSUInteger idx, BOOL * _Nonnull stop) {
10                     
11                     /* 这里换成系统的方法也是一样的,[[PHImageManager defaultManager] requestImageForAsset:asset targetSize:PHImageManagerMaximumSize contentMode:PHImageContentModeAspectFit options:option resultHandler:^(UIImage *result, NSDictionary *info) {
12     }];*/ 
13                         __block int time = 0;  // 由于获取原图会有2次回调,PHImageResultIsDegradedKey为0时才为原图
14                     [[TZImageManager manager] getOriginalPhotoWithAsset:tmpAsset newCompletion:^(UIImage *photo, NSDictionary *info, BOOL isDegraded) {
15                         // 原图
16                         if ([[info jk_stringForKey:PHImageResultIsDegradedKey] isEqualToString:@"0"]) {
17                             [tmpPhotoes addObject:photo];
18                         }
19                         time++;
20                         if (time == 2) {
21                             dispatch_semaphore_signal(sema);
22                         }
23                     }];
24                     
25                     dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
26                     
27                 }];
28 
29                 dispatch_async(dispatch_get_main_queue(), ^{
30                     if (tmpPhotoes.count) {
31                         [self refreshImageDataWithPhotos:tmpPhotoes assets:assets];
32                     }
33                 });
34             });

2.外界多个渠道随时可能来查询一个会话列表数组的数据,但是原始的数组里没有包含想要的东西,需要包装一下再供外界使用。首先需要创建一个条件锁,防止这个列表数据被多方访问造成资源抢夺,其次循环遍历原始数组,通过数据库查询和网络请求操作重新包装会话列表里的数据,最后等待for循环结束,将包装过的数组回传给外界以供使用。

 1 /** 会话列表 */
 2 -(void)getConversationList:(void(^)(NSArray *conversationList))handler {
 3 // 条件锁,若当前资源可获取则进行下一步,否则等待条件锁完成才能访问
 4 if ([self.lock obtain]) {
 5     BBLog(@"BigBang- 获取资源中");
 6     // 会话列表
 7     NSArray *oriList = [[BBConversationManager manager] getConversationList];
 8     
 9     dispatch_queue_t serailQueue = dispatch_queue_create("conversationQueue", DISPATCH_QUEUE_SERIAL);
10     dispatch_async(serailQueue, ^{
11         
12         // 每次重置,防止 dispose 
13         self.sema = nil;
14         self.sema = dispatch_semaphore_create(1);
15         BBLog(@"BigBang- CrashInfo -- 信号量创建 当前线程---%@",[NSThread currentThread]);
16         // 临时数组 存储变换后的会话模型
17         __block NSMutableArray *temList = [NSMutableArray array];
18         if (oriList.count > 0) {
19             [oriList enumerateObjectsUsingBlock:^(RCConversation *conversation, NSUInteger idx, BOOL * _Nonnull stop) {
20                 
21                 NSString *identifier = conversation.targetId;
22                 RCMessage *message = [[RCIMClient sharedRCIMClient] getMessage:conversation.lastestMessageId];
23                 
24                 if ([conversation conversationType] == ConversationType_PRIVATE || [conversation conversationType] == ConversationType_SYSTEM) {
25                     
26                     BBLog(@"BigBang- CrashInfo 信号量-1");
27                     dispatch_semaphore_wait(self.sema, DISPATCH_TIME_FOREVER);
28                     
29                     // 本地存在,直接从本地数据库取 这里是一个数据库的查询操作,是在子线程中进行的
30                     if ([BBUserTable isUserExist:identifier]) {
31                         
32                         BBConversationListModel *model = [[BBConversationListModel alloc] initWithRCMessage:message userInfo:[BBUserTable gerUserWithID:identifier]];
33                         model.unreadMessageCount = conversation.unreadMessageCount;
34                         if ([conversation conversationType] == ConversationType_SYSTEM) {
35                             [[BBConversationManager manager] setConversationToTop:ConversationType_SYSTEM targetId:identifier isTop:YES];
36                         }
37                         
38                         [temList addObject:model];
39                         // 从数据库获取完成 发送信号 继续下一次取值
40                         dispatch_semaphore_signal(self.sema);
41                         BBLog(@"BigBang- CrashInfo 数据库 信号量+1");
42                         
43                     }else {
44                         
45                         //  本地不存在 调接口请求数据 这里是一个网络请求操作,无论请求成功与否,都要将信号量恢复
46                         [[BBDataManager manager] asyncGetUserInfoWithUserID:identifier result:^(NSDictionary *userInfo) {
47                             
48                             BBUserInfo *newUserInfo = [[BBUserInfo alloc] initWithUserID:identifier userName:[NSString stringWithFormat:@"%@",userInfo[@"nickname"]] avatar:[NSString stringWithFormat:@"%@",userInfo[@"avatar"]] level:[NSString stringWithFormat:@"%@",userInfo[@"level"]]];
49                             BBConversationListModel *model = [[BBConversationListModel alloc] initWithRCMessage:message userInfo:newUserInfo];
50                             model.unreadMessageCount = conversation.unreadMessageCount;
51                             
52                             if ([conversation conversationType] == ConversationType_SYSTEM) {
53                                 [[BBConversationManager manager] setConversationToTop:ConversationType_SYSTEM targetId:identifier isTop:YES];
54                             }
55                             
56                             [temList addObject:model];
57                             
58                             BBLog(@"BigBang- CrashInfo 网络请求 信号量+1");
59                             dispatch_semaphore_signal(self.sema);
60                             [BBUserTable saveUserInfoWithModel:newUserInfo];
61                             
62                         } error:^(NSError *error){
63                             
64                             BBLog(@"BigBang- CrashInfo 网络请求失败 信号量+1");
65                             dispatch_semaphore_signal(self.sema);
66                             
67                         }];
68                     }
69                 }
70             }];
71             // 结束操作后,将锁打开,以供后面的访问
72             if (handler) {
73                 if (temList.count) {
74                     dispatch_async(dispatch_get_main_queue(), ^{
75                         handler(temList);
76                     });
77                     BBLog(@"BigBang- CrashInfo 获取数据完毕,回传显示  当前线程--%@ ",[NSThread currentThread]);
78                 }
79             }
80             [self.lock finish];
81         }else {
82             if (handler) {
83                 handler(nil);
84             }
85             [self.lock finish];
86         }
87     });
88     
89 }else {
90     BBLog(@"BigBang- 抢夺资源中,等待当前获取资源结束继续进行");
91 }
 }

上面的条件锁的文件如下: .h 文件 这里的 < > 不能用,请自行替换 ‘ 为 < >
使用方法为:

self.lock = [[BBResourceLock alloc] init];
if ([self.lock obtain]) {
// 各种任务

// 结束后将锁关闭
[self.lock finish];

}

具体的文件如下:
import ‘Foundation/Foundation.h’

@interface BBResourceLock : NSObject

/**

  • 获取资源,如果资源已经被获取过则返回 NO,否则返回 YES
  • @return 如果资源已经被获取过则返回 NO,否则返回 YES
    */
    -(BOOL)obtain;

/**
等待资源可用
如果资源没有被获取过 则获取资源并返回YES
如果资源被其他线程获取则等待,直到资源可用并返回 YES
如果资源是被同一线程获取则不会等待,并且返回NO
@return YES/NO
*/
-(BOOL)wait;

/**
释放占用的资源
*/
-(void)finish;

@end

.m文件
#import “BBResourceLock.h”

@interface BBResourceLock ()
/** 当前线程 /
@property (nonatomic,strong) NSThread mOccupiedThread;
/ 条件 */
@property (nonatomic,strong) NSCondition *mCondition;
@end

@implementation BBResourceLock

  • (instancetype)init
    {
    self = [super init];
    if (self) {
    self.mCondition = [[NSCondition alloc] init];
    }
    return self;
    }

/**

  • 获取资源,如果资源已经被获取过则返回 NO,否则返回 YES

  • @return 如果资源已经被获取过则返回 NO,否则返回 YES
    */
    -(BOOL)obtain {

    @synchronized(self.mCondition) {
    if (self.mOccupiedThread) {
    return NO;
    }else {
    self.mOccupiedThread = [NSThread currentThread];
    return YES;
    }
    }
    }

/**
等待资源可用
如果资源没有被获取过 则获取资源并返回YES
如果资源被其他线程获取则等待,直到资源可用并返回 YES
如果资源是被同一线程获取则不会等待,并且返回NO
@return YES/NO
*/
-(BOOL)wait {
@synchronized (self.mCondition) {
if (!self.mOccupiedThread) {
return [self obtain];
}
if (self.mOccupiedThread == [NSThread currentThread]) {
return YES;
}
[self.mCondition wait];
return [self obtain];
}
}

/**
释放占用的资源
*/
-(void)finish {
@synchronized(self.mCondition) {
self.mOccupiedThread = nil;
[self.mCondition signal];
}
}

@end

以上就是关于for循环的异步任务处理

原文地址:https://www.cnblogs.com/-yun/p/14297132.html