iOS多线程下的行为管理

  在iOS开发的过程中,经常会有这样的场景:在多线程下进行多任务操作,任务之间有相互的依赖,优先级关系等。为了最大可能的发挥设备的性能。我这边基于手机内核数量,cpu利用率作为app开启线程数量,以及app线程优先级调度的开关。

我这里将行为分为了几个类

JKSingleAction

单独的一个行为

@interface JKSingleAction : JKBaseAction

@property (nonatomic, copy) void(^actionBlock)(id _Nullable data);
@property (nonatomic, copy, nullable) void(^completeBlock)(__kindof JKSingleAction *tmpAction);

@end
@implementation JKSingleAction
- (void)cleanBlock
{
    if (self.completeBlock) {
        self.completeBlock = nil;
    }
}
@end

JKBatchAction

组队列行为,所有的子行为同时开始,每一个子行为都结束了,整个组队列行为都结束了,才算是真正的结束。其中组队列里的每一个子行为不能是组队列行为。

@interface JKBatchAction : JKBaseAction

@property (nonatomic, strong, readonly) NSMutableArray <__kindof JKBaseAction *>*actions;

@property (nonatomic, copy, nullable) void(^completeBlock)(__kindof JKBatchAction *batchAction, __kindof JKBaseAction * _Nullable failedAction);


+ (instancetype)new NS_UNAVAILABLE;

- (instancetype)init NS_UNAVAILABLE;

+ (instancetype)initWithArray:(NSArray <__kindof JKBaseAction *>*)array;

- (void)addAction:(__kindof JKBaseAction *)action;

@end
#import "JKActionManager.h"

@interface JKBatchAction()

@property (nonatomic, strong, readwrite) NSMutableArray <__kindof JKBaseAction *>*actions;
@property (nonatomic, copy, nullable) void(^afterCompleteBlock)(__kindof JKBatchAction *batchAction, __kindof JKBaseAction * _Nullable failedAction);

@end

@implementation JKBatchAction

+ (instancetype)initWithArray:(NSArray <__kindof JKBaseAction *>*)array
{
    for (id action in array) {
        if (![action isKindOfClass:[JKBaseAction class]]) {
#if DEBUG
        NSAssert(NO, @"action in array must be kindof JKBaseAction");
#endif
            return nil;
        }
        if ([action isKindOfClass:[JKBatchAction class]]) {
#if DEBUG
        NSAssert(NO, @"action in array can not be an instance of JKBatchAction");
#endif
        return nil;
        }
    }
    JKBatchAction *batchAction = [[self alloc] init];
    if (batchAction) {
        if (array) {
           [batchAction.actions addObjectsFromArray:array];
        }
    }
    return batchAction;
}

- (void)addAction:(__kindof JKBaseAction *)action
{
    if (self.status != JKActionStatusReady) {
#if DEBUG
        NSAssert(NO, @"make sure status is JKActionStatusReady before addAction");
#endif
        return;
    }
    if (action) {
        if (![action isKindOfClass:[JKBaseAction class]]) {
#if DEBUG
            NSAssert(NO, @"action must be kindof JKBaseAction");
#endif
            return;
        }
        if ([action isKindOfClass:[JKBatchAction class]]) {
#if DEBUG
            NSAssert(NO, @"action can not be an instance of JKBatchAction");
#endif
            return;
        }
        [self.actions addObject:action];
    }
}

- (void)cleanBlock
{
    if (self.completeBlock) {
        self.completeBlock = nil;
    }
    if (self.afterCompleteBlock) {
        self.afterCompleteBlock = nil;
    }
}

#pragma mark - - getter - -
- (NSMutableArray *)actions
{
    if (!_actions) {
        _actions = [NSMutableArray new];
    }
    return _actions;
}

@end

JKChainAction

链式行为,一个行为的开始依赖前一个行为的结束。其中链式行为的每一个子行为不能是链式行为。

@interface JKChainAction : JKBaseAction

@property (nonatomic, strong, readonly) NSMutableArray *actions;

@property (nonatomic, copy, nullable) void(^completeBlock)(__kindof JKChainAction *chainAction, __kindof JKBaseAction * _Nullable failedAction);


+ (instancetype)new NS_UNAVAILABLE;

- (instancetype)init NS_UNAVAILABLE;

+ (instancetype)initWithArray:(NSArray <__kindof JKBaseAction *>*)array;

- (void)addAction:(__kindof JKBaseAction *)action;

@end
@interface JKChainAction()

@property (nonatomic, strong, readwrite) NSMutableArray <__kindof JKBaseAction *>*actions;
@property (nonatomic, copy, nullable) void(^afterCompleteBlock)(__kindof JKChainAction *batchAction, __kindof JKBaseAction * _Nullable failedAction);

@end

@implementation JKChainAction

+ (instancetype)initWithArray:(NSArray <__kindof JKBaseAction *>*)array
{
    for (id action in array) {
        if (![action isKindOfClass:[JKBaseAction class]]) {
#if DEBUG
            NSAssert(NO, @"action in array must be kindof JKBaseAction");
#endif
            return nil;
        }
        if ([action isKindOfClass:[JKChainAction class]]) {
#if DEBUG
            NSAssert(NO, @"JKChainAction instance cannot in JKChainAction array");
#endif
            return nil;
        }
    }
    JKChainAction *chainAction = [[self alloc] init];
    if (chainAction) {
        if (array) {
           [chainAction.actions addObjectsFromArray:array];
        }
    }
    return chainAction;
}

- (void)addAction:(__kindof JKBaseAction *)action
{
    if (self.status != JKActionStatusReady) {
#if DEBUG
        NSAssert(NO, @"make sure status is JKActionStatusReady before addAction");
#endif
        return;
    }
    if (action) {
        if (![action isKindOfClass:[JKBaseAction class]]) {
#if DEBUG
            NSAssert(NO, @"action must be kindof JKBaseAction");
#endif
            return;
        }
        if ([action isKindOfClass:[JKChainAction class]]) {
#if DEBUG
         NSAssert(NO, @"action can not be an instance of JKChainAction");
#endif
            return;
        }
        
        [self.actions addObject:action];
    }
}

- (void)cleanBlock
{
    if (self.completeBlock) {
        self.completeBlock = nil;
    }
    if (self.afterCompleteBlock) {
        self.afterCompleteBlock = nil;
    }
}

#pragma mark - - getter - -
- (NSMutableArray *)actions
{
    if (!_actions) {
        _actions = [NSMutableArray new];
    }
    return _actions;
}

@end

JKActionManager

行为管理类,主要代码如下:

#import <Foundation/Foundation.h>
#import "JKSingleAction.h"
#import "JKGroupAction.h"

NS_ASSUME_NONNULL_BEGIN

@interface JKActionManager : NSObject

@property (nonatomic, strong, readonly) NSMutableArray <__kindof JKSingleAction *>*actions;
@property (nonatomic, strong, readonly) NSMutableArray <__kindof JKBatchAction *>*batchActions;
@property (nonatomic, strong, readonly) NSMutableArray <__kindof JKChainAction *>*chainActions;


+ (instancetype)new NS_UNAVAILABLE;

- (instancetype)init NS_UNAVAILABLE;

+ (instancetype)sharedManager;

///action must be member of class JKSingleAction,JKBatchAction,JKChainAction
+ (void)addAction:(__kindof JKBaseAction *)action
             data:(nullable id)data;

+ (void)removeAction:(__kindof JKBaseAction *)action;

+ (void)removeAllActions;

@end

NS_ASSUME_NONNULL_END
#import "JKActionManager.h"
#import "JKKVOHelper.h"
#import "JKDataHelper.h"
#include <sys/sysctl.h>
#import <mach/mach.h>

#pragma mark - - JKBatchAction category - -

@interface JKBatchAction(JKPrivate)

@property (nonatomic, copy, nullable) void(^afterCompleteBlock)(__kindof JKBatchAction *batchAction, __kindof JKBaseAction * _Nullable failedAction);

@end

@implementation JKBatchAction(JKPrivate)
@dynamic afterCompleteBlock;

@end

#pragma mark - - JKChainAction category - -

@interface JKChainAction(JKPrivate)

@property (nonatomic, copy, nullable) void(^afterCompleteBlock)(__kindof JKChainAction *batchAction, __kindof JKBaseAction * _Nullable failedAction);

@end

@implementation JKChainAction(JKPrivate)
@dynamic afterCompleteBlock;

@end

@interface JKActionManager ()

@property (nonatomic, strong) NSOperationQueue *queue;
@property (nonatomic, strong) NSRecursiveLock *lock;
@property (nonatomic, strong) NSMutableDictionary <NSString *,__kindof NSOperation *>*operationDic;
@property (nonatomic, strong, readwrite) NSMutableArray <__kindof JKSingleAction *>*actions;
@property (nonatomic, strong, readwrite) NSMutableArray <__kindof JKBatchAction *>*batchActions;
@property (nonatomic, strong, readwrite) NSMutableArray <__kindof JKChainAction *>*chainActions;

@end

@implementation JKActionManager

static JKActionManager *_manager = nil;
+ (instancetype)sharedManager
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _manager = [[self alloc] init];
    });
    return _manager;
}

- (instancetype)init
{
    self = [super init];
    if (self) {
        _queue = [[NSOperationQueue alloc] init];
        NSUInteger count = [self cpuCoreCount];
        if (count == 0) {
            count = 1;
        }
        _queue.maxConcurrentOperationCount = 3 * count;
        _lock = [NSRecursiveLock new];
        _operationDic = [NSMutableDictionary new];
        _actions = [NSMutableArray new];
        _batchActions = [NSMutableArray new];
        _chainActions = [NSMutableArray new];
    }
    return self;
}

+ (void)addAction:(__kindof JKBaseAction *)action
             data:(nullable id)data
{
    if (!action) {
#if DEBUG
         NSAssert(NO, @"action can not be nil");
#endif
         return;
    }
    if (action.status != JKActionStatusReady) {
    #if DEBUG
         NSAssert(NO, @"make sure action's status be JKActionStatusReady");
    #endif
         return;
    }
    if ([action isKindOfClass:[JKSingleAction class]]) {
            JKSingleAction *singleAction = (JKSingleAction *)action;
            if (!singleAction.completeBlock) {
                singleAction.completeBlock = ^(__kindof JKSingleAction * _Nonnull tmpAction) {
                    tmpAction.status = JKActionStatusFinish;
                    [self updateOperationPriority];
                };
            }
            [self addJKAction:singleAction data:data];
            singleAction.status = JKActionStatusExecuting;
        } else if ([action isKindOfClass:[JKBatchAction class]]) {
            JKBatchAction *batchAction = (JKBatchAction *)action;
            if (batchAction.actions.count == 0) {
#if DEBUG
                NSAssert(NO, @"batchAction.actions.count must be bigger than zero");
#endif
                return;
            }
            [self addBatchAction:batchAction data:data];
            batchAction.status = JKActionStatusExecuting;
        } else if ([action isKindOfClass:[JKChainAction class]]) {
            JKChainAction *chainAction = (JKChainAction *)action;
            if (chainAction.actions.count == 0) {
#if DEBUG
                NSAssert(NO, @"chainAction.actions.count must be bigger than zero");
#endif
                return;
            }
            [self addChainAction:chainAction data:data];
            chainAction.status = JKActionStatusExecuting;
        } else {
    #if DEBUG
            NSAssert(NO, @"no support this kind of action");
    #endif
            return;
        }
}

+ (void)removeAction:(__kindof JKBaseAction *)action
{
    if ([action isKindOfClass:[JKSingleAction class]]) {
        [self removeJKAction:action];
    } else if ([action isKindOfClass:[JKBatchAction class]]) {
        [self removeBatchAction:action];
    } else if ([action isKindOfClass:[JKChainAction class]]) {
        [self removeChainAction:action];
    } else {
    #if DEBUG
            NSAssert(NO, @"no support this kind of action");
    #endif
        return;
    }
    action.status = JKActionStatusCancel;
}

+ (void)removeAllActions
{
    [[JKActionManager sharedManager].lock lock];
    NSArray *batchActions = [[JKActionManager sharedManager].batchActions copy];
    NSArray *chainActions = [[JKActionManager sharedManager].chainActions copy];
    
    for (__kindof JKBatchAction *batchAction in batchActions) {
        [self removeAction:batchAction];
    }
    
    for (__kindof JKChainAction *chainAction in chainActions) {
        [self removeAction:chainAction];
    }
    
    NSArray *actions = [[JKActionManager sharedManager].actions copy];
    for (__kindof JKSingleAction *action in actions) {
        [self removeAction:action];
    }
    [[JKActionManager sharedManager].lock unlock];

}

+ (void)addJKAction:(JKSingleAction *)action
               data:(nullable id)data
{
    [[JKActionManager sharedManager].lock lock];
    NSOperationQueue *queue = [JKActionManager sharedManager].queue;
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        if(action.actionBlock){
            action.actionBlock(data);
        }
    }];
    
   if ([self isVeryCriticalCodition]){
        operation.queuePriority = NSOperationQueuePriorityVeryLow;
    } else if ([self isCriticalCodition]) {
       operation.queuePriority = NSOperationQueuePriorityLow;
    } else {
        operation.queuePriority = NSOperationQueuePriorityNormal;
    }
    [self observeOperation:operation action:action];
    NSString *key = [NSString stringWithFormat:@"%p",action];
    [[JKActionManager sharedManager].operationDic setValue:operation forKey:key];
    [[JKActionManager sharedManager].actions addObject:action];
    [queue addOperation:operation];
    [[JKActionManager sharedManager].lock unlock];
}

+ (void)addBatchAction:(JKBatchAction *)batchAction
                  data:(nullable id)data
{
   [[JKActionManager sharedManager].batchActions addObject:batchAction];
   __block NSInteger successCount = 0;
   for (__kindof JKBaseAction *tmpAction in batchAction.actions) {
       if ([tmpAction isKindOfClass:[JKSingleAction class]]) {
           JKSingleAction *action = (JKSingleAction *)tmpAction;
           action.completeBlock = ^(__kindof JKSingleAction *tmpAction) {
               tmpAction.status = JKActionStatusFinish;
               [self updateOperationPriority];
               if (!tmpAction.error) {
                   successCount++;
                   if (successCount == batchAction.actions.count) {
                       [self batchActionComplete:batchAction failedAction:nil];
                   }
               } else {
                   [self batchActionComplete:batchAction failedAction:tmpAction];
               }
           };
       } else if ([tmpAction isKindOfClass:[JKChainAction class]]) {
           JKChainAction *chainAction = (JKChainAction *)tmpAction;
           chainAction.afterCompleteBlock = ^(__kindof JKChainAction * _Nonnull chainAction, __kindof JKBaseAction * _Nullable failedAction) {
               [self updateOperationPriority];
               if (!failedAction) {
                   successCount++;
                   if (successCount == batchAction.actions.count) {
                       [self batchActionComplete:batchAction failedAction:nil];
                   }

               } else {
                   [self batchActionComplete:batchAction failedAction:failedAction];
               }
           };
       }
       [self addAction:tmpAction data:data];
   }
}

+ (void)addChainAction:(JKChainAction *)chainAction
                  data:(nullable id)data
{
   [[JKActionManager sharedManager].chainActions addObject:chainAction];
   [self startActionInChainAction:chainAction successCount:0 data:data];
}

+ (void)removeJKAction:(JKSingleAction *)action
{
    [action cleanBlock];
    [[JKActionManager sharedManager].lock lock];
    NSString *key = [NSString stringWithFormat:@"%p",action];
    NSBlockOperation *operaion = [[JKActionManager sharedManager].operationDic objectForKey:key];
    [operaion cancel];
    [[JKActionManager sharedManager].operationDic removeObjectForKey:key];
    [[JKActionManager sharedManager].actions removeObject:action];
    [[JKActionManager sharedManager].lock unlock];
}

+ (void)removeBatchAction:(JKBatchAction *)batchAction
{
    [batchAction cleanBlock];
    [[JKActionManager sharedManager].lock lock];
    NSArray *actions = [batchAction.actions copy];
    [[JKActionManager sharedManager].batchActions removeObject:batchAction];

    for (__kindof JKBaseAction *action in actions) {
        if ([action isKindOfClass:[JKSingleAction class]]) {
            [self removeJKAction:action];
        } else if ([action isKindOfClass:[JKChainAction class]]) {
            [self removeChainAction:action];
        }
    }
    [[JKActionManager sharedManager].lock unlock];

}

+ (void)removeChainAction:(JKChainAction *)chainAction
{
    [chainAction cleanBlock];
    [[JKActionManager sharedManager].lock lock];
    NSArray *actions = [chainAction.actions copy];
    [[JKActionManager sharedManager].chainActions removeObject:chainAction];
    
    for (__kindof JKBaseAction *action in actions) {
        if ([action isKindOfClass:[JKSingleAction class]]) {
            [self removeJKAction:action];
        } else if ([action isKindOfClass:[JKBatchAction class]]) {
            [self removeBatchAction:action];
        }
    }
    [[JKActionManager sharedManager].lock unlock];
}

#pragma mark - - private - -

+ (BOOL)isCriticalCodition
{
    float cpuUsage = [self cpuUsage];
    return cpuUsage >= 0.35;
}

+ (BOOL)isVeryCriticalCodition
{
    float cpuUsage = [self cpuUsage];
    return cpuUsage >= 0.5;
}

+ (BOOL)isRecoverConditiion
{
    float cpuUsage = [self cpuUsage];
    return cpuUsage <= 0.2;
}

+ (void)observeOperation:(NSBlockOperation *)operation
                  action:(__kindof JKSingleAction *)action
{
    [operation jk_addObserver:operation
                  forKeyPaths:@[@"cancelled",@"executing"]
                      options:NSKeyValueObservingOptionNew context:nil
              withDetailBlock:^(NSString * _Nonnull keyPath, NSDictionary * _Nonnull change, void * _Nonnull context) {
        if ([keyPath isEqualToString:@"executing"]) {
            BOOL executing = [change jk_boolForKey:@"new"];
            if (executing) {
                action.status = JKActionStatusExecuting;
            }
        } else if ([keyPath isEqualToString:@"cancelled"]) {
            BOOL cancelled = [change jk_boolForKey:@"new"];
            if (cancelled) {
                action.status = JKActionStatusCancel;
            }
        }
    }];
}

+ (void)batchActionComplete:(__kindof JKBatchAction *)batchAction
               failedAction:(nullable __kindof JKBaseAction *)faileAction
{
    batchAction.status = JKActionStatusFinish;
    if (batchAction.completeBlock) {
        batchAction.completeBlock(batchAction, faileAction);
    }
    if (batchAction.afterCompleteBlock) {
        batchAction.afterCompleteBlock(batchAction, faileAction);
    }
    [self removeBatchAction:batchAction];
}

+ (void)chainActionComplete:(__kindof JKChainAction *)chainAction
               failedAction:(nullable __kindof JKBaseAction *)faileAction
{
    chainAction.status = JKActionStatusFinish;
    if (chainAction.completeBlock) {
        chainAction.completeBlock(chainAction, nil);
    }
    if (chainAction.afterCompleteBlock) {
        chainAction.afterCompleteBlock(chainAction, nil);
    }
    [self removeChainAction:chainAction];
}

+ (NSUInteger)startActionInChainAction:(__kindof JKChainAction *)chainAction
                          successCount:(NSUInteger)lastSuccessCount
                                  data:(nullable id)data
{
    __block NSInteger successCount = lastSuccessCount;
    __kindof JKBaseAction *baseAction = (__kindof JKBaseAction *)[chainAction.actions jk_objectWithIndex:successCount];
    if (baseAction) {
        if ([baseAction isKindOfClass:[JKSingleAction class]]) {
            JKSingleAction *action = (JKSingleAction *)baseAction;
            action.completeBlock = ^(__kindof JKSingleAction * _Nonnull tmpAction) {
                tmpAction.status = JKActionStatusFinish;
                [self updateOperationPriority];
                if (!tmpAction.error) {
                    successCount++;
                    if (successCount < chainAction.actions.count) {
                        successCount = [self startActionInChainAction:chainAction successCount:successCount data:tmpAction.result];
                    } else {
                        [self chainActionComplete:chainAction failedAction:nil];
                    }
                } else {
                   [self chainActionComplete:chainAction failedAction:tmpAction];
                }
            };
        } else if ([baseAction isKindOfClass:[JKBatchAction class]]) {
            JKBatchAction *batchAction = (JKBatchAction *)baseAction;
            batchAction.afterCompleteBlock = ^(__kindof JKBatchAction * _Nonnull batchAction, __kindof JKBaseAction * _Nullable failedAction) {
                [self updateOperationPriority];
                if (!failedAction) {
                    successCount++;
                    if (successCount < chainAction.actions.count) {
                        [self batchActionComplete:batchAction failedAction:nil];
                        successCount = [self startActionInChainAction:chainAction successCount:successCount data:batchAction.result];
                    } else {
                        [self chainActionComplete:chainAction failedAction:nil];
                    }
                } else {
                   [self chainActionComplete:chainAction failedAction:failedAction];
                }
            };
        }
        [self addAction:baseAction data:data];
    }
    return successCount;
}

+ (void)updateOperationPriority
{
    if ([self isRecoverConditiion]) {
        [self updateOperationPriorityWithQueueOperations:[JKActionManager sharedManager].queue.operations];
    }
}

+ (void)updateOperationPriorityWithQueueOperations:(NSArray *)operations
{
    for (__kindof NSOperation *operation in operations) {
        if (operation.queuePriority < NSOperationQueuePriorityNormal && operation.ready) {
          operation.queuePriority = NSOperationQueuePriorityNormal;
        }
    }
}


/// the cpu core count
- (NSUInteger)cpuCoreCount
{
    unsigned int ncpu;
    size_t len = sizeof(ncpu);
    sysctlbyname("hw.ncpu", &ncpu, &len, NULL, 0);
    return ncpu;
}


/// the usage of the cpu
+ (float)cpuUsage
{
    kern_return_t kr;
       task_info_data_t tinfo;
       mach_msg_type_number_t task_info_count;

       task_info_count = TASK_INFO_MAX;
       kr = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)tinfo, &task_info_count);
       if (kr != KERN_SUCCESS) {
           return -1;
       }

       task_basic_info_t      basic_info;
       thread_array_t         thread_list;
       mach_msg_type_number_t thread_count;

       thread_info_data_t     thinfo;
       mach_msg_type_number_t thread_info_count;

       thread_basic_info_t basic_info_th;
       uint32_t stat_thread = 0; // Mach threads

       basic_info = (task_basic_info_t)tinfo;

       // get threads in the task
      kr = task_threads(mach_task_self(), &thread_list, &thread_count);
       if (kr != KERN_SUCCESS) {
           return -1;
       }
       if (thread_count > 0)
           stat_thread += thread_count;

       long tot_sec = 0;
       long tot_usec = 0;
       float tot_cpu = 0;
       int j;

       for (j = 0; j < thread_count; j++)
       {
           thread_info_count = THREAD_INFO_MAX;
           kr = thread_info(thread_list[j], THREAD_BASIC_INFO,
                            (thread_info_t)thinfo, &thread_info_count);
           if (kr != KERN_SUCCESS) {
               return -1;
           }

           basic_info_th = (thread_basic_info_t)thinfo;

           if (!(basic_info_th->flags & TH_FLAGS_IDLE)) {
               tot_sec = tot_sec + basic_info_th->user_time.seconds + basic_info_th->system_time.seconds;
               tot_usec = tot_usec + basic_info_th->user_time.microseconds + basic_info_th->system_time.microseconds;
               tot_cpu = tot_cpu + basic_info_th->cpu_usage / (float)TH_USAGE_SCALE * 100.0;
           }

       } // for each thread

       kr = vm_deallocate(mach_task_self(), (vm_offset_t)thread_list, thread_count * sizeof(thread_t));
       assert(kr == KERN_SUCCESS);

       return tot_cpu / 100;
}
@end

我这边用了一个测试样例,来给大家演示如何使用:

it(@"action is JKChainAction ", ^{
            __block NSInteger age1 = 0;
            __block NSInteger age2 = 0;
            __block NSInteger age3 = 0;
            __block NSInteger age4 = 0;
            JKPerson *person = [JKPerson new];

            JKSingleAction *action1 = [JKSingleAction new];
            action1.actionBlock = ^(id  _Nullable data) {
                NSLog(@"__data %@\n",data);
               age1 = [person inputAge:1];
                action1.result = @(age1);
                action1.completeBlock(action1);
            };
            JKSingleAction *action2 = [JKSingleAction new];
            action2.actionBlock = ^(id  _Nullable data) {
                NSLog(@"__data %@\n",data);
               age2 = [person inputAge:2];
                action2.result = @(age2 + [data integerValue]);
                action2.completeBlock(action2);
            };
            JKSingleAction *action3 = [JKSingleAction new];
            action3.actionBlock = ^(id  _Nullable data) {
                NSLog(@"__data %@\n",data);
               age3 = [person inputAge:3];
                action3.result = @(age3 + [data integerValue]);
                action3.completeBlock(action3);
            };
            JKChainAction *chainAction = [JKChainAction initWithArray:@[action1,action2,action3]];
            [chainAction start];
            [[[JKActionManager sharedManager].chainActions should] haveCountOf:1];

            chainAction.completeBlock = ^(__kindof JKChainAction * _Nonnull chainAction, __kindof JKBaseAction * _Nullable failedAction) {
                if(!failedAction){
                    for(__kindof JKSingleAction *singleAction in chainAction.actions){
                        NSLog(@"__age %@\n",singleAction.result);
                        age4 += [singleAction.result integerValue];
                    }
                }
            };
            
            [[expectFutureValue(theValue(age1)) shouldEventually] equal:theValue(1)];
            [[expectFutureValue(theValue(age2)) shouldEventually] equal:theValue(2)];
            [[expectFutureValue(theValue(age3)) shouldEventually] equal:theValue(3)];
            [[expectFutureValue(theValue(age4)) shouldEventually] equal:theValue(10)];

            [[[JKActionManager sharedManager].actions should] haveCountOf:0];
            [[[JKActionManager sharedManager].batchActions should] haveCountOf:0];
        });

源码下载:https://github.com/xindizhiyin2014/JKActionManager
pod 集成

pod 'JKActionManager'

欢迎大家多多批评指正。
更多干货文章,欢迎扫描二维码关注公众号
这里写图片描述

发布了231 篇原创文章 · 获赞 110 · 访问量 60万+

猜你喜欢

转载自blog.csdn.net/HHL110120/article/details/104079801
今日推荐