登录 立即注册
金钱:

Code4App-iOS开发-iOS 开源代码库-iOS代码实例搜索-iOS特效示例-iOS代码例子下载-Code4App.com

基于`ReactiveCocoa` AwesomeCommand 组件

[复制链接]
来自: shareiOS 分类: iOS精品源码 上传时间: 2016-11-2 14:47:05

项目介绍:

AwesomeCommand

https://github.com/Bupterambition/AwesomeCommand

(一)组件介绍

随着业务的发展,越来越多的业务逻辑堆积到一块,日积月累后,很多业务逻辑会交叠在一起,导致后续整理的时候十分混乱,基于这个需求,我们重构command组件,整体是基于ReactiveCocoa,这样的话不需要考虑调用顺序,只需要知道考虑结果,这样每个业务逻辑可以写成一条Signal链,后续只需要对进行管理就可以。同时,我们对AwesomeCommand的线程做了很大优化,使得执行线程回调线程完全分离,同时保证了线程安全,这样外部调用者就只需要关系业务逻辑不需要关心线程问题。

(二)安装

pod 'AwesomeCommand'

(三)Core Class

AwesomeCommand

组件的基础类,提供了多种调用方式,方便对RAC不熟悉的同学,也可以玩转RAC

(四)使用姿势

AwesomeCommand作为一个原子的基类,提供了多种使用方法

1. 基本姿势-继承AwesomeCommand,将逻辑写在run方法中

// RequestCommand.h
#import <AwesomeCommand/AwesomeCommand.h>

@interface RequestCommand : AwesomeCommand
@property (nonatomic, copy) NSDictionary *param;//执行command需要的参数
@end
// RequestCommand.m

#import "RequestCommand.h"
#import <AwesomeCommand/AwesomeCommandPublicHeader.h>

@implementation RequestCommand

@synthesize excuteQueue = _excuteQueue;

- (instancetype)init {
    self = [super init];
    if (self) {
        // 内部指定command的执行线程
        _excuteQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    }
    return self;
}
- (id<AwesomeCancelable>)run:(id<AwesomeResult>)result {
    // 逻辑需要写在这里
    NSLog(@"开始执行 Command Request");
    NSLog(@"current thread:__%@__",[NSThread currentThread]);
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [result onNext:self.param];  // 网络返回结果
        [result onComplete];  // 必须执行,标示Command执行完毕
    });
    NSOperation *disposeOperation = [NSOperation new];
    return [DefaultAwesomeCancelable cancelableWithCancelBlock:^{
        // something to compose
        // Example
        [disposeOperation cancel];
    }];
}
@end
// Block回调形式,Cmd会retain block,在block里请自行管理好持有对象的生命周期。
requestCMD = [[RequestCommand alloc] init];
requestCMD.param = @{"bankName":@"CCB"};
id<AwesomeCancelable> cancelObject_two = [requestCMD executeWithBlock:^(id<AwesomeExecutable> cmd, id data, NSError *error, BOOL isCompleted) {
        // 获取回调
        // 这里回调的线程是在执行executeWithBlock:方法的线程中
    }];
// Callback object回调形式,Cmd不会retain callback object,这个对象的生命周期需要外部自己管理。
@Interface AwesomeCallbackViewModel()<AwesomeCallback>
@property (nonatomic, strong) RequestCommand *requestCMD;
@end

@implementation AwesomeCallbackViewModel

- (void)onNext:(AwesomeCommand *)command AndData:(id)data{

}
- (void)onComplete:(AwesomeCommand *)command {

}
- (void)onError:(AwesomeCommand *)command AndError:(NSError *)error {

}
- (void)executeRequestCMD {
   self.requestCMD.param = @{@"argu":@"Awesome"};
   [self.requestCMD executeWithCallback:self];
}

2. Exec block使用姿势(如果您能管理好)

不需要重写run方法(不推荐)

//RequestCommand.h
#import <AwesomeCommand/AwesomeCommand.h>

@interface RequestCommand : AwesomeCommand

@end
//RequestCommand.m

#import "RequestCommand.h"
#import <AwesomeCommand/AwesomeCommandPublicHeader.h>

@implementation RequestCommand

@synthesize excuteQueue = _excuteQueue;

- (instancetype)init {
    self = [super init];
    if (self) {
        //内部指定command的执行线程
        _excuteQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    }
    return self;
}

@end
//main.m

requestCMD = [[RequestCommand alloc] init];
requestCMD.excuteBlock = ^(id<AwesomeResult> result){
        NSLog(@"开始执行 Command Request");
        NSLog(@"current thread:__%@__",[NSThread currentThread]);
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [result onNext:@"requestCMD"];
            [result onComplete];
        });
        NSOperation *disposeOperation = [NSOperation new];
        return [DefaultAwesomeCancelable cancelableWithCancelBlock:^{
            //something to compose
            //Example
            [disposeOperation cancel];
        }];
    };
id<AwesomeCancelable> cancelObject_two = [requestCMD executeWithBlock:^(id<AwesomeExecutable> cmd, id data, NSError *error, BOOL isCompleted) {
        //获取回调
        //这里回调的线程是在执行executeWithBlock:方法的线程中
    }];

3. RAC用法

假设我们有一个这样的场景
<div align=center>
<img src="https://github.com/Bupterambition/AwesomeCommand/blob/master/AwesomeCommand/Assets/Untitled.gif" width = "400" height = "300" alt="" />
</div>

有6个操作,但是它们之间的执行顺序是有依赖的,比如3的执行需要依赖 1 2完成后才执行,0的执行需要依赖3 4执行完,最后的输出需要0和5都执行完毕,这样一个略显复杂的图如果用传统的逻辑来写的话肯定会组织的非常混乱,如果考虑到每个操作的执行和回调线程可能都是不同的话,那更将加大了复杂度。

但是如果使用AwesomeCommand来写的话,是下面这样子的

  RACSignal *signal_0 = [requestCMD createSignal];

  RACSignal *signal_1 = [self.firstCMD createSignal];

  secondCMD = [[SecondCommand alloc] init];

  RACSignal *signal_2 = [secondCMD createSignal];

  thirdCMD = [[ThirdCommand alloc] init];

  RACSignal *signal_3 = [thirdCMD createSignal];

  fourthCMD = [[FourthCommand alloc] init];

  RACSignal *signal_4 = [fourthCMD createSignal];

  fifthCMD = [[FifthCommand alloc] init];

  RACSignal *signal_5 = [fifthCMD createSignal];

  RACSignal *combine1_2 = [signal_1 combineLatestWith:signal_2];
  RACSignal *then12_3 = [combine1_2 then:^RACSignal * {
    return signal_3;
  }];
  RACSignal *combine3_4 = [then12_3 combineLatestWith:signal_4];
  RACSignal *then34_0 = [combine3_4 then:^RACSignal * {
    return signal_0;
  }];
  [[RACSignal combineLatest:@[ then34_0, signal_5 ]
                     reduce:^id(NSNumber *num1, NSNumber *num2) {
                       return @(num1.integerValue + num2.integerValue);
                     }] subscribeNext:^(id x) {
    NSLog(@"final value is:______%ld______", [x integerValue]);
  }];

4. 更多案例

请查看工程中案例

(五)上下文环境与Cancel

AwesomeCommand作为一个原子基类,在使用时需要将它子类化,所需的Context即是子类的属性,因此子类本身就是一个Context。如果调用方需要检测command的执行情况的话,只需要KVO下executing这个属性。

AwesomeCommand提供了手动Cancel与自动Cancel功能.

手动cancel
id<AwesomeCancelable> cancelObject_two = [requestCMD executeWithBlock:^(id<AwesomeExecutable> cmd, id data, NSError *error, BOOL isCompleted) {
        
}];
    
[cancelObject_one cancel];
自动cancel

自动cancel功能,我们是在AwesomeCommand的dealloc中进行cancel

//AwesomeCommand.m

- (void)dealloc {
    [self cancel];
}

(六)若干问题

1线程问题

执行线程与回调线程

AwesomeCommand设计的初衷是让使用者不用去关心线程问题,并且保证不会出错,在设计时我们将执行线程和回调线程进行分离,具体的做法的作用是通过下面两个函数实现

- (RACSignal *)deliverOn:(RACScheduler *)scheduler;//将订阅和副作用转移到目标线程
- (RACSignal *)subscribeOn:(RACScheduler *)scheduler;//只将订阅转移到目标线程

使用时我们只需要在初始化的时候指定一下需要进行逻辑执行的线程即可

@implementation RequestCommand
@synthesize excuteQueue = _excuteQueue;
- (instancetype)init {
    self = [super init];
    if (self) {
        _excuteQueue = dispatch_get_global_queue(0, 0);
    }
    return self;
}

对于回调线程,调用方不需要关心,因为AwesomeCommand会捕获当前执行下面语句时的线程并在有回调时进行返回

- (id<AwesomeCancelable>)executeWithCallback:(id<AwesomeCallback>)callback;

- (id<AwesomeCancelable>)executeWithBlock:    (AwesomeExcuteCallbaclBlock)callbackBlock;

- (RACSignal *)createSignal;

比如说你想写一个在子线程中跑逻辑在主线程中取回调的逻辑

#import <AwesomeCommand/AwesomeCommand.h>

@interface FirstCommand : AwesomeCommand

@end

_________________________________________________________________

#import "FirstCommand.h"
#import <AwesomeCommand/AwesomeCommandPublicHeader.h>
@implementation FirstCommand

@synthesize excuteQueue = _excuteQueue;

- (instancetype)init {
    self = [super init];
    if (self) {
        _excuteQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    }
    return self;
}

- (id<AwesomeCancelable>)run:(id<AwesomeResult>)result {
    NSLog(@"开始执行 Command 1,线程:%@",[NSThread currentThread]);
    [result onNext:@"1"];
    [result onComplete];
    NSOperation *disposeOperation = [NSOperation new];
    return [DefaultAwesomeCancelable cancelableWithCancelBlock:^{
        //something to compose
        //Example
        [disposeOperation cancel];
    }];
}

@end
_________________________________________________________________

@interface ViewController ()
@property (nonatomic, strong) FirstCommand *firstCMD;

@end

@implementation ViewController

- (void)viewDidLoad {
  [super viewDidLoad];
  self.firstCMD = [[FirstCommand alloc] init];
  id<AwesomeCancelable> cancelObject_two = [firstCMD executeWithBlock:^(id<AwesomeExecutable> cmd, id data, NSError *error, BOOL isCompleted) {
        //逻辑在子线程,回调在主线程
    }];

线程安全问题

资源竞争

AwesomeCommand的驱动是依靠RACSignal的,因此我在创建这个驱动Signal的didSubscribe中加入pthread_mutex_t,在副作用执行时会进行加锁。具体代码如下


@implementation SignalUtil

+ (RACSignal *)createSignal:(nonnull AwesomeCommand *)command {
    pthread_mutex_t _mutex;
    const int result = pthread_mutex_init(&_mutex, NULL);
    NSCAssert(0 == result, @"Failed to initialize mutex with error %d.", result);
    
    @weakify(command);
    RACSignal *racSignal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        @strongify(command);
        if (command && [command respondsToSelector:@selector(run:)]) {
            pthread_mutex_lock(&_mutex);
            
            id<AwesomeResult> result = [AwesomeResultImpl resultWithSubscriber:subscriber];

            [command setValue:@(YES) forKey:@"Executing"];
            id<AwesomeCancelable> cancelable = [command run:result];
            
            pthread_mutex_unlock(&_mutex);
            if (cancelable) {
                return [RACDisposable disposableWithBlock:^{
                    [cancelable cancel];
                }];
            } else {
                return nil;
            }
            
        }
        return nil;
    }];

    return racSignal;

}
优先级反转

在我们的实际代码中,可能不会像火星探测器那样,越到优先级反转时,不断的重启。

解决这个问题的方法,通常就是不要使用不同的优先级--将高优先级的代码和低优先级的代码修改为相同优先级。当使用GCD时,总是使用默认的优先级队列。如果使用不同的优先级,就可能会引发事故。

虽然有些文章上说,在不同的队列中使用不同的优先级,但是这会增加并发编程的复杂度和不可预见性。

那么如果你非要在回调时使用不同优先级的话,也是没有问题的,因为awesomecommand里面是设置的callback Queue,里面不会有大量的读取任务占据着资源,只是起到一个回调通道的作用,所以也不存在卡住问题。

2.RAC使用成本

AwesomeCommand的驱动是依靠RACSignal,但是除了复杂的场景外——比如像是多个操作有依赖关系,在使用AwesomeCommand时,RAC相关的都被封装起来了,使用方只需要关注业务逻辑就可以。

但是如果你是RAC的深度用户或是对RAC比较感兴趣的话,将会更加灵活的去使用AwesomeCommand。

因此AwesomeCommand对于熟悉或是不熟悉RAC的同学都可以无差别使用。

3.RAC学习资源

如果你也对RAC感兴趣的话,大量相关资料请戳下面

资料

资料名称资料描述
Learn ReactiveCocoa Source学习ReactiveCocoa(主要针对2.x Objective-C 版本)过程中整理的一些资料。

开源项目

项目名称项目描述
ddaajing/ReactiveCocoaDemo该Demo主要用来测验MVVM模式的分层,使APP更方便维护,测试 以及练习ReactiveCocoa相关API
ashfurrow/C-41介绍博客
arbullzhang/KoalaFRP说明:FunctionalReactivePixels 做了修改,因为编译不过。
leichunfeng/MVVMReactiveCocoa推荐!一个完整的使用MVVM和RAC的Github客户端,leichunfeng大师作品。
ashfurrow / FunctionalReactivePixels演示使用500px的API来使用FRP与ReactiveCocoa在iOS的环境。

Tips

为了方便构建AwesomeCommand的子类,你可以通过使用下面的脚本建立一个AwesomeComand的模版

sudo chmod 755 install-templates.sh

sudo sh install-templates.sh 

Author

senmiao, senmiao@meili-inc.com,

code4app

相关源码推荐:

我来说两句
*滑动验证:
所有评论(13)
王颖博 2016-11-3 09:36:02
感谢分享,Code4App有你更精彩
回复
suhaidong 2016-11-4 10:26:12
楼主用心了,内容非常精彩。
回复
code4app热心网友 2016-11-4 10:26:18
淡定,淡定,淡定……
回复
AlonMessi 2016-11-5 15:14:42
好好 学习了 确实不错
回复
BlueManlove 2016-11-5 15:22:59
code4app好的内容真的很多~赞
回复
littleRed 2016-11-5 15:33:59
支持,感谢,祝code4app越来越好~
回复
hellokenken 2016-11-5 15:46:10
感谢分享,code4app有你更精彩
回复
phoiu 2016-11-5 16:00:42
code4app确实是个好地方,必须支持~
回复
lengyouxin 2016-11-8 20:03:39
我只是路过打酱油的。
回复
12下一页
提取码:  下载次数:7 状态:已购或VIP 售价:0(原价:10)金钱 下载权限:初级码农 
1067 0 7
联系我们
首页/微信公众账号投稿

帖子代码编辑/版权问题

QQ:435399051,742864542

如何获得代码达人称号?

代码贡献英雄榜
用户名 下载数
通过邮件订阅最新 Code4App 信息
上一条 /4 下一条
联系我们
关闭
合作电话:
13802416937
Email:
435399051@qq.com
商务市场合作/投稿
问题反馈及帮助
联系我们

广告投放| 广东互联网违法和不良信息举报中心|中国互联网举报中心|Github|申请友链|手机版|Code4App ( 粤ICP备15117877号-1 )

快速回复 返回顶部 返回列表