ReactiveCocoa基本组件:理解和使用RACCommand

ZYNQ专题-7020驱动KSZ9031 PHY芯片 之所以有这篇文章,得益于Alinx的开发板。 阅读详情

原文地址在这里。

本文源码:https://github.com/olegam/RACCommandExample

 

RACCommand

RACCommand是ReactiveCocoa的基本组件之一,能节省开发的大部分时间,同时使得iOS/OS X 应用更健壮。

       我看到一部分ReactiveCocoa(以下简单RAC)新人并没有完全理解RACCommand,自然也就不知道怎么用它。所以我写了这个小小介绍性文章,希望能对你的理解有所帮助。RACCommand源文件里的注释写得很不错,不过它并没有给任何例子来说说具体怎么用它,对于RAC的新人来说,只看这些注释还是比较难以理解的。

       RACCommand类用于表示事件的执行,一般来说是在UI上的某些动作来触发这些事件,比如点击一个按钮。RACCommand的实例能够决定是否可以被执行,这个特性能反应在UI上,而且它能确保在其不可用时不会被执行。通常,当一个命令可以执行时,会将它的属性allowsConcurrentExecution设置为它的默认值:NO,从而确保在这个命令已经正在执行的时候,不会同时再执行新的操作。命令执行的返回值是一个RACSignal,因此我们能对该返回值进行next:,completed或error:,这在下文会有所展示。

 

例子应用

现在假设我们要写一个简单的iOS APP,它能让用户订阅邮件列表。我们将其做到最简单:一个输入框和一个按钮。当用户输入了电子邮箱地址、点击订阅按钮后,电子邮箱地址会提交到我们的web服务器上。够简单了吧!然而,我们还是得处理一些边界情况,以提供最好的体验。比如如果用户点击按钮两次怎么办?错误如何处理?如果邮箱地址非法呢?RACCommand能帮助我们处理这些情况。我已经实现了一个小的app来演示本文中所讨论的这些概念。

源码:https://github.com/olegam/RACCommandExample

 

本例用了一个非常简单的视图控制器,同样还演示了iOS应用的MVVM模式。

根视图控制器包含了视图,还有view model的实例。

[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. - (void)bindWithViewModel {     
  2.   RAC(self.viewModel, email) =self.emailTextField.rac_textSignal;   
  3.   self.subscribeButton.rac_command = self.viewModel.subscribeCommand;     
  4.   RAC(self.statusLabel, text) =RACObserve(self.viewModel, statusMessage);   
  5. }  

上面的这个方法(在viewDidLoad中调用)将view以及view model绑定。对于咱们感兴趣的代码在都view model中。看看view model的接口:

[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. @interface SubscribeViewModel :NSObject    
  2.   @property(nonatomic, strong)RACCommand *subscribeCommand;  // writeto this property  
  3.   @property(nonatomic, strong) NSString *email;  // read from this property  
  4.   @property(nonatomic, strong) NSString *statusMessage;    
  5. @end  
RACCommand暴露在这里,就是本文接下来要讨论的。另外还有两个被绑定到view上用于展示的字符串。这个view model的完整实现如下:

[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. #import "SubscribeViewModel.h"  
  2. #import "AFHTTPRequestOperationManager+RACSupport.h"  
  3. #import"NSString+EmailAdditions.h"  
  4.    
  5. static NSString *const kSubscribeURL =@"http://reactivetest.apiary.io/subscribers";  
  6.    
  7. @interface SubscribeViewModel ()  
  8. @property(nonatomic, strong) RACSignal*emailValidSignal;  
  9. @end  
  10.    
  11. @implementation SubscribeViewModel  
  12.    
  13. - (id)init {  
  14.        self= [super init];  
  15.        if(self) {  
  16.               [selfmapSubscribeCommandStateToStatusMessage];  
  17.        }  
  18.        returnself;  
  19. }  
  20.    
  21. -(void)mapSubscribeCommandStateToStatusMessage {  
  22.        RACSignal*startedMessageSource = [self.subscribeCommand.executionSignalsmap:^id(RACSignal *subscribeSignal) {  
  23.               returnNSLocalizedString(@"Sending request...", nil);  
  24.        }];  
  25.    
  26.        RACSignal*completedMessageSource = [self.subscribeCommand.executionSignalsflattenMap:^RACStream *(RACSignal *subscribeSignal) {  
  27.               return[[[subscribeSignal materialize] filter:^BOOL(RACEvent *event) {  
  28.                      returnevent.eventType == RACEventTypeCompleted;  
  29.               }]map:^id(id value) {  
  30.                      returnNSLocalizedString(@"Thanks", nil);  
  31.               }];  
  32.        }];  
  33.    
  34.        RACSignal*failedMessageSource = [[self.subscribeCommand.errors subscribeOn:[RACSchedulermainThreadScheduler]] map:^id(NSError *error) {  
  35.               returnNSLocalizedString(@"Error :(", nil);  
  36.        }];  
  37.    
  38.        RAC(self,statusMessage) = [RACSignal merge:@[startedMessageSource,completedMessageSource, failedMessageSource]];  
  39. }  
  40.    
  41. - (RACCommand *)subscribeCommand {  
  42.        if(!_subscribeCommand) {  
  43.               @weakify(self);  
  44.               _subscribeCommand= [[RACCommand alloc] initWithEnabled:self.emailValidSignalsignalBlock:^RACSignal *(id input) {  
  45.                      @strongify(self);  
  46.                      return[SubscribeViewModel postEmail:self.email];  
  47.               }];  
  48.        }  
  49.        return_subscribeCommand;  
  50. }  
  51.    
  52. + (RACSignal *)postEmail:(NSString *)email{  
  53.        AFHTTPRequestOperationManager*manager = [AFHTTPRequestOperationManager manager];  
  54.        manager.requestSerializer= [AFJSONRequestSerializer new];  
  55.        NSDictionary*body = @{@"email": email ?: @""};  
  56.        return[[[manager rac_POST:kSubscribeURL parameters:body] logError] replayLazily];  
  57. }  
  58.    
  59. - (RACSignal *)emailValidSignal {  
  60.        if(!_emailValidSignal) {  
  61.               _emailValidSignal= [RACObserve(self, email) map:^id(NSString *email) {  
  62.                      return@([email isValidEmail]);  
  63.               }];  
  64.        }  
  65.        return_emailValidSignal;  
  66. }  
  67.    
  68. @end  
  69. 呃,这是个大块头,我们一点一点来看。我们最感兴趣的RACCommand的创建如下:  
  70. - (RACCommand *)subscribeCommand {  
  71.        if(!_subscribeCommand) {  
  72.               @weakify(self);  
  73.               _subscribeCommand= [[RACCommand alloc] initWithEnabled:self.emailValidSignalsignalBlock:^RACSignal *(id input) {  
  74.                      @strongify(self);  
  75.                      return[SubscribeViewModel postEmail:self.email];  
  76.               }];  
  77.        }  
  78.        return_subscribeCommand;  
  79. }  

command的初始化方法中有一个enabledSignal参数,这个signal就是用来指名command能否被执行的。在我们的例子中,当用户输入的email地址合法时,它才能被执行。self.emailValidSignal这个signal每当email的文本更新时,会发送NO或YES。

signalBlock参数在command需要执行时调用,这个block需要返回一个signal用来表示正在执行,之前将allowsConcurrentExecute的值设置为默认值NO,此时command会观察这个signal,而且在这个执行进度完成前,不允许新的执行。

由于command是button的rac_command的属性(定义在UIButton+RACCommandSupport),这个button的enable状态会根据command能否执行来自动改变。

当用户点击按钮时,command会自动执行。如果你需要手动执行command,可以发送消息:-[RACCommand execute:],参数是可选的,在我们的例子中,我们不需要手动执行。尽管在我们这里不用,但它还是很有用的,这个-execute:方法是一个查看命令执行的状态的方法,如下:

[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. [[self.viewModel.subscribeCommand execute:nil] subscribeCompleted:^{  
  2.   NSLog(@"Thecommand executed");   
  3. }];  

在我们的例子中,button自动执行command(所以我们不需要调用-execute:),所以我们得监听command的另一个属性,在command执行时更新UI。在这里有几种选择来达到目的,也许会有点小迷惑。executionSignals是RACCommand的signal,每当command开始执行时next:,其参数是由command创建的signal,所以这个executionSignals是一个值为signal的signal。我们在view model的mapSubscribeCommandStateToStatusMessage方法中,在command每次开始执行时得到一个包含字符串值的signal:

[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. RACSignal*startedMessageSource = [self.subscribeCommand.executionSignalsmap:^id(RACSignal *subscribeSignal) {  
  2.   returnNSLocalizedString(@"Sending request...", nil);  
  3. }];  
如果我们想用纯粹的函数在command执行完成后得到一个包含字符串的signal,我们不得不多做一点小工作:

[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. RACSignal *completedMessageSource =[self.subscribeCommand.executionSignals flattenMap:^RACStream *(RACSignal*subscribeSignal) {  
  2.               return[[[subscribeSignal materialize] filter:^BOOL(RACEvent *event) {  
  3.                      returnevent.eventType == RACEventTypeCompleted;  
  4.               }]map:^id(id value) {  
  5.                      returnNSLocalizedString(@"Thanks", nil);  
  6.               }];  
  7.        }];  


flattenMap:方法在command执行时,会调用block并传入subscribeSignal,这个block会返回一个新的signal,它的值就在这个要返回的signal中。materialize会将一个signal转换为RACEvent信号(将一个signal的next:complete和error:消息转换为RACEvent实例的next:的值)。接下来我们就过滤这些事件,只留下RACEventTypeCompleted完成事件,并将其map成一个字符串值。看懂了没?如果没看懂的话,你最好看一下flattenMap:和materialize:是用来做什么的。

我们可以用一个不同的方法来实现上面这种行为,还可能更容易懂:

[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. @weakify(self);  
  2. [self.subscribeCommand.executionSignals subscribeNext:^(RACSignal*subscribeSignal) {    
  3.    [subscribeSignalsubscribeCompleted:^{        
  4.     @strongify(self);       
  5.     self.statusMessage= @"Thanks";    
  6.    }];   
  7. }];  

我不推荐上面这种方法,因为在block里的是副作用(side effects)。还有,上面的block会referring和retaining self,所以我用了@weakify和@strongify宏(在libextobjc中定义)来避免循环引用。所以为了避免使用这种副作用,还是要尽量采用原始的实现好一些。

还有一个要重点注意的是executionSignals属性,这个signals不会发送了error事件,而是由errors这个属性来发送的。在一个command的执行期间,如果一个signal发送了error,这会被signals当成一个next:事件发送,而errors属性则会发送这个错误信息。errors属性不会发送error:事件。我们很容易就能将错误信息map成一个字符串消息:

[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. RACSignal *failedMessageSource = [[self.subscribeCommand.errorssubscribeOn:[RACScheduler mainThreadScheduler]] map:^id(NSError *error) {  
  2.   returnNSLocalizedString(@"Error :(", nil);  
  3. }];  
现在我们有3个关于状态的signals了,现在要展示给用户看,我们将这个3个signals合并成一个signal,并将其绑定到view model的statusMessage属性上(这会被绑定到view controller的statusLabel.text属性上)。

[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. RAC(self, statusMessage) = [RACSignalmerge:@[startedMessageSource, completedMessageSource, failedMessageSource]];  
以上就是在iOS应用里怎么使用RACCommand的一例子了。我认为这个实现逻辑相比较于在view controller里用UITextFieldDegate实现来说要好得多,因为它不需要各种状态变量和属性。

微软登录OAuth 2.0授权流程 OAuth 2.0是一个关于授权的开放网络标准,它允许第三方应用获取用户数据,是目前最流行的授权机制。微软身份平台支持OAuth 2.0授权码流程,使得客户端应用程序能够获得对受保护资源(如网络API)的授权访问。这一流程在保障用户数据安全的前提下,实现了应用的便捷登录与数据共享。当用户在一个支持OAuth 2.0的应用上,希望使用微软账号进行登录时,OAuth 2.0授权流程即被触发。 阅读详情

相关推荐

对 RAC 中 RACCommand 的理解和应用

RACSignal 和 RACCommand RACCommand 是 RAC 中的最复杂的一个类之一,它也是一种广义上的信号。RAC 中信号其实是一种对象(或者是不同代码块)之间通信机制,在面向对象中,类之间的通信方式主要是方法调用,而信号也是一种调用,只不过它是函数式的,因此信号不仅仅可以在对象之间相互调用(传参),也可以在不同代码块(block)之间进行调用。 一般来说,RAC 中用 R...

kmyhy的专栏 8388

JFET放大电路实例设计仿真

参照《晶体管电路设计》中的例子 JFET 2SK184 JFET 2SK184 数据手册如下 电路设计 共源放大 步骤:1、静态工作点 2N4416 1ma 2、RD和RS A=5 5:1 RS 2V /1m=2k RD 5k 3、偏压VG 根据传输特性 VGS-ID vgs=-0.7 vg=2-0.7=1.3 400k^2/50k...

f的博客 7440

RACCommand使用总结

1、RACCommand是什么平常使用button可以使用rac_signalForControlEvents获得点击事件的信号,执行点击事件。rac提供了rac_command来执行某个block。首先看下RACCommand的基本属性有哪些:1. executionSignals:需要执行的block成功的时候返回的信号,他是在主线程执行的。 2. executing:判断当前的block是否在

taishanduba的专栏 9990

基于PlatformIO与CMSIS的STM32库函数开发实战:从环境搭建到外设驱动

本文详细介绍了基于PlatformIO与CMSIS的STM32库函数开发实战,从环境搭建到外设驱动实现。通过对比传统IDE,PlatformIO结合CMSIS框架在跨平台开发、代码可移植性和调试体验上更具优势。文章包含PlatformIO.ini配置详解、GPIO/USART驱动开发实例及常见问题解决方案,帮助开发者高效完成STM32项目开发。

weixin_29190169的博客 421

使用循环神经网络预测温度

简介 本文以天气时间序列数据集为例,讨论如何使用深度学习模型预测未来天气温度。 使用的是由德国耶拿马克思普朗克生物地球化学研究所的气象站记录。 数据每10分钟记录一次,共记录14种不同的量,如气温、气压、湿度、风向等,原始数据可追溯到2003年,本例只使用2009-2016年的数据。 数据 下载并解压数据略。 加载数据: data_dir = r'E:\practice\tf2\temperature' fname = os.path.join(data_dir, 'jena_climate_2009_

guotianqing的博客 5281

优雅的 RACCommand

前言 RACCommand是一个在 ReactiveCocoa 中比较复杂的类,大多数使用 ReactiveCocoa 的人,尤其是初学者并不会经常使用它。 在很多情况下,虽然使用 RACSignal 和 RACSubject 就能解决绝大部分问题,但是 RACCommand 的使用会为我们带来巨大的便利,尤其是在与副作用相关的操作中。 文章中不会讨论 RACCommand 中的并行执行问题,也就是忽略了 allowsConcurrentExecution 以及 allowsConcurrentExec

Rey的技术博客 537

PADS VX2.7学习记录06-PADS Router软件功能操作

PADS VX2.7学习记录06-PADS Router软件功能操作

qq_31444421的博客 7860

RACCommand使用

1.RACCommand:RAC中用于处理事件的类,可以把事件如何处理,事件中的数据如何传递,包装到这个类中,他可以很方便的监控事件的执行过程。 2.使用场景:监听按钮点击,网络请求   RACCommand的简单使用        一、RACCommand使用步骤:      1.创建命令 initWithSignalBlock:(RACSignal * (^)(id input))...

九天揽月 1600

实践指南——【SPSS操作解析】线性混合效应模型在重复测量数据中的应用

本文详细解析了线性混合效应模型在重复测量数据分析中的应用,并提供了SPSS操作指南。文章阐述了该模型如何通过区分固定效应与随机效应,有效处理数据非独立性和缺失值问题,从而获得更可靠的统计推断。

oo7890的博客 550

SA8295P QNX主机上电启动流程分析

嵌入式系统的启动过程对于确保系统正常运行至关重要。本文将详细分析SA8295P芯片在QNX主机上的上电启动流程,并提供相应的源代码示例。嵌入式系统的启动过程对于确保系统正常运行至关重要。本文将详细分析SA8295P芯片在QNX主机上的上电启动流程,并提供相应的源代码示例。以下是一个SA8295P QNX主机上电启动流程分析。

CodeMaven的博客 1230

RACCommandExample 项目教程

RACCommandExample 项目教程 1、项目介绍 RACCommandExample 是一个基于 ReactiveCocoa (RAC) 框架的示例项目,旨在帮助开发者理解和使用 RACCommand 组件。ReactiveCocoa 是一个功能强大的响应式编程框架,广泛应用于 iOS 和 macOS 开发中。RACCommand 是 ReactiveCocoa 中的一个核心组件,用于封...

gitblog_00082的博客 309

Centos 7 Linux 离线安装单机 Kafka:下载、安装、配置、使用

本文详细介绍了在 CentOS 7 系统上离线安装单机版 Apache Kafka 的完整流程,包括环境准备(JDK 安装)、Kafka 安装包上传与解压、关键配置修改(如监听地址)、ZooKeeper 与 Kafka 服务的启动,以及 Topic 的创建、消息的生产和消费等基础操作。适用于无网络环境下的 Kafka 快速部署与学习,为后续开发或测试提供本地消息队列支持。

寒水馨 · 轻量级技术文档 · hanshuixin.org 1319

ReactiveCocoa要点:理解和使用RACCommand

这篇文章附带的源代码在github: https://github.com/olegam/RACCommandExample 是RACCommand新的最好的朋友? 的 RACCommand是最重要的部分之一ReactiveCocoa最终可以节省你大量的时间和帮助使你的iOS或OS X应用程序更健壮。 我见过几个人新ReactiveCocoa(以下略RAC)不完全了解 R

King的专栏 8882

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

ReactiveCocoa:理解和使用RACCommand

ReactiveCocoa基本组件:理解和使用RACCommand RACCommand RACCommand是ReactiveCocoa的基本组件之一,能节省开发的大部分时间,同时使得iOS/OS X 应用更健壮。        我看到一部分ReactiveCocoa(以下简单RAC)新人并没有完全理解RACCommand,自然也就不知道怎么用它。所以我写了这个小小介绍

我叫MT 936

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

FunctionalReactivePixels源码深度剖析:ReactiveCocoa信号流设计模式

FunctionalReactivePixels是一个基于ReactiveCocoa框架构建的iOS应用示例,通过500px API展示了函数式响应式编程(FRP)在移动开发中的实际应用。本文将深入剖析其信号流设计模式,帮助开发者理解如何利用ReactiveCocoa构建响应式iOS应用。 ## 核心架构概览:信号驱动的数据流 FunctionalReactivePixels采用MVVM架构,

gitblog_00896的博客 947

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

探索RACCommand的神奇魅力:构建响应式iOS应用

探索RACCommand的神奇魅力:构建响应式iOS应用 在当今快速迭代的移动开发领域,寻找高效、灵活的编程模型成为了开发者们的共同追求。今天,我们要带您深入了解一个基于Reactivecocoa的精华示例——RACCommand示例应用,这是一扇通往响应式编程世界的窗,让您能够领略到如何利用RACCommand的强大特性来构建更加优雅的应用程序。 项目介绍 RACCommand示例应用 是一款精...

gitblog_00398的博客 719

AI视频生成可控性实战:Kling 4.0五种玩法拆解

AI视频生成技术快速演进,但创作者常面临镜头运动失控、角色漂移、画面随机性过强等问题。可控性的本质在于让创作者掌握镜头语言、角色动作与叙事节奏的决策权,而非单纯依赖模型自由发挥。理解首尾帧锚定、提示词语义驱动、运镜参数调节、角色参考图锁定以及多镜头分镜编排,是提升生成命中率的核心路径。这些技术手段广泛应用于产品短片、剧情转场、个人品牌视频和系列化内容创作中,帮助创作者从“抽卡式生成”转向“导演式制作”。掌握这些基础控制逻辑,能显著降低试错成本,让AI视频真正服务于精准表达。本文以Kling 4.0为实践对象

weixin_34320724的博客 325

iOS开发:初探ReactiveCocoa

前言 上次接触ReactiveCocoa已经是三年前的事情了,最近公司开新项目了,我选择了MVVM+ReactiveCocoa架构,但是开始撸代码时发现RAC的接口好像变化很大,就再此学习一下。 框架引入 框架介绍 为了适应Objective-C、swift,ReactiveCocoa将代码拆分为四个库:ReactiveCocoa(集中于UI)、ReactiveSwift(swift版)、Re...

ctguqsx的专栏 236

TypeSpec 生成 ASP.NET Core C 服务的完整指南:代码布局、Mock 脚手架与运行原理

本文以 `@typespec/http-server-csharp` 发射器(emitter)生成的示例服务为对象,系统讲解 TypeSpec 规范编译为 ASP.NET Core C# 服务后的目录布局、`hscs` 脚手架与 `--emit-mocks` 选项生成的工程文件、SwaggerUI 调试入口,以及控制器、业务逻辑接口、依赖注入容器、模型四者之间的协作原理。读完本文,你将能够理解生成

gitblog_01123的博客 314

iOS学习笔记39-ReactiveCocoa入门

一、响应式编程范式FRPFRP,全称为Functional Reactive Programming,是一种响应变化的编程范式,最近几年比较火,大概的理解就像这样: 当a的值或者b的值发生变化时,c的值会自动响应a的值或b的值变化的信号,自动更正自己的值,类似这种编程思想就称为FRP。FRP提供了一种信号机制来实现这样的效果,通过信号来记录值的变化。信号可以被叠加、分割或合并。通过对信号的组合

执着丶执念的博客 1544

ReactiveCocoa 学习笔记七(RACCommand)

RACCommand RACCommand 关键的两个方法如下,理解了他们便能理解 RACCommand 的作用。 - (instancetype)initWithEnabled:(nullable RACSignal<NSNumber *> *)enabledSignal signalBlock:(RACSignal<ValueType> * (^)(InputT...

集腋成裘 702

iOS学习笔记 ReactiveCocoa入门

一、响应式编程范式FRP FRP,全称为Functional Reactive Programming,是一种响应变化的编程范式,最近几年比较火,大概的理解就像这样: 当a的值或者b的值发生变化时,c的值会自动响应a的值或b的值变化的信号,自动更正自己的值,类似这种编程思想就称为FRP。 FRP提供了一种信号机制来实现这样的效果,通过信号来记录值的变化。信号可以被叠加

sifenkesi1的专栏 466

FunctionalReactivePixels终极指南:iOS开发中ReactiveCocoa实战案例详解

FunctionalReactivePixels是一个基于ReactiveCocoa框架的iOS应用示例,展示了如何在iOS开发中使用函数式响应式编程(FRP)模式与500px API交互。本文将通过完整的项目结构解析和核心功能实现,帮助开发者快速掌握FRP在实际项目中的应用技巧。 ## 🚀 项目架构概览:FRP核心组件解析 FunctionalReactivePixels采用经典的MVVM

gitblog_00409的博客 976
上一篇: ReactiveCocoa 入门指导
下一篇: copypng emitted errors but did not return a nonzero exit code to indicate failure
kintan哥
博客等级 码龄12年 11粉丝 32原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值