1#import <Foundation/Foundation.h>
2
3@protocol TaskQueueDelegate <NSObject>
4@required
5- (void)taskQueue:(id)queue didCompleteTask:(NSString *)taskId;
6@optional
7- (void)taskQueue:(id)queue didFailTask:(NSString *)taskId
8 error:(NSError *)error;
9@end
10
11typedef void (^TaskBlock)(void (^completion)(BOOL success));
12
13@interface TaskQueue : NSObject
14
15@property (nonatomic, weak) id<TaskQueueDelegate> delegate;
16@property (nonatomic, readonly) NSUInteger pendingCount;
17@property (nonatomic, assign) NSUInteger maxConcurrent;
18
19- (void)enqueue:(NSString *)taskId block:(TaskBlock)block;
20- (void)cancelAll;
21
22@end
23
24@implementation TaskQueue {
25 NSMutableDictionary<NSString *, TaskBlock> *_tasks;
26 NSMutableSet<NSString *> *_running;
27 dispatch_queue_t _queue;
28}
29
30- (instancetype)init {
31 self = [super init];
32 if (self) {
33 _tasks = [NSMutableDictionary new];
34 _running = [NSMutableSet new];
35 _maxConcurrent = 4;
36 _queue = dispatch_queue_create(
37 "com.app.taskqueue",
38 DISPATCH_QUEUE_CONCURRENT
39 );
40 }
41 return self;
42}
43
44- (NSUInteger)pendingCount {
45 return [_tasks count];
46}
47
48- (void)enqueue:(NSString *)taskId block:(TaskBlock)block {
49 @synchronized (self) {
50 _tasks[taskId] = [block copy];
51 }
52 [self processNext];
53}
54
55- (void)processNext {
56 @synchronized (self) {
57 if ([_running count] >= _maxConcurrent) return;
58 NSString *nextId = [[_tasks allKeys] firstObject];
59 if (!nextId) return;
60 TaskBlock block = _tasks[nextId];
61 [_tasks removeObjectForKey:nextId];
62 [_running addObject:nextId];
63 dispatch_async(_queue, ^{
64 block(^(BOOL success) {
65 @synchronized (self) {
66 [self->_running removeObject:nextId];
67 }
68 if (success) {
69 [self.delegate taskQueue:self
70 didCompleteTask:nextId];
71 }
72 [self processNext];
73 });
74 });
75 }
76}
77
78- (void)cancelAll {
79 @synchronized (self) {
80 [_tasks removeAllObjects];
81 }
82}
83
84@end