阅读 113

iOS攻防——(四)class-dump-与-Dumpdecrypted-使用

1 class dump

class dump 是一个用于检查保存在 Mach-O 文件中的 objective-c 运行时信息的工具,攻防中最常用、实用的命令行工具。

1.1 class dump 好玩在哪?

class dump 绝对可以满足你的好奇心。你可以通过 class dump :

  1. 查看闭源的应用、frameworks、bundles。
  2. 对比一个 APP 不同版本之间的接口变化。
  3. 对一些私有 frameworks 做些有趣的试验。

1.2 Download

当前版本: 3.5 (64 bit Intel) 需要 Mac OS X 10.8 或更高版本

class-dump-3.5.dmg class-dump-3.5.tar.gz class-dump-3.5.tar.bz2

1.3 Use

下载好后,双击dmg文件,将其中的 class-dump 文件放到/usr/local/sbin 目录下,然后就可以在命令行中使用了。

image
> date: 2016-08-05 10:33:00

原文地址

点击这里

这几天部门的前辈再用RAC的时候问到一个问题,RACCommand在RAC中具体的作用和起到的功能,到底应该如何应用它。

关于RAC的使用文章非常多,但是大多仅限于介绍和基本的使用方法,很少介绍RAC究竟应该如何优雅的嵌入到项目中。

在查阅资料的时候发现了此篇博文,写的非常细致,所以做了一次搬运工。

另,妹子我的英文属于渣渣系列,所以有什么翻译不当,请一定要指教。

Code

文章中所有代码在这里

RACCommand是你的新伙伴吗?

RACCommand是ReactiveCocoa最精华的部分之一,它可以让你在开发中节约大量的时间并让你的iOS或者OS X app有更强的鲁棒性。

我见过不少刚接触ReactiveCocoa(后文将简写为RAC),还不能完全理解RACCommand是如何工作又不知何时应该使用RACCommand的同学。所以我认为这个小介绍将会很实用,可以给他们带来一些启发。官方文档并没有给出多少如何使用RACCommand的Examples,但是RACCommand头文件的介绍还是很不错的,不过这对刚开始用RAC的同学来说还是太难理解了。

RACCommand类是用于表示一些操作的执行。通常,是由于UI上的一些事件触发了RACCommand的执行。比如当用户按了一个按钮,如果对应RACCommand实例可以被执行,就会执行相应的操作。这使得它很容易和UI进行绑定,同时可以保证当RACCommand处于not enabledRACCommand实例的操作不会被执行。当Command可以执行时,常做的方式是把allowsconcuuent的属性设置为NO,这可以保证Command已经执行完成后不会被重复执行。Command执行的结果是一个RACSignal,因此你可以调用next:completed:、或者error:。后面将会展示具体使用方式。

Example App

我们假设我们正在设计一个简单的app,其功能是让用户订阅一个邮件。最简单的方式是,用一个UITextField和一个UIButton。当用户输入email并且点击按钮的时候,email地址将会传给某个web服务。看起来很简单,但是我们应该确保用户有最好的体验。如果用户按了两次按钮?``如何处理请求出错?``如果email不合法?``RACCommand可以帮助我们处理这些情况。在这篇文章中将一步步完善这个小app以此来讨论一些概念和工作原理。

image

可以从这里获得源码。

从一个非常简单的ViewController可以很好的实践MVVM模式。

- (void)bindWithViewModel {
  RAC(self.viewModel, email) = self.emailTextField.rac_textSignal;
  self.subscribeButton.rac_command = self.viewModel.subscribeCommand;
  RAC(self.statusLabel, text) = RACObserve(self.viewModel, statusMessage);
}
复制代码

在上面的方法(在viewDidLoad中调用),在View和ViewModel中建立了绑定关系。下面是ViewModel的定义:


@interface SubscribeViewModel : NSObject

@property(nonatomic, strong) RACCommand *subscribeCommand;

// write to this property
@property(nonatomic, strong) NSString *email;

// read from this property
@property(nonatomic, strong) NSString *statusMessage;

@end
复制代码

如上所示,一个暴露出的RACCommand属性。另外两个是字符串属性,它们和View的两个属性绑定在一起。ViewModel的完整实现如下:

#import "SubscribeViewModel.h"
#import "AFHTTPRequestOperationManager+RACSupport.h"
#import "NSString+EmailAdditions.h"

static NSString *const kSubscribeURL = @"http://reactivetest.apiary.io/subscribers";

@interface SubscribeViewModel ()
@property(nonatomic, strong) RACSignal *emailValidSignal;
@end

@implementation SubscribeViewModel

- (id)init {
  self = [super init];
  if (self) {
      [self mapSubscribeCommandStateToStatusMessage];
  }
  return self;
}

- (void)mapSubscribeCommandStateToStatusMessage {
  RACSignal *startedMessageSource = [self.subscribeCommand.executionSignals map:^id(RACSignal *subscribeSignal) {
      return NSLocalizedString(@"Sending request...", nil);
  }];

  RACSignal *completedMessageSource = [self.subscribeCommand.executionSignals flattenMap:^RACStream *(RACSignal *subscribeSignal) {
      return [[[subscribeSignal materialize] filter:^BOOL(RACEvent *event) {
          return event.eventType == RACEventTypeCompleted;
      }] map:^id(id value) {
          return NSLocalizedString(@"Thanks", nil);
      }];
  }];

  RACSignal *failedMessageSource = [[self.subscribeCommand.errors subscribeOn:[RACScheduler mainThreadScheduler]] map:^id(NSError *error) {
      return NSLocalizedString(@"Error :(", nil);
  }];

  RAC(self, statusMessage) = [RACSignal merge:@[startedMessageSource, completedMessageSource, failedMessageSource]];
}

- (RACCommand *)subscribeCommand {
  if (!_subscribeCommand) {
      NSString *email = self.email;
      _subscribeCommand = [[RACCommand alloc] initWithEnabled:self.emailValidSignal signalBlock:^RACSignal *(id input) {
          return [SubscribeViewModel postEmail:email];
      }];
  }
  return _subscribeCommand;
}

+ (RACSignal *)postEmail:(NSString *)email {
  AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
  manager.requestSerializer = [AFJSONRequestSerializer new];
  NSDictionary *body = @{@"email": email ?: @""};
  return [[[manager rac_POST:kSubscribeURL parameters:body] logError] replayLazily];
}

- (RACSignal *)emailValidSignal {
  if (!_emailValidSignal) {
      _emailValidSignal = [RACObserve(self, email) map:^id(NSString *email) {
          return @([email isValidEmail]);
      }];
  }
  return _emailValidSignal;
}

@end
复制代码

这看起来真是一大坨~看还是从小的地方来看吧。我们真正感兴趣RACCommandRACCommand创建部分是以下代码:

- (RACCommand *)subscribeCommand {
  if (!_subscribeCommand) {
      NSString *email = self.email;
      _subscribeCommand = [[RACCommand alloc] initWithEnabled:self.emailValidSignal signalBlock:^RACSignal *(id input) {
          return [SubscribeViewModel postEmail:email];
      }];
  }
  return _subscribeCommand;
}
复制代码

Command通过一个enabledSignal参数来初始化。这个Signal可以指示Command是否可以被执行。在我们本次的用例中Command应该在用户合法输入email时允许被执行。self.emailValidSignal就是用来在email发生变化发送NO或者YES指示的。

signalBlock参数在Command需要执行时被调用。block应该返回一个signal。当我们设置allowsConcurrentExecutionNO,Command将会看守这个signal并且在本次执行未完成前不允许任何新的执行。

由于本次用例中的Command来自于按钮的rac_command(在UIButtton+RACCommandSupport分类中定义),根据Command是否可以被执行,按钮会自动切换enableddisabled状态。

当然,Command会在按钮被用户点击的时候自动执行。我们可以通过RACCommand自由的实现这一切。如果你需要手动执行你可以调用-[RACCommand execute:],参数是可选的,你可以传递nil。我们的用例里不需要参数,不过这里的参数通常会十分有用(按钮可以将自己当做-execute:的参数传入)。-execute:方法也是一个你可以监控执行状态的地方,你可以这样写:

[[self.viewModel.subscribeCommand execute:nil] subscribeCompleted:^{
  NSLog(@"The command executed");
}];
复制代码

在我们的用例中按钮为我们调用Command的执行(所以我们不需要手动调用-execute:),所以在Command执行时,为了及时更新UI,我们需要监听Command的另一个属性。有几个让人迷惑的地方,RACCommandexecutionSignals属性是一个每当Commands开始执行时就发送next:的Signal。问题在于Signal由Command创建,所以Signal中还有一层Signal。每次Command开始执行的时候, 我们会在ViewModel中通过mapSubscribeCommandStateToStatusMessage方法里面获取到一个信号。同时在这个信号里面返回了一个字符串:

RACSignal *startedMessageSource = [self.subscribeCommand.executionSignals map:^id(RACSignal *subscribeSignal) {
  return NSLocalizedString(@"Sending request...", nil);
}];
复制代码

假如我们想用更函数的方式,来在Command执行完成后都能获取string,我们需要做更多的工作:

RACSignal *completedMessageSource = [self.subscribeCommand.executionSignals flattenMap:^RACStream *(RACSignal *subscribeSignal) {
  return [[[subscribeSignal materialize] filter:^BOOL(RACEvent *event) {
      return event.eventType == RACEventTypeCompleted;
  }] map:^id(id value) {
      return NSLocalizedString(@"Thanks", nil);
  }];
}];
复制代码

当Command执行时,flattenMap:方法调用一个带subscribeSignal参数的block。这个block返回一个新的Signal并且它的值会被传递到下一个返回信号。materialize操作符让我们捕获到一个RACEvent(例如 next: completeerror:都是RACEvent的实例)。我们可以在信号完成之后过滤这些event并且映射成一个string。这些解释让你晕了吗,不过你可以去看一下flattenMap:materialize的文档以助于你的理解。

我们可以用另一种不同但更容易理解的方式来实现:

@weakify(self);
[self.subscribeCommand.executionSignals subscribeNext:^(RACSignal *subscribeSignal) {
  [subscribeSignal subscribeCompleted:^{
      @strongify(self);
      self.statusMessage = @"Thanks";
  }];
}];
复制代码

但是我并不喜欢上面的写法,因为这样会block中的操作会更多并且会更多的在block中使用到self。所以在这里还使用了@weakify@strongify(在libextobjc中定义)避免循环retain。

关于executionSignals属性,有一个重要的细节。在这里的Signal所发送的event不包含error,所以对于那些有特殊errors属性

RACSignal *failedMessageSource = [[self.subscribeCommand.errors subscribeOn:[RACScheduler mainThreadScheduler]] map:^id(NSError *error) {
  return NSLocalizedString(@"Error :(", nil);
}];
复制代码

如果我们有三个带有状态消息的Signal,我们可以将他们合并成一个信号并绑定到ViewModel的一个statusMessage属性 (statusMessage绑定ViewController的statusLabel.text)。

RAC(self, statusMessage) = [RACSignal merge:@[startedMessageSource, completedMessageSource, failedMessageSource]];
复制代码

那么以上是一个RACCommand在iOS app 开发中的一个example。我相信这种实现逻辑比使用UITextFieldDelegate有更多的优点,能在属性和变量中体现更多的状态。

其他有趣的RACCommand使用细节

RACCommand有一个executing属性,实际上它是一个当execute:时会发送YES,终止时发送NO的信号。在订阅信号时这个信号将会发送它的当前值,如果你只需要获取当前值而不需要获得信号,你可以通过以下方式:

BOOL commandIsExecuting = [[command.executing first] boolValue];
复制代码

enabled属性也是一个发送YESNO的信号。当Command通过发送NOenabledSignal信号创建,或者如果信号在执行并且allowsConcurrentExecutionsNOenabled就会发送NO

-execute:方法会自动订阅原始Signal并且广播它。这意味着你不需要去订阅-execute:返回的信号,但是如果你订阅了也不需要担心它会被执行两次。


有什么问题都可以在博文后面留言,或者微博上私信我,或者邮件我 coderfish@163.com

博主是 iOS 妹子一枚。

希望大家一起进步。

我的微博:小鱼周凌宇

date: 2016-08-05 10:33:00

原文地址

点击这里

这几天部门的前辈再用RAC的时候问到一个问题,RACCommand在RAC中具体的作用和起到的功能,到底应该如何应用它。

关于RAC的使用文章非常多,但是大多仅限于介绍和基本的使用方法,很少介绍RAC究竟应该如何优雅的嵌入到项目中。

在查阅资料的时候发现了此篇博文,写的非常细致,所以做了一次搬运工。

另,妹子我的英文属于渣渣系列,所以有什么翻译不当,请一定要指教。

Code

文章中所有代码在这里

RACCommand是你的新伙伴吗?

RACCommand是ReactiveCocoa最精华的部分之一,它可以让你在开发中节约大量的时间并让你的iOS或者OS X app有更强的鲁棒性。

我见过不少刚接触ReactiveCocoa(后文将简写为RAC),还不能完全理解RACCommand是如何工作又不知何时应该使用RACCommand的同学。所以我认为这个小介绍将会很实用,可以给他们带来一些启发。官方文档并没有给出多少如何使用RACCommand的Examples,但是RACCommand头文件的介绍还是很不错的,不过这对刚开始用RAC的同学来说还是太难理解了。

RACCommand类是用于表示一些操作的执行。通常,是由于UI上的一些事件触发了RACCommand的执行。比如当用户按了一个按钮,如果对应RACCommand实例可以被执行,就会执行相应的操作。这使得它很容易和UI进行绑定,同时可以保证当RACCommand处于not enabledRACCommand实例的操作不会被执行。当Command可以执行时,常做的方式是把allowsconcuuent的属性设置为NO,这可以保证Command已经执行完成后不会被重复执行。Command执行的结果是一个RACSignal,因此你可以调用next:completed:、或者error:。后面将会展示具体使用方式。

Example App

我们假设我们正在设计一个简单的app,其功能是让用户订阅一个邮件。最简单的方式是,用一个UITextField和一个UIButton。当用户输入email并且点击按钮的时候,email地址将会传给某个web服务。看起来很简单,但是我们应该确保用户有最好的体验。如果用户按了两次按钮?``如何处理请求出错?``如果email不合法?``RACCommand可以帮助我们处理这些情况。在这篇文章中将一步步完善这个小app以此来讨论一些概念和工作原理。

[站外图片上传中...(image-8ef4eb-1511406632743)]

可以从这里获得源码。

从一个非常简单的ViewController可以很好的实践MVVM模式。

- (void)bindWithViewModel {
  RAC(self.viewModel, email) = self.emailTextField.rac_textSignal;
  self.subscribeButton.rac_command = self.viewModel.subscribeCommand;
  RAC(self.statusLabel, text) = RACObserve(self.viewModel, statusMessage);
}
复制代码

在上面的方法(在viewDidLoad中调用),在View和ViewModel中建立了绑定关系。下面是ViewModel的定义:


@interface SubscribeViewModel : NSObject

@property(nonatomic, strong) RACCommand *subscribeCommand;

// write to this property
@property(nonatomic, strong) NSString *email;

// read from this property
@property(nonatomic, strong) NSString *statusMessage;

@end
复制代码

如上所示,一个暴露出的RACCommand属性。另外两个是字符串属性,它们和View的两个属性绑定在一起。ViewModel的完整实现如下:

#import "SubscribeViewModel.h"
#import "AFHTTPRequestOperationManager+RACSupport.h"
#import "NSString+EmailAdditions.h"

static NSString *const kSubscribeURL = @"http://reactivetest.apiary.io/subscribers";

@interface SubscribeViewModel ()
@property(nonatomic, strong) RACSignal *emailValidSignal;
@end

@implementation SubscribeViewModel

- (id)init {
  self = [super init];
  if (self) {
      [self mapSubscribeCommandStateToStatusMessage];
  }
  return self;
}

- (void)mapSubscribeCommandStateToStatusMessage {
  RACSignal *startedMessageSource = [self.subscribeCommand.executionSignals map:^id(RACSignal *subscribeSignal) {
      return NSLocalizedString(@"Sending request...", nil);
  }];

  RACSignal *completedMessageSource = [self.subscribeCommand.executionSignals flattenMap:^RACStream *(RACSignal *subscribeSignal) {
      return [[[subscribeSignal materialize] filter:^BOOL(RACEvent *event) {
          return event.eventType == RACEventTypeCompleted;
      }] map:^id(id value) {
          return NSLocalizedString(@"Thanks", nil);
      }];
  }];

  RACSignal *failedMessageSource = [[self.subscribeCommand.errors subscribeOn:[RACScheduler mainThreadScheduler]] map:^id(NSError *error) {
      return NSLocalizedString(@"Error :(", nil);
  }];

  RAC(self, statusMessage) = [RACSignal merge:@[startedMessageSource, completedMessageSource, failedMessageSource]];
}

- (RACCommand *)subscribeCommand {
  if (!_subscribeCommand) {
      NSString *email = self.email;
      _subscribeCommand = [[RACCommand alloc] initWithEnabled:self.emailValidSignal signalBlock:^RACSignal *(id input) {
          return [SubscribeViewModel postEmail:email];
      }];
  }
  return _subscribeCommand;
}

+ (RACSignal *)postEmail:(NSString *)email {
  AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
  manager.requestSerializer = [AFJSONRequestSerializer new];
  NSDictionary *body = @{@"email": email ?: @""};
  return [[[manager rac_POST:kSubscribeURL parameters:body] logError] replayLazily];
}

- (RACSignal *)emailValidSignal {
  if (!_emailValidSignal) {
      _emailValidSignal = [RACObserve(self, email) map:^id(NSString *email) {
          return @([email isValidEmail]);
      }];
  }
  return _emailValidSignal;
}

@end
复制代码

这看起来真是一大坨~看还是从小的地方来看吧。我们真正感兴趣RACCommandRACCommand创建部分是以下代码:

- (RACCommand *)subscribeCommand {
  if (!_subscribeCommand) {
      NSString *email = self.email;
      _subscribeCommand = [[RACCommand alloc] initWithEnabled:self.emailValidSignal signalBlock:^RACSignal *(id input) {
          return [SubscribeViewModel postEmail:email];
      }];
  }
  return _subscribeCommand;
}
复制代码

Command通过一个enabledSignal参数来初始化。这个Signal可以指示Command是否可以被执行。在我们本次的用例中Command应该在用户合法输入email时允许被执行。self.emailValidSignal就是用来在email发生变化发送NO或者YES指示的。

signalBlock参数在Command需要执行时被调用。block应该返回一个signal。当我们设置allowsConcurrentExecutionNO,Command将会看守这个signal并且在本次执行未完成前不允许任何新的执行。

由于本次用例中的Command来自于按钮的rac_command(在UIButtton+RACCommandSupport分类中定义),根据Command是否可以被执行,按钮会自动切换enableddisabled状态。

当然,Command会在按钮被用户点击的时候自动执行。我们可以通过RACCommand自由的实现这一切。如果你需要手动执行你可以调用-[RACCommand execute:],参数是可选的,你可以传递nil。我们的用例里不需要参数,不过这里的参数通常会十分有用(按钮可以将自己当做-execute:的参数传入)。-execute:方法也是一个你可以监控执行状态的地方,你可以这样写:

[[self.viewModel.subscribeCommand execute:nil] subscribeCompleted:^{
  NSLog(@"The command executed");
}];
复制代码

在我们的用例中按钮为我们调用Command的执行(所以我们不需要手动调用-execute:),所以在Command执行时,为了及时更新UI,我们需要监听Command的另一个属性。有几个让人迷惑的地方,RACCommandexecutionSignals属性是一个每当Commands开始执行时就发送next:的Signal。问题在于Signal由Command创建,所以Signal中还有一层Signal。每次Command开始执行的时候, 我们会在ViewModel中通过mapSubscribeCommandStateToStatusMessage方法里面获取到一个信号。同时在这个信号里面返回了一个字符串:

RACSignal *startedMessageSource = [self.subscribeCommand.executionSignals map:^id(RACSignal *subscribeSignal) {
  return NSLocalizedString(@"Sending request...", nil);
}];
复制代码

假如我们想用更函数的方式,来在Command执行完成后都能获取string,我们需要做更多的工作:

RACSignal *completedMessageSource = [self.subscribeCommand.executionSignals flattenMap:^RACStream *(RACSignal *subscribeSignal) {
  return [[[subscribeSignal materialize] filter:^BOOL(RACEvent *event) {
      return event.eventType == RACEventTypeCompleted;
  }] map:^id(id value) {
      return NSLocalizedString(@"Thanks", nil);
  }];
}];
复制代码

当Command执行时,flattenMap:方法调用一个带subscribeSignal参数的block。这个block返回一个新的Signal并且它的值会被传递到下一个返回信号。materialize操作符让我们捕获到一个RACEvent(例如 next: completeerror:都是RACEvent的实例)。我们可以在信号完成之后过滤这些event并且映射成一个string。这些解释让你晕了吗,不过你可以去看一下flattenMap:materialize的文档以助于你的理解。

我们可以用另一种不同但更容易理解的方式来实现:

@weakify(self);
[self.subscribeCommand.executionSignals subscribeNext:^(RACSignal *subscribeSignal) {
  [subscribeSignal subscribeCompleted:^{
      @strongify(self);
      self.statusMessage = @"Thanks";
  }];
}];
复制代码

但是我并不喜欢上面的写法,因为这样会block中的操作会更多并且会更多的在block中使用到self。所以在这里还使用了@weakify@strongify(在libextobjc中定义)避免循环retain。

关于executionSignals属性,有一个重要的细节。在这里的Signal所发送的event不包含error,所以对于那些有特殊errors属性

RACSignal *failedMessageSource = [[self.subscribeCommand.errors subscribeOn:[RACScheduler mainThreadScheduler]] map:^id(NSError *error) {
  return NSLocalizedString(@"Error :(", nil);
}];
复制代码

如果我们有三个带有状态消息的Signal,我们可以将他们合并成一个信号并绑定到ViewModel的一个statusMessage属性 (statusMessage绑定ViewController的statusLabel.text)。

RAC(self, statusMessage) = [RACSignal merge:@[startedMessageSource, completedMessageSource, failedMessageSource]];
复制代码

那么以上是一个RACCommand在iOS app 开发中的一个example。我相信这种实现逻辑比使用UITextFieldDelegate有更多的优点,能在属性和变量中体现更多的状态。

其他有趣的RACCommand使用细节

RACCommand有一个executing属性,实际上它是一个当execute:时会发送YES,终止时发送NO的信号。在订阅信号时这个信号将会发送它的当前值,如果你只需要获取当前值而不需要获得信号,你可以通过以下方式:

BOOL commandIsExecuting = [[command.executing first] boolValue];
复制代码

enabled属性也是一个发送YESNO的信号。当Command通过发送NOenabledSignal信号创建,或者如果信号在执行并且allowsConcurrentExecutionsNOenabled就会发送NO

-execute:方法会自动订阅原始Signal并且广播它。这意味着你不需要去订阅-execute:返回的信号,但是如果你订阅了也不需要担心它会被执行两次。


有什么问题都可以在博文后面留言,或者微博上私信我,或者邮件我 coderfish@163.com

博主是 iOS 妹子一枚。

希望大家一起进步。

我的微博:小鱼周凌宇

官方用法指南:

class-dump 3.5 (64 bit)
Usage: class-dump [options] <mach-o-file>

  where options are:
        -a             show instance variable offsets
        -A             show implementation addresses
        --arch <arch>  choose a specific architecture from a universal binary (ppc, ppc64, i386, x86_64)
        -C <regex>     only display classes matching regular expression
        -f <str>       find string in method name
        -H             generate header files in current directory, or directory specified with -o
        -I             sort classes, categories, and protocols by inheritance (overrides -s)
        -o <dir>       output directory used for -H
        -r             recursively expand frameworks and fixed VM shared libraries
        -s             sort classes and categories by name
        -S             sort methods by name
        -t             suppress header in output, for testing
        --list-arches  list the arches in the file, then exit
        --sdk-ios      specify iOS SDK version (will look in /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS<version>.sdk
        --sdk-mac      specify Mac OS X version (will look in /Developer/SDKs/MacOSX<version>.sdk
        --sdk-root     specify the full SDK root path (or use --sdk-ios/--sdk-mac for a shortcut)
复制代码

简单的举例:

class-dump -H /Applications/Calculator.app -o ~/Desktop/dump/Calculate-dump
复制代码

/Applications/Calculator.app 是 Mac 上计算器应用的路径。 ~/Desktop/dump/Calculate-dump 是指定的存放 dump 出来头文件的文件夹路径。

执行完成后可以看到指定的保存目录下已经有 dump 出来的头文件了:

image
> date: 2016-08-05 10:33:00

原文地址

点击这里

这几天部门的前辈再用RAC的时候问到一个问题,RACCommand在RAC中具体的作用和起到的功能,到底应该如何应用它。

关于RAC的使用文章非常多,但是大多仅限于介绍和基本的使用方法,很少介绍RAC究竟应该如何优雅的嵌入到项目中。

在查阅资料的时候发现了此篇博文,写的非常细致,所以做了一次搬运工。

另,妹子我的英文属于渣渣系列,所以有什么翻译不当,请一定要指教。

Code

文章中所有代码在这里

RACCommand是你的新伙伴吗?

RACCommand是ReactiveCocoa最精华的部分之一,它可以让你在开发中节约大量的时间并让你的iOS或者OS X app有更强的鲁棒性。

我见过不少刚接触ReactiveCocoa(后文将简写为RAC),还不能完全理解RACCommand是如何工作又不知何时应该使用RACCommand的同学。所以我认为这个小介绍将会很实用,可以给他们带来一些启发。官方文档并没有给出多少如何使用RACCommand的Examples,但是RACCommand头文件的介绍还是很不错的,不过这对刚开始用RAC的同学来说还是太难理解了。

RACCommand类是用于表示一些操作的执行。通常,是由于UI上的一些事件触发了RACCommand的执行。比如当用户按了一个按钮,如果对应RACCommand实例可以被执行,就会执行相应的操作。这使得它很容易和UI进行绑定,同时可以保证当RACCommand处于not enabledRACCommand实例的操作不会被执行。当Command可以执行时,常做的方式是把allowsconcuuent的属性设置为NO,这可以保证Command已经执行完成后不会被重复执行。Command执行的结果是一个RACSignal,因此你可以调用next:completed:、或者error:。后面将会展示具体使用方式。

Example App

我们假设我们正在设计一个简单的app,其功能是让用户订阅一个邮件。最简单的方式是,用一个UITextField和一个UIButton。当用户输入email并且点击按钮的时候,email地址将会传给某个web服务。看起来很简单,但是我们应该确保用户有最好的体验。如果用户按了两次按钮?``如何处理请求出错?``如果email不合法?``RACCommand可以帮助我们处理这些情况。在这篇文章中将一步步完善这个小app以此来讨论一些概念和工作原理。

[站外图片上传中...(image-8ef4eb-1511406632743)]

可以从这里获得源码。

从一个非常简单的ViewController可以很好的实践MVVM模式。

- (void)bindWithViewModel {
  RAC(self.viewModel, email) = self.emailTextField.rac_textSignal;
  self.subscribeButton.rac_command = self.viewModel.subscribeCommand;
  RAC(self.statusLabel, text) = RACObserve(self.viewModel, statusMessage);
}
复制代码

在上面的方法(在viewDidLoad中调用),在View和ViewModel中建立了绑定关系。下面是ViewModel的定义:


@interface SubscribeViewModel : NSObject

@property(nonatomic, strong) RACCommand *subscribeCommand;

// write to this property
@property(nonatomic, strong) NSString *email;

// read from this property
@property(nonatomic, strong) NSString *statusMessage;

@end
复制代码

如上所示,一个暴露出的RACCommand属性。另外两个是字符串属性,它们和View的两个属性绑定在一起。ViewModel的完整实现如下:

#import "SubscribeViewModel.h"
#import "AFHTTPRequestOperationManager+RACSupport.h"
#import "NSString+EmailAdditions.h"

static NSString *const kSubscribeURL = @"http://reactivetest.apiary.io/subscribers";

@interface SubscribeViewModel ()
@property(nonatomic, strong) RACSignal *emailValidSignal;
@end

@implementation SubscribeViewModel

- (id)init {
  self = [super init];
  if (self) {
      [self mapSubscribeCommandStateToStatusMessage];
  }
  return self;
}

- (void)mapSubscribeCommandStateToStatusMessage {
  RACSignal *startedMessageSource = [self.subscribeCommand.executionSignals map:^id(RACSignal *subscribeSignal) {
      return NSLocalizedString(@"Sending request...", nil);
  }];

  RACSignal *completedMessageSource = [self.subscribeCommand.executionSignals flattenMap:^RACStream *(RACSignal *subscribeSignal) {
      return [[[subscribeSignal materialize] filter:^BOOL(RACEvent *event) {
          return event.eventType == RACEventTypeCompleted;
      }] map:^id(id value) {
          return NSLocalizedString(@"Thanks", nil);
      }];
  }];

  RACSignal *failedMessageSource = [[self.subscribeCommand.errors subscribeOn:[RACScheduler mainThreadScheduler]] map:^id(NSError *error) {
      return NSLocalizedString(@"Error :(", nil);
  }];

  RAC(self, statusMessage) = [RACSignal merge:@[startedMessageSource, completedMessageSource, failedMessageSource]];
}

- (RACCommand *)subscribeCommand {
  if (!_subscribeCommand) {
      NSString *email = self.email;
      _subscribeCommand = [[RACCommand alloc] initWithEnabled:self.emailValidSignal signalBlock:^RACSignal *(id input) {
          return [SubscribeViewModel postEmail:email];
      }];
  }
  return _subscribeCommand;
}

+ (RACSignal *)postEmail:(NSString *)email {
  AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
  manager.requestSerializer = [AFJSONRequestSerializer new];
  NSDictionary *body = @{@"email": email ?: @""};
  return [[[manager rac_POST:kSubscribeURL parameters:body] logError] replayLazily];
}

- (RACSignal *)emailValidSignal {
  if (!_emailValidSignal) {
      _emailValidSignal = [RACObserve(self, email) map:^id(NSString *email) {
          return @([email isValidEmail]);
      }];
  }
  return _emailValidSignal;
}

@end
复制代码

这看起来真是一大坨~看还是从小的地方来看吧。我们真正感兴趣RACCommandRACCommand创建部分是以下代码:

- (RACCommand *)subscribeCommand {
  if (!_subscribeCommand) {
      NSString *email = self.email;
      _subscribeCommand = [[RACCommand alloc] initWithEnabled:self.emailValidSignal signalBlock:^RACSignal *(id input) {
          return [SubscribeViewModel postEmail:email];
      }];
  }
  return _subscribeCommand;
}
复制代码

Command通过一个enabledSignal参数来初始化。这个Signal可以指示Command是否可以被执行。在我们本次的用例中Command应该在用户合法输入email时允许被执行。self.emailValidSignal就是用来在email发生变化发送NO或者YES指示的。

signalBlock参数在Command需要执行时被调用。block应该返回一个signal。当我们设置allowsConcurrentExecutionNO,Command将会看守这个signal并且在本次执行未完成前不允许任何新的执行。

由于本次用例中的Command来自于按钮的rac_command(在UIButtton+RACCommandSupport分类中定义),根据Command是否可以被执行,按钮会自动切换enableddisabled状态。

当然,Command会在按钮被用户点击的时候自动执行。我们可以通过RACCommand自由的实现这一切。如果你需要手动执行你可以调用-[RACCommand execute:],参数是可选的,你可以传递nil。我们的用例里不需要参数,不过这里的参数通常会十分有用(按钮可以将自己当做-execute:的参数传入)。-execute:方法也是一个你可以监控执行状态的地方,你可以这样写:

[[self.viewModel.subscribeCommand execute:nil] subscribeCompleted:^{
  NSLog(@"The command executed");
}];
复制代码

在我们的用例中按钮为我们调用Command的执行(所以我们不需要手动调用-execute:),所以在Command执行时,为了及时更新UI,我们需要监听Command的另一个属性。有几个让人迷惑的地方,RACCommandexecutionSignals属性是一个每当Commands开始执行时就发送next:的Signal。问题在于Signal由Command创建,所以Signal中还有一层Signal。每次Command开始执行的时候, 我们会在ViewModel中通过mapSubscribeCommandStateToStatusMessage方法里面获取到一个信号。同时在这个信号里面返回了一个字符串:

RACSignal *startedMessageSource = [self.subscribeCommand.executionSignals map:^id(RACSignal *subscribeSignal) {
  return NSLocalizedString(@"Sending request...", nil);
}];
复制代码

假如我们想用更函数的方式,来在Command执行完成后都能获取string,我们需要做更多的工作:

RACSignal *completedMessageSource = [self.subscribeCommand.executionSignals flattenMap:^RACStream *(RACSignal *subscribeSignal) {
  return [[[subscribeSignal materialize] filter:^BOOL(RACEvent *event) {
      return event.eventType == RACEventTypeCompleted;
  }] map:^id(id value) {
      return NSLocalizedString(@"Thanks", nil);
  }];
}];
复制代码

当Command执行时,flattenMap:方法调用一个带subscribeSignal参数的block。这个block返回一个新的Signal并且它的值会被传递到下一个返回信号。materialize操作符让我们捕获到一个RACEvent(例如 next: completeerror:都是RACEvent的实例)。我们可以在信号完成之后过滤这些event并且映射成一个string。这些解释让你晕了吗,不过你可以去看一下flattenMap:materialize的文档以助于你的理解。

我们可以用另一种不同但更容易理解的方式来实现:

@weakify(self);
[self.subscribeCommand.executionSignals subscribeNext:^(RACSignal *subscribeSignal) {
  [subscribeSignal subscribeCompleted:^{
      @strongify(self);
      self.statusMessage = @"Thanks";
  }];
}];
复制代码

但是我并不喜欢上面的写法,因为这样会block中的操作会更多并且会更多的在block中使用到self。所以在这里还使用了@weakify@strongify(在libextobjc中定义)避免循环retain。

关于executionSignals属性,有一个重要的细节。在这里的Signal所发送的event不包含error,所以对于那些有特殊errors属性

RACSignal *failedMessageSource = [[self.subscribeCommand.errors subscribeOn:[RACScheduler mainThreadScheduler]] map:^id(NSError *error) {
  return NSLocalizedString(@"Error :(", nil);
}];
复制代码

如果我们有三个带有状态消息的Signal,我们可以将他们合并成一个信号并绑定到ViewModel的一个statusMessage属性 (statusMessage绑定ViewController的statusLabel.text)。

RAC(self, statusMessage) = [RACSignal merge:@[startedMessageSource, completedMessageSource, failedMessageSource]];
复制代码

那么以上是一个RACCommand在iOS app 开发中的一个example。我相信这种实现逻辑比使用UITextFieldDelegate有更多的优点,能在属性和变量中体现更多的状态。

其他有趣的RACCommand使用细节

RACCommand有一个executing属性,实际上它是一个当execute:时会发送YES,终止时发送NO的信号。在订阅信号时这个信号将会发送它的当前值,如果你只需要获取当前值而不需要获得信号,你可以通过以下方式:

BOOL commandIsExecuting = [[command.executing first] boolValue];
复制代码

enabled属性也是一个发送YESNO的信号。当Command通过发送NOenabledSignal信号创建,或者如果信号在执行并且allowsConcurrentExecutionsNOenabled就会发送NO

-execute:方法会自动订阅原始Signal并且广播它。这意味着你不需要去订阅-execute:返回的信号,但是如果你订阅了也不需要担心它会被执行两次。


有什么问题都可以在博文后面留言,或者微博上私信我,或者邮件我 coderfish@163.com

博主是 iOS 妹子一枚。

希望大家一起进步。

我的微博:小鱼周凌宇

打开一个 .h 文件可以看到相应内容:

[站外图片上传中...(image-95f25a-1511406635581)]

2 Dumpdecrypted

class dump 虽然能帮你解析出一个 app 的头文件,但是对于 App Store 下载的 App 都是通过苹果的一层签名加密,通常我们成为『加壳』。对于已经加壳的 APP,解析后的效果就和加了代码混淆类似。

比如直接去 dump 微信,出来的结果大概是这样:

[站外图片上传中...(image-545ab-1511406635581)]

2.1 Download

dumpdecrypted GitHub 地址

2.2 Install

Dumpdecrypted 比另一个砸壳工具 Clutch 要难用的多。但由于 Clutch 无法砸开含有兼容 WatchOs 2 的 App,所以只能使用 Dumpdecrypted。

下载后打开 Makefile 文件,注意第三行:

$ SDK=`xcrun --sdk iphoneos --show-sdk-path`
复制代码

这里填写的 SDK 必须与你越狱的 iPhone 系统等级一致,你可以这样查看你 MAC 的 SDK :

$ xcrun --sdk iphoneos --show-sdk-path
复制代码

输出:

/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.3.sdk
复制代码

而我的手机是 7.0 的,所以只能去这里下载对应的 SDK。

Makefile 所有需要填写的填好后:

$ cd dumpdecrypted
$ make
复制代码

在当前目录下生成 dumpdecrypted.dylib 文件。

如果你觉得很麻烦,可以直接来这里或者这里下载。

2.3 Use

dumpdecrypted.dylib 放到需要砸壳 app 的 Documents 下。

查找微信的 Documents:

$ find / -name WeChat.app
复制代码

得到路径:

/private/var/mobile/Applications/3DE3657E-0F69-45FF-928B-3DD5CD7A59FD/WeChat.app
复制代码

scp 传输:

$ scp ~/Desktop/dump/dumpdecrypted/dumpdecrypted_7.dylib root@172.16.212.217:/private/var/mobile/Applications/3DE3657E-0F69-45FF-928B-3DD5CD7A59FD/Documents
复制代码

砸壳:

$ cd /private/var/mobile/Applications/3DE3657E-0F69-45FF-928B-3DD5CD7A59FD/Documents
$ DYLD_INSERT_LIBRARIES=dumpdecrypted_7.dylib /private/var/mobile/Applications/3DE3657E-0F69-45FF-928B-3DD5CD7A59FD/WeChat.app/WeChat
复制代码

注意 DYLD_INSERT_LIBRARIES= 后填写的是你刚刚传输的 .dylib 文件名,我的是 dumpdecrypted_7.dylib

砸壳成功:

[+] detected 32bit ARM binary in memory.
[+] offset to cryptid found: @0xfea4c(from 0xfe000) = a4c
[+] Found encrypted data at address 00004000 of length 42237952 bytes - type 1.[+] Opening /private/var/mobile/Applications/3DE3657E-0F69-45FF-928B-3DD5CD7A59FD/WeChat.app/WeChat for reading.
[+] Reading header
[+] Detecting header type[+] Executable is a FAT image - searching for right architecture[+] Correct arch is at offset 16384 in the file[+] Opening WeChat.decrypted for writing.
[+] Copying the not encrypted start of the file[+] Dumping the decrypted data into the file[+] Copying the not encrypted remainder of the file[+] Setting the LC_ENCRYPTION_INFO->cryptid to 0 at offset 4a4c
[+] Closing original file[+] Closing dump file
复制代码

ls 查看 Documents 中文件多了一个 WeChat.decrypted,这就是砸壳过后的文件,scp出来:

$ scp WeChat.decrypted zhoulingyu@172.16.211.181:/Users/zhoulingyu/Desktop/dump/Wechat
复制代码

3 class dump 砸壳后的文件

上一步我们得到了 WeChat.decrypted,现在可以对其进行 dump:

class-dump --arch armv7 WeChat.decrypted -H -o /Users/zhoulingyu/Desktop/dump/Wechat/Wechat-decrypted-dump
复制代码

--arch armv7 是指定架构,dumpdecrypted 只会砸你手机处理器对应的那个壳,fat binary 的其它部分仍然是有壳的,而 class-dump 的默认目标又不是被砸壳的那个部分,如果不指定架构只能导出 CDStructures.h 一个文件

现在就可以看到 dump 后是明文的了:

[站外图片上传中...(image-135734-1511406635581)]

4 Learn More

当你能看到 .h 文件时候,意味着你知道了这个应用程序的各种接口,除了学习别人的优雅代码之外,显然也也可以做一些更有意思的事情,通过一些猜想和试验,我们可以去尝试做一个微信的插件。

下一次iOS攻防将会介绍动态库的注入和微信插件的制作~

如果您感兴趣~请点击下方打赏支持萌妹子的原创哟~


有什么问题都可以在博文后面留言,或者微博上私信我,或者邮件我 coderfish@163.com

博主是 iOS 妹子一枚。

希望大家一起进步。

我的微博:小鱼周凌宇

date: 2016-08-05 10:33:00

原文地址

点击这里

这几天部门的前辈再用RAC的时候问到一个问题,RACCommand在RAC中具体的作用和起到的功能,到底应该如何应用它。

关于RAC的使用文章非常多,但是大多仅限于介绍和基本的使用方法,很少介绍RAC究竟应该如何优雅的嵌入到项目中。

在查阅资料的时候发现了此篇博文,写的非常细致,所以做了一次搬运工。

另,妹子我的英文属于渣渣系列,所以有什么翻译不当,请一定要指教。

Code

文章中所有代码在这里

RACCommand是你的新伙伴吗?

RACCommand是ReactiveCocoa最精华的部分之一,它可以让你在开发中节约大量的时间并让你的iOS或者OS X app有更强的鲁棒性。

我见过不少刚接触ReactiveCocoa(后文将简写为RAC),还不能完全理解RACCommand是如何工作又不知何时应该使用RACCommand的同学。所以我认为这个小介绍将会很实用,可以给他们带来一些启发。官方文档并没有给出多少如何使用RACCommand的Examples,但是RACCommand头文件的介绍还是很不错的,不过这对刚开始用RAC的同学来说还是太难理解了。

RACCommand类是用于表示一些操作的执行。通常,是由于UI上的一些事件触发了RACCommand的执行。比如当用户按了一个按钮,如果对应RACCommand实例可以被执行,就会执行相应的操作。这使得它很容易和UI进行绑定,同时可以保证当RACCommand处于not enabledRACCommand实例的操作不会被执行。当Command可以执行时,常做的方式是把allowsconcuuent的属性设置为NO,这可以保证Command已经执行完成后不会被重复执行。Command执行的结果是一个RACSignal,因此你可以调用next:completed:、或者error:。后面将会展示具体使用方式。

Example App

我们假设我们正在设计一个简单的app,其功能是让用户订阅一个邮件。最简单的方式是,用一个UITextField和一个UIButton。当用户输入email并且点击按钮的时候,email地址将会传给某个web服务。看起来很简单,但是我们应该确保用户有最好的体验。如果用户按了两次按钮?``如何处理请求出错?``如果email不合法?``RACCommand可以帮助我们处理这些情况。在这篇文章中将一步步完善这个小app以此来讨论一些概念和工作原理。

image

可以从这里获得源码。

从一个非常简单的ViewController可以很好的实践MVVM模式。

- (void)bindWithViewModel {
  RAC(self.viewModel, email) = self.emailTextField.rac_textSignal;
  self.subscribeButton.rac_command = self.viewModel.subscribeCommand;
  RAC(self.statusLabel, text) = RACObserve(self.viewModel, statusMessage);
}
复制代码

在上面的方法(在viewDidLoad中调用),在View和ViewModel中建立了绑定关系。下面是ViewModel的定义:


@interface SubscribeViewModel : NSObject

@property(nonatomic, strong) RACCommand *subscribeCommand;

// write to this property
@property(nonatomic, strong) NSString *email;

// read from this property
@property(nonatomic, strong) NSString *statusMessage;

@end
复制代码

如上所示,一个暴露出的RACCommand属性。另外两个是字符串属性,它们和View的两个属性绑定在一起。ViewModel的完整实现如下:

#import "SubscribeViewModel.h"
#import "AFHTTPRequestOperationManager+RACSupport.h"
#import "NSString+EmailAdditions.h"

static NSString *const kSubscribeURL = @"http://reactivetest.apiary.io/subscribers";

@interface SubscribeViewModel ()
@property(nonatomic, strong) RACSignal *emailValidSignal;
@end

@implementation SubscribeViewModel

- (id)init {
  self = [super init];
  if (self) {
      [self mapSubscribeCommandStateToStatusMessage];
  }
  return self;
}

- (void)mapSubscribeCommandStateToStatusMessage {
  RACSignal *startedMessageSource = [self.subscribeCommand.executionSignals map:^id(RACSignal *subscribeSignal) {
      return NSLocalizedString(@"Sending request...", nil);
  }];

  RACSignal *completedMessageSource = [self.subscribeCommand.executionSignals flattenMap:^RACStream *(RACSignal *subscribeSignal) {
      return [[[subscribeSignal materialize] filter:^BOOL(RACEvent *event) {
          return event.eventType == RACEventTypeCompleted;
      }] map:^id(id value) {
          return NSLocalizedString(@"Thanks", nil);
      }];
  }];

  RACSignal *failedMessageSource = [[self.subscribeCommand.errors subscribeOn:[RACScheduler mainThreadScheduler]] map:^id(NSError *error) {
      return NSLocalizedString(@"Error :(", nil);
  }];

  RAC(self, statusMessage) = [RACSignal merge:@[startedMessageSource, completedMessageSource, failedMessageSource]];
}

- (RACCommand *)subscribeCommand {
  if (!_subscribeCommand) {
      NSString *email = self.email;
      _subscribeCommand = [[RACCommand alloc] initWithEnabled:self.emailValidSignal signalBlock:^RACSignal *(id input) {
          return [SubscribeViewModel postEmail:email];
      }];
  }
  return _subscribeCommand;
}

+ (RACSignal *)postEmail:(NSString *)email {
  AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
  manager.requestSerializer = [AFJSONRequestSerializer new];
  NSDictionary *body = @{@"email": email ?: @""};
  return [[[manager rac_POST:kSubscribeURL parameters:body] logError] replayLazily];
}

- (RACSignal *)emailValidSignal {
  if (!_emailValidSignal) {
      _emailValidSignal = [RACObserve(self, email) map:^id(NSString *email) {
          return @([email isValidEmail]);
      }];
  }
  return _emailValidSignal;
}

@end
复制代码

这看起来真是一大坨~看还是从小的地方来看吧。我们真正感兴趣RACCommandRACCommand创建部分是以下代码:

- (RACCommand *)subscribeCommand {
  if (!_subscribeCommand) {
      NSString *email = self.email;
      _subscribeCommand = [[RACCommand alloc] initWithEnabled:self.emailValidSignal signalBlock:^RACSignal *(id input) {
          return [SubscribeViewModel postEmail:email];
      }];
  }
  return _subscribeCommand;
}
复制代码

Command通过一个enabledSignal参数来初始化。这个Signal可以指示Command是否可以被执行。在我们本次的用例中Command应该在用户合法输入email时允许被执行。self.emailValidSignal就是用来在email发生变化发送NO或者YES指示的。

signalBlock参数在Command需要执行时被调用。block应该返回一个signal。当我们设置allowsConcurrentExecutionNO,Command将会看守这个signal并且在本次执行未完成前不允许任何新的执行。

由于本次用例中的Command来自于按钮的rac_command(在UIButtton+RACCommandSupport分类中定义),根据Command是否可以被执行,按钮会自动切换enableddisabled状态。

当然,Command会在按钮被用户点击的时候自动执行。我们可以通过RACCommand自由的实现这一切。如果你需要手动执行你可以调用-[RACCommand execute:],参数是可选的,你可以传递nil。我们的用例里不需要参数,不过这里的参数通常会十分有用(按钮可以将自己当做-execute:的参数传入)。-execute:方法也是一个你可以监控执行状态的地方,你可以这样写:

[[self.viewModel.subscribeCommand execute:nil] subscribeCompleted:^{
  NSLog(@"The command executed");
}];
复制代码

在我们的用例中按钮为我们调用Command的执行(所以我们不需要手动调用-execute:),所以在Command执行时,为了及时更新UI,我们需要监听Command的另一个属性。有几个让人迷惑的地方,RACCommandexecutionSignals属性是一个每当Commands开始执行时就发送next:的Signal。问题在于Signal由Command创建,所以Signal中还有一层Signal。每次Command开始执行的时候, 我们会在ViewModel中通过mapSubscribeCommandStateToStatusMessage方法里面获取到一个信号。同时在这个信号里面返回了一个字符串:

RACSignal *startedMessageSource = [self.subscribeCommand.executionSignals map:^id(RACSignal *subscribeSignal) {
  return NSLocalizedString(@"Sending request...", nil);
}];
复制代码

假如我们想用更函数的方式,来在Command执行完成后都能获取string,我们需要做更多的工作:

RACSignal *completedMessageSource = [self.subscribeCommand.executionSignals flattenMap:^RACStream *(RACSignal *subscribeSignal) {
  return [[[subscribeSignal materialize] filter:^BOOL(RACEvent *event) {
      return event.eventType == RACEventTypeCompleted;
  }] map:^id(id value) {
      return NSLocalizedString(@"Thanks", nil);
  }];
}];
复制代码

当Command执行时,flattenMap:方法调用一个带subscribeSignal参数的block。这个block返回一个新的Signal并且它的值会被传递到下一个返回信号。materialize操作符让我们捕获到一个RACEvent(例如 next: completeerror:都是RACEvent的实例)。我们可以在信号完成之后过滤这些event并且映射成一个string。这些解释让你晕了吗,不过你可以去看一下flattenMap:materialize的文档以助于你的理解。

我们可以用另一种不同但更容易理解的方式来实现:

@weakify(self);
[self.subscribeCommand.executionSignals subscribeNext:^(RACSignal *subscribeSignal) {
  [subscribeSignal subscribeCompleted:^{
      @strongify(self);
      self.statusMessage = @"Thanks";
  }];
}];
复制代码

但是我并不喜欢上面的写法,因为这样会block中的操作会更多并且会更多的在block中使用到self。所以在这里还使用了@weakify@strongify(在libextobjc中定义)避免循环retain。

关于executionSignals属性,有一个重要的细节。在这里的Signal所发送的event不包含error,所以对于那些有特殊errors属性

RACSignal *failedMessageSource = [[self.subscribeCommand.errors subscribeOn:[RACScheduler mainThreadScheduler]] map:^id(NSError *error) {
  return NSLocalizedString(@"Error :(", nil);
}];
复制代码

如果我们有三个带有状态消息的Signal,我们可以将他们合并成一个信号并绑定到ViewModel的一个statusMessage属性 (statusMessage绑定ViewController的statusLabel.text)。

RAC(self, statusMessage) = [RACSignal merge:@[startedMessageSource, completedMessageSource, failedMessageSource]];
复制代码

那么以上是一个RACCommand在iOS app 开发中的一个example。我相信这种实现逻辑比使用UITextFieldDelegate有更多的优点,能在属性和变量中体现更多的状态。

其他有趣的RACCommand使用细节

RACCommand有一个executing属性,实际上它是一个当execute:时会发送YES,终止时发送NO的信号。在订阅信号时这个信号将会发送它的当前值,如果你只需要获取当前值而不需要获得信号,你可以通过以下方式:

BOOL commandIsExecuting = [[command.executing first] boolValue];
复制代码

enabled属性也是一个发送YESNO的信号。当Command通过发送NOenabledSignal信号创建,或者如果信号在执行并且allowsConcurrentExecutionsNOenabled就会发送NO

-execute:方法会自动订阅原始Signal并且广播它。这意味着你不需要去订阅-execute:返回的信号,但是如果你订阅了也不需要担心它会被执行两次。


有什么问题都可以在博文后面留言,或者微博上私信我,或者邮件我 coderfish@163.com

博主是 iOS 妹子一枚。

希望大家一起进步。

我的微博:小鱼周凌宇