Back Up For Learning Runtime

Just a back up after learning Runtime.


R:南峰子的技术博客 & http://www.jianshu.com/p/927c8384855a...


Just copy following codes into the .m file , then run it and check results in the console.


Tips:

 1,对一个 Class 使用 class 方法获取不到它的 Meta-Class, 只会返回这个 Class.

 2,Method, Ivar, IMP 本身为指针, Protocol Obj-c , 需要 Protocol *;

 3,属性不包含成员变量, 成员变量包含了属性内隐式生成的成员变量('_'+属性名);

 4,类方法列表中有一个 C++的析构函数 .cxx_destruct. ARC 模式下 dealloc , 它被自动添加, 根据继承链调用来释放对象.

 5,自定义类继承 NSObject 会出错(Why?), 为自定义类添加方法时, 同时添加 C++IMP, @selector(名字:推荐和 IMP 一样, 首字母小写), type(What?"v@:" : v 表示返回值类型为 void, @:表示拥有 self _cmd参数时的 type 第二第三位固定字符);

 6,通过取得IMP,我们可以跳过Runtime的消息传递机制,直接执行IMP指向的函数实现,这样省去了Runtime消息传递过程中所做的一系列查找操作,会比直接向对象发送消息高效一些.

 7,当我们向一个对象发送消息时,runtime会在这个对象所属的这个类的方法列表中查找方法, 没有则去对象所属类的父类中查找;而向一个类发送消息时,会在这个类的meta-class的方法列表中查找, 没有则去这个类的父类的 meta-class 中查找。

 8,xxx_copyxx 到的数组需要 free().

 9,只能在 alocate register 新类和新的协议之间进行属性和方法等的更改, 注册后的操作不生效.

 */



#import <UIKit/UIKit.h>

#import "MyClass.h"

#import <objc/runtime.h>


void TestObjClass(id self, SEL _cmd){

    printf("Original method is %s\n", __func__);

}

void testObjClass(void){

    printf("Method changed to %s\n", __func__);

}


int main(int argc, char * argv[]) {

    @autoreleasepool {

        MyClass *myClass = [[MyClass alloc] init];

        unsigned int outCount = 0;

        Class cls = myClass.class;

        

        /*类名*/

        NSLog(@"Class name is %s", class_getName(cls));

        

        /*父类名*/

        NSLog(@"Super class name is %s", class_getName(class_getSuperclass(cls)));

        

        /*是否是元类*/

        NSLog(@"MyClass is %@ mate-class", class_isMetaClass(cls)? @"": @"not");

        

        /*元类*/

        Class metaClass = objc_getMetaClass(class_getName(cls));

        NSLog(@"%s's meta-class is %s", class_getName(cls), class_getName(metaClass));  //myClass 的元类是 MyClass

        NSLog(@"%s's meta-class is %p", class_getName(metaClass),  objc_getClass((__bridge void *)metaClass));  //MyClass 的元类是 NSObjcet的元类(0x0).对一个 Class 调用 class 方法只会返回 Class, 不会返回 Meta-Class, 所以使用地址打印.

        Class currentClass = [cls class];

        for (int i = 0; i < 4; i++) {

            NSLog(@"Following the isa pointer %d times gives %p", i, currentClass);

            currentClass = objc_getClass((__bridge void *)currentClass);

        }

        

        /*实例变量大小*/

        size_t size = class_getInstanceSize(cls);

        NSLog(@"Instance size is %zu", size);

        

        /*成员变量*/

        //获得成员变量列表

        Ivar *ivars = class_copyIvarList(cls, &outCount);

        for (int i = 0; i < outCount; i++) {

            Ivar ivar = *(ivars + i);

            NSLog(@"The %d ivar name is %s", i, ivar_getName(ivar));

        }  //成员变量包括@interface: 1,'{}'的成员变量 2,@property内隐式实现的成员变量('_'+属性名)

        free(ivars);

        

        Ivar string = class_getInstanceVariable(cls, "_string");

        NSLog(@"'_string' ivar name is %s", ivar_getName(string));  //属性 string 的成员变量名

        

        Ivar array = class_getInstanceVariable(cls, "_array");

        NSLog(@"'_array' ivar name is %s", ivar_getName(array));  //属性 array 的成员变量名

        

        /*属性操作*/

        //获得属性列表

        objc_property_t *properties = class_copyPropertyList(cls, &outCount);

        for (int i = 0; i < outCount; i++) {

            objc_property_t property = *(properties + i);

            NSLog(@"The %d property name is %s", i, property_getName(property));

        }

        free(properties);

        

        objc_property_t stringP = class_getProperty(cls, "string");

        NSLog(@"'string' property  name is %s", property_getName(stringP));  //获得属性string

        

        objc_property_t arrayP = class_getProperty(cls, "array");

        NSLog(@"'array' property name is %s", property_getName(arrayP));

        

        objc_property_t integer = class_getProperty(cls, "integer");

        NSLog(@"'integer' property name is %s", property_getName(integer));

        

        objc_property_t instance1 = class_getProperty(cls, "instance1");

        if (instance1) {

            NSLog(@"'_instance1' property name is %s", property_getName(instance1));

        }  //instance1 nil, 它为 _instance1 成员变量, 不是属性.

        

        /*方法操作*/

        //获得方法列表

        Method *methods = class_copyMethodList(cls, &outCount);

        for (int i = 0; i < outCount; i++) {

            Method method = *(methods + i);

            NSLog(@"The %d mehod name is %s", i, method_getName(method));

        }

        free(methods);

        

        //实例的方法会去类(实例的 Meta-Class)的方法列表内搜索, 类的方法回去元类(类的 Meta-Class)的方法列表内搜索.

        Method method1 = class_getInstanceMethod(cls, @selector(method1));  //通过@selector 搜索实例方法列表(ClassMethod),获得实例方法(-,动态)

        NSLog(@"The instance method name is %s", method_getName(method1));

        

        Method classMethod = class_getClassMethod(cls, @selector(classMethod1));  //通过@selector 搜索方法列表(Meta-ClassMethod),获得类方法(+,静态)

        NSLog(@"The class method name is %s", method_getName(classMethod));

        //获得方法实现, 并调用.

        IMP imp = class_getMethodImplementation(cls, @selector(method1));

        imp();

        IMP logStr = class_getMethodImplementation(cls, @selector(logString));

        //logStr(); crash! Why?

        /*协议*/

        Protocol * __unsafe_unretained *protocols = class_copyProtocolList(cls, &outCount);

        Protocol *protocol;  //protocols是一个协议列表, 在里面按下标获得协议的指针.

        for (int i = 0; i < outCount; i++) {

            protocol = *(protocols + i);

            NSLog(@"Protocol name is %s", protocol_getName(protocol));

        }

        free(protocols);

        

        NSLog(@"MyClass is %@ conform to protocol %s", class_conformsToProtocol(cls, protocol)? @"" : @"not", protocol_getName(protocol));

        

        /*动态创建类*/

        //创建一个 NSError 的子类, 名叫 TestClass, 申请额外空间为0.

        Class newClass = objc_allocateClassPair([NSError class], "TestClass", 0);

        // TestClass 添加一个叫 testObjClass 的方法, 实现为 IMPTestObjClass.

        class_addMethod(newClass,

                        @selector(testObjClass/*只是为 IMP 提供一个搜索的名字, 可随意更改, 建议与 IMP 相同, 首字母小写*/),

                        (IMP)TestObjClass/*提供了 c++ 的实现, 在调用 selector 时进行调用*/,

                        "v@:"/*@param types An array of characters that describe the types of the arguments to the method*/);

        //增加成员变量

        class_addIvar(newClass, "_ivar1", sizeof(NSString *), log(sizeof(NSString *)), "i");  //只有在申请空间和注册类之间调用

        //增加属性

        objc_property_attribute_t type = {"T","\"NSString\""};

        objc_property_attribute_t ownerShip = {"C", ""};

        objc_property_attribute_t backingIvar = {"V", "_ivar1"};

        objc_property_attribute_t attrs[] = {type, ownerShip, backingIvar};

        class_addProperty(newClass, "property2"/*属性名*/, attrs/*属性详情*/, 3/*属性个数*/);

        //注册此类

        objc_registerClassPair(newClass);

        //创建实例对象

        id instanceCls = [[newClass alloc] initWithDomain:@"some domain" code:0 userInfo:nil];

        //通过@selector 调用c++的实现方法

        [instanceCls performSelector:@selector(testObjClass)];

        //替换方法的实现

        class_replaceMethod(newClass, @selector(testObjClass), (IMP)testObjClass, "v@:");

        [instanceCls performSelector:@selector(testObjClass)];

        //注销类(不能存在它或它子类的实例)

        //objc_disposeClassPair(newClass);

        

        /*动态创建实例*/

        /*

         id class_createInstance (Class cls, size_t extraBytes) 默认位置动态创建实例,提供额外的参数额外字节数, ARC模式下不可用.

         id objc_constructInstance ( Class cls, void *bytes ) 指定位置创建实例

         void * objc_destructInstance ( id obj ) 销毁实例, 但不移除引用.

         */

        

        /*实例操作函数*/

        /*

         id object_copy(id obj, size_t size) 返回指定对象的拷贝, ARC 模式下不可用.

         id object_dispose(id obj) 销毁一个指定的对象的内存, ARC 模式下不可用.

         

         NSObject *a = [[NSObject alloc] init];  //父类的实例 a

         id newB = object_copy(a, class_getInstanceSize(myClass.class));  //拷贝父类实例, 并申请一个子类空间大小的内存.

         object_setClass(newB, myClass.class);  //设置父类的 Class 为子类 Class

         object_dispose(a);  //释放父类原实例

         */

        

        /*类实例中实例变量操作*/

        /*

         for MRC

         Ivar object_setInstanceVariable ( id obj, const char *name, void *value );  //修改类实例的实例变量的值

         Ivar object_getInstanceVariable ( id obj, const char *name, void **outValue );  //获取类实例的实例变量的值

         */

        

        void * object_getIndexedIvars ( id obj );  //获取给实例变量分配的额外地址指针

        

        /*for ARC, ivar 知道的情况下, object_get/setinstanceVariable 速度快.

         id object_getIvar(id obj, Ivar ivar);  //获得对象中实例变量的值

         id object_setIvar(id obj, Ivar ivar, id valur);  //设置对象中实例变量的值

         */

        

        /*针对对象的类进行操作*/

        const char * object_getClassName(id obj);  //获得对象的类名

        Class object_getClass(id obj);  //获得对象的类

        Class object_setClass(id obj, __unsafe_unretained Class cls);  //设置对象的类

        

        /*获得类定义*/

        int objc_getClassList ( Class *buffer, int bufferCount );  获取已注册的类定义的列表, 需要提供 buffer:类列表所需要的空间, bufferCount:类个数.

        Class * objc_copyClassList ( unsigned int *outCount );  //创建并返回一个指向所有已注册类的指针列表

        //三种方法返回指定类的类定义

        Class objc_lookUpClass ( const char *name );  //单次查看, 若类未注册, 返回 nil.

        Class objc_getClass ( const char *name );  //会调用类处理回调,并再次确认类是否注册,如果确认未注册,再返回nil.

        Class objc_getRequiredClass ( const char *name );  //操作与objc_getClass相同,只不过如果没有找到类,则会杀死进程.

        Class objc_getMetaClass ( const char *name );  //返回指定类的元类

        

        /*类型编码(Type Encoding):将每个方法的返回值和参数类型编码为一个字符串, 并关联 selector*/

        //@encode()返回这个类型的字符串编码, 任何sizeof()操作参数的类型都可以用于@encode().

        //:Objective-C不支持long double类型。@encode(long double)返回d,与double是一样的.

        float a[] = {1.0, 2.0, 3.0};

        NSLog(@"array encoding type: %s", @encode(typeof(a)));

        //console:array encoding type: [3f]

        

        

        /*关联对象(AssocatedObject)

          类似于成员变量, 但是在 runtime 时添加.

         */

        void objc_setAssociatedObject(id object/*大腿*/, const void *key/*标识符, @selector(method)*/, id value/*关联的属性*/, objc_AssociationPolicy policy/*内存策略*/);  // 设置 value nil 可覆盖原关联属性.

        id objc_getAssociatedObject(id object, const void *key);

        void objc_removeAssociatedObjects(id object);  // 移除所有关联, 慎用, 使用 set->nil.

        

        /*成员变量操作*/

        const char * ivar_getName(Ivar v);  //获得名字

        const char * ivar_getTypeEncoding(Ivar v);  // 获得类型编码字符串

        ptrdiff_t ivar_getOffset(Ivar v);  // 基祉偏移. ptrdiff_tC/C++标准库中定义的一个与机器相关的数据类型。ptrdiff_t类型变量通常用来保存两个指针减法操作的结果.

        

        /*属性操作*/

        const char * property_getName(objc_property_t property);

        const char * property_getAttributes(objc_property_t property);

        char * property_copyAttributeValue(objc_property_t property, const char *attributeName);  // 获得所描述的属性的值的字符串

        objc_property_attribute_t * property_copyAttributeList(objc_property_t property, unsigned int *outCount);

        

        /*方法和消息*/

        typedef struct objc_selector *SEL;

        /*

        1,Objective-C在编译时,会依据每一个方法的名字、参数序列,生成一个唯一的整型标识(Int类型的地址),这个标识就是SEL.

        2,父类子类相同的方法, SEL 相同.

        3,缺点是导致 OC 不支持泛型.

        4,所有的 SEL 组成一个 set 集合, 元素唯一.

        5,SEL 就是根据方法名 hash 化一个字符串,而对于字符串的比较仅仅需要比较他们的地址就可以了,可以说速度上无与伦比, 但方法过多会 hash 冲突, 所以应当减少 SEL.

        */

        //获取SEL 的方法有三种:

        SEL sel0 = sel_registerName(/*const char *str*/"str");

        SEL sel1 = @selector(main);

        SEL sel2 = NSSelectorFromString(@"main");

        

        /*IMP

         函数指针, 指向方法实现的首地址.

         */

        

        id (*IMP)(id, SEL, ...);

        /*

         参数

         id: 指向 self 的指针.实例方法则指向类实例的地址, 类方法则指向元类的地址.

         SEL: 方法唯一标识.由方法名和参数hash 化的一个 int 值地址, 用于唯一标识一个方法, 查找 IMP.

         ...: 方法的实际参数列表

         */

        

        /*Method

         用于表示类定义中的方法

         */

        typedef struct objc_method *Method;

        struct objc_method {

            SEL method_name;  //方法标识

            char *method_types;

            IMP method_imp;  //方法实现

        };

        //objc_method结构体相当于在 SEL IMP 之间建立了一个映射.

        struct objc_method_description{SEL name; char *types;};

        

        /*方法相关操作函数*/

        //id method_invoke ( id receiver, Method m, ... );  调用指定方法的实现

        //void method_invoke_stret ( id receiver, Method m, ... ); 调用返回一个数据结构的方法的实现

        //SEL method_getName ( Method m );  获取方法名

        //IMP method_getImplementation ( Method m );  返回方法的实现

        const char * method_getTypeEncoding ( Method m );  // 获取描述方法参数和返回值类型的字符串

        char * method_copyReturnType ( Method m );  // 获取方法的返回值类型的字符串

        char * method_copyArgumentType ( Method m, unsigned int index );  // 获取方法的指定位置参数的类型字符串

        void method_getReturnType ( Method m, char *dst, size_t dst_len );  // 通过引用返回方法的返回值类型字符串

        unsigned int method_getNumberOfArguments ( Method m );  // 返回方法的参数的个数

        void method_getArgumentType ( Method m, unsigned int index, char *dst, size_t dst_len );  // 通过引用返回方法指定位置参数的类型字符串

        //struct objc_method_description * method_getDescription ( Method m );  // 返回指定方法的方法描述结构体

        //IMP method_setImplementation ( Method m, IMP imp );  // 设置方法的实现, 返回值为之前的实现

        void method_exchangeImplementations ( Method m1, Method m2 );  // 交换两个方法的实现

        /*

         id target;

         void(*getMethod)(id,SEL) = (void(*)(id,SEL))[target methodForSelector:@selector(alloc)];

         getMethod(target,@selector(alloc));  //获得方法的实现, 并调用

         */

        

        /*方法选择器*/

        //const char *sel_getName(SEL sel);  根据 SEL 获得方法的字符串名字

        //SEL sel_registerName(const char *str);  runtime 注册一个 str 名称的方法, 并映射方法名到 SEL, 然后返回 SEl.

        //SEL sel_getUid(const char *str);  runtime 注册一个方法, 返回 SEL( sel_registerName的区别?).

        //BOOL sel_isEqual(SEL lhs, SEL rhs);

        

        /*方法调用流程

         [receiver message:arg1 :arg2 :...]->objc_msgSend(receiver, @selector(message), arg1, arg2, ...);

         */

        //1,当向一个对象发送消息的时候, 会到这个对象所属的类的方法分发表内查找方法, 如果没有则根据其 super_class 指针去所属的类的父类的分发表中查找. \

          2,当向一个类发送消息的时候, 会到这个类所属的类(元类)的方法分发表内查找方法, 如果没有则根据 所属类的 super_class 去其父类的元类中的分发表中查找.

        

        /*消息转发机制

         当消息是以[object message]调用时, 如果不能向该对象发送消息则编译时就会报错.

         当消息以 performSelector: 调用, 则不会在编译时校验是否接收消息, 而会在运行时执行, 不接收则由 -doesNotRecognizedSelector 扔出异常, 程序崩溃.

         可以通过调用 responsesToSelector: 来判断是否响应某个方法.

         

         消息转发机制包含了三个方面:

         */

        /*

         动态方法解析:

         当对象在接收到未知的消息时, 首先会调用 +resolveInstanceMethod:/+resolveClassMethod:, 我们可以在这个方法里面 class_addMethod 来处理.

         void functionForMethod1(id self, SEL _cmd) {

         NSLog(@"%@, %p", self, _cmd);

         }

         

         + (BOOL)resolveInstanceMethod:(SEL)sel {

         

         NSString *selectorString = NSStringFromSelector(sel);

         

         if ([selectorString isEqualToString:@"method1"]) {

         class_addMethod(self.class, @selector(method1), (IMP)functionForMethod1, "@:");  //在父类添加一个方法的实现

         }

         

         return [super resolveInstanceMethod:sel];  //去父类调用新添加的方法

         }

         */

        /*

         备用接受者

         当动态方法解析不能处理消息时, 会调用 - (id)forwardingTargetForSelector: 进行接受者转移.详见 Class TestForwarding.

         */

        

        /*

         完整消息转发

         调用 -forwardInvocation: 来最后一次处理未知消息, 将消息封装为一个 NSInvocation 对象(包括 selector, target, arguments), 它的作用有两个:

         1,定位可以响应 invocation 中的未知消息的对象.

         2, invocation 发送给可以处理的对象, 并将处理结果保存在 invocation , 运行时系统将会提取其中的结果发送给消息的原始接收者;

         */

        //-forwardInvocation: 中的参数需要重写另一个方法:-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector 来获得. 详见 Class TestForwarding.

        //NSObject 中的 -forwardInvocation: 方法只是简单的调用了 -doesNotRecognizedSelector.

        

        /*Method Swizzling

         用于改变 selector 映射的实际实现的技术. 详情见 Class UIViewController+Swizzling.

         */

        

        

        /*协议与分类

         */

        typedef struct objc_category *Category;

        

        struct objc_category {

            char *category_name                          OBJC2_UNAVAILABLE; // 分类名

            char *class_name                             OBJC2_UNAVAILABLE; // 分类所属的类名

            struct objc_method_list *instance_methods    OBJC2_UNAVAILABLE; // 实例方法列表

            struct objc_method_list *class_methods       OBJC2_UNAVAILABLE; // 类方法列表

            struct objc_protocol_list *protocols         OBJC2_UNAVAILABLE; // 分类所实现的协议列表

        };

        //objc_category 结构体中的 instance_methods objc_class 中方法列表的一个子集. class_methods 是元类方法列表的一个子集.

        //objc_class 中方法列表包含分类中的方法.

        

        typedef struct objc_object Protocol;

        /*

        Protocol * objc_getProtocol ( const char *name );  // 返回指定的协议

        Protocol ** objc_copyProtocolList ( unsigned int *outCount );  // 获取运行时所知道的所有协议的数组

        Protocol * objc_allocateProtocol ( const char *name );  // 创建新的协议实例, 若已经存在此协议则返回 nil;

        void objc_registerProtocol ( Protocol *proto );  // 在运行时中注册新创建的协议

        void protocol_addMethodDescription ( Protocol *proto, SEL name, const char *types, BOOL isRequiredMethod, BOOL isInstanceMethod );  // 为协议添加方法

        void protocol_addProtocol ( Protocol *proto, Protocol *addition );  // 添加一个已注册的协议到协议中

        void protocol_addProperty ( Protocol *proto, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount, BOOL isRequiredProperty, BOOL isInstanceProperty );  // 为协议添加属性

        const char * protocol_getName ( Protocol *p );  // 返回协议名

        BOOL protocol_isEqual ( Protocol *proto, Protocol *other );  // 测试两个协议是否相等

        struct objc_method_description * protocol_copyMethodDescriptionList ( Protocol *p, BOOL isRequiredMethod, BOOL isInstanceMethod, unsigned int *outCount );  // 获取协议中指定条件的方法的方法描述数组

        struct objc_method_description protocol_getMethodDescription ( Protocol *p, SEL aSel, BOOL isRequiredMethod, BOOL isInstanceMethod );  // 获取协议中指定方法的方法描述

        objc_property_t * protocol_copyPropertyList ( Protocol *proto, unsigned int *outCount );  // 获取协议中的属性列表

        objc_property_t protocol_getProperty ( Protocol *proto, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty );  // 获取协议的指定属性

        Protocol ** protocol_copyProtocolList ( Protocol *proto, unsigned int *outCount );  // 获取协议采用的协议

        BOOL protocol_conformsToProtocol ( Protocol *proto, Protocol *other );  // 查看协议是否采用了另一个协议

         */

        

        

        /*Super

         self 是一个隐藏参数, 会在每个方法的第一个参数.

         super 是一个编译器标识符, 用于告诉编译器这个方法到父类的方法分发表中查找.

         */

        struct objc_super {

            //id(struct objc_class *) receiver;

            Class super_Class;

        };

        /*

         super 发送消息时会调用 objc_msgSendSuper();

        1,id objc_msgSendSuper ( struct objc_super *super, SEL op, ... );

        2,objc_msgSend(objc_super->receiver, @selector(selector));

        3,objc_msgSend(self, @selector(selector));

        所以归根结底, 还是向 self 发送消息, 只不过调用的方法是从 super_class 的方法分发表中获得的.

         */

        

        /*库相关操作:

         framework, kit

         */

        // 获取所有加载的Objective-C框架和动态库的名称

        const char ** objc_copyImageNames ( unsigned int *outCount );

        

        // 获取指定类所在动态库

        const char * class_getImageName ( Class cls );

        

        // 获取指定库或框架中所有类的类名

        const char ** objc_copyClassNamesForImage ( const char *image, unsigned int *outCount );

        

        

        /*块操作

         oc 为了见兼容块操作, 将块绑定到函数指针上进行运行, 删除, 添加等操作.

         */

        /* 

         创建一个指针函数的指针,该函数调用时会调用特定的block

        IMP imp_implementationWithBlock ( id block );

        

        // 返回与IMP(使用imp_implementationWithBlock创建的)相关的block

        id imp_getBlock ( IMP anImp );

        

        // 解除blockIMP(使用imp_implementationWithBlock创建的)的关联关系,并释放block的拷贝

        BOOL imp_removeBlock ( IMP anImp );

         */

        

        /*弱引用操作

         不懂什么意思!!!!

         */

        // 加载弱引用指针引用的对象并返回

        id objc_loadWeak ( id *location );

        

        // 存储__weak变量的新值

        id objc_storeWeak ( id *location, id obj );

        

        /*

         objc_loadWeak函数:该函数加载一个弱指针引用的对象,并在对其做retainautoreleasing操作后返回它。这样,对象就可以在调用者使用它时保持足够长的生命周期。该函数典型的用法是在任何有使用__weak变量的表达式中使用。

         objc_storeWeak函数:该函数的典型用法是用于__weak变量做为赋值对象时。

         */

        

        

        

         return 0;

         }

        }



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值