多线程学习

一.什么是多线程
      把不同的任务放到不通的线程当中去,解决界面卡死问题
    1. 为什么使用多线程示例:
    NSLog(@"%d", [[NSThread currentThread] isMainThread]);
    for (NSInteger i = 0; i < 100; i++) {
        NSLog(@"func1 i: %ld", i);
        [NSThread sleepForTimeInterval:1];
    }

二.NSThread
    2.1 三种创建线程方式
      //第一种创建线程的方式
     [self performSelectorInBackground:@selector(thread1) withObject:nil];
    函数结束之后,线程即自然结束了。

       //创建线程的第二种方式
      [NSThread detachNewThreadSelector:@selector(thread2:) toTarget:self withObject:@(100)];

      //创建线程的第三种方式
      NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(thread3) object:nil];
    [thread setName:@"thread3"];
    [thread start];

    2.2 监听线程结束
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(myThreadFinish:) name:NSThreadWillExitNotification object:nil];

    - (void)myThreadFinish:(NSNotification *)notification
    {
            NSThread *thread = [notification object];
            if ([thread.name isEqualToString:@"thread3"]) {
                    NSLog(@"thread3 finish!");
            }
            NSLog(@"thread ===%@",thread);
    }

    - (void)dealloc {
        // 对象销毁的时候删除观察者
        [[NSNotificationCenter defaultCenter] removeObserver:self     name:NSThreadWillExitNotification object:nil];
    }

     2.3 线程间通讯
    // 线程2走完之后给线程1发送cancel消息,线程1在内部判断自己是否被cancel掉,如果被cancel掉,则结束线程
        - (IBAction)sendCancel:(id)sender {
            NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(thread1:) object:@"线-程-1"];
            thread1.name = @"thread 1";
            
            NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(thread2:) object:thread1];
            thread2.name = @"thread 2";
            
            [thread1 start];
            [thread2 start];
        }

    thread1:
        while ([[NSThread currentThread] isCancelled] == NO) {
                [NSThread sleepForTimeInterval:2];
                NSLog(@"线程1正在执行循环....");
                // [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
                [[NSRunLoop currentRunLoop] run];
                
                // [self performSelector:@selector(thread2:) onThread:(NSThread *) withObject:(id) waitUntilDone:(BOOL)];
            }
            
            NSLog(@"线程1终结");
            [NSThread exit];


    第二种方法:performSelector:onThread:withObject:waitUntilDone:
// threadA下载结束之后,告诉threadB
- (IBAction)performSelectorOnThread:(id)sender {
   [NSThread detachNewThreadSelector:@selector(threadA) toTarget:self withObject:@"线程A"];

    _threadB = [[NSThread alloc] initWithTarget:self selector:@selector(threadB) object:nil];
    [_threadB start];
}

- (void)threadB {
    while (_isLoading) {
        //date = [NSDate dateWithTimeIntervalSinceNow:0.1];
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; // 阻塞到距现在0.1秒
    }
    NSLog(@"----over----");
}

    2.4 线程锁
对同一个资源的争抢就容易引起问题,
      [NSThread detachNewThreadSelector:@selector(saleTicket1:) toTarget:self withObject:nil];
    [NSThread detachNewThreadSelector:@selector(saleTicket2:) toTarget:self withObject:nil];
    [NSThread detachNewThreadSelector:@selector(saleTicket3:) toTarget:self withObject:nil];

- (void)saleTicket1:(id)obj {
    /**
    一旦线程 执行 [_lock lock],先检测一下 有没有加锁,如果加锁那么当前线程就会阻塞 直到 这个锁解锁之后 才能继续执行,如果 锁 没有锁着,立即加上锁锁住继续执行下面的代码
    同一个资源多个线程必须是同一把锁
     */
    while (_tickets > 0) {
        sleep(1);
        NSLog(@"线程 1 售了第 %ld 张票, 剩余:%ld", _tickets, --_tickets);
    }
}

- (void)saleTicket2:(id)obj {
    // [_lock lock];
    while (_tickets > 0) {
        sleep(1);
        NSLog(@"线程 2 售了第 %ld 张票, 剩余:%ld", _tickets, --_tickets);
    }
    // [_lock unlock];
}

- (void)saleTicket3:(id)obj {
    // [_lock lock];
    while (_tickets > 0) {
        sleep(1);
        NSLog(@"线程 3 售了第 %ld 张票, 剩余:%ld", _tickets, --_tickets);
    }
    // [_lock unlock];
}

synchronized同步
/*
 下面两个线程 执行的函数 共同操作同一个资源变量
 如果我们不做任何处理 那么两个线程就会争抢资源,这样就达不到我们想要的效果,数据有可能导致比较混乱
 如果两个线程要操作同一个资源,那么我们一般要进行加锁保护,当访问资源的时候进行加锁,访问结束 解锁
 */
    @synchronized(self) {//使线程同步
        
        for (NSInteger i = 0; i < 10; i++) {
            _cnt -= 10;
            NSLog(@"线程1: _cnt:%ld", _cnt);
            [NSThread sleepForTimeInterval:0.5];
        }
    }
。。。

    子线程刷新UI
    - (IBAction)go:(id)sender {
    [NSThread detachNewThreadSelector:@selector(thread) toTarget:self withObject:nil];
}

- (void)thread {
    while (1) {
        if (_progressView.progress >= 1) {
            break;
        }
        [NSThread sleepForTimeInterval:1];
        //刷新UI的操作子线程 不能做 只能由UI主线程来做
        //子线程 内部 可以通知主线程来进行刷新UI的操作
        //每隔 1s 通知主线程 刷新0.1
#if 0
        [self performSelectorOnMainThread:@selector(refreshUI:) withObject:@0.1 waitUntilDone:NO];
        NSLog(@"子线程");
#elif 0
        
        //2.通知主线程(GCD形式)
        //获取主线程队列
        //block 就是一个线程任务
        //把block 任务交给 主线程队列执行
        dispatch_async(dispatch_get_main_queue(), ^{
            self.progressView.progress += 0.1;
        });
#elif 1
        //3.通知主线程(NSOperationQueue)
        //创建一个请求任务
        NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(refreshUI:) object:@0.1];
        [[NSOperationQueue mainQueue] addOperation:operation];
#endif
    }
}

- (void)refreshUI:(NSNumber *)number {
    self.progressView.progress += [number floatValue];
    NSLog(@"主线程执行");
    //[NSThread sleepForTimeInterval:0.1];
}

自定义线程
//  From Doc: In OS X v10.5 and later, you can subclass NSThread and override the main method to implement your thread’s main entry point. If you override main, you do not need to invoke the inherited behavior by calling super.

- (void)viewDidLoad {
    [super viewDidLoad];
    
    MyThread *myThread = [[MyThread alloc] initWithTarget:self selector:@selector(myThread) object:nil];
    [myThread start];
}

- (void)myThread {
    for (NSInteger i = 0; i < 5; i++) {
        [NSThread sleepForTimeInterval:1];
        NSLog(@"自定义线程:%ld", i);
    }
}

#import "MyThread.h"

@interface MyThread ()
@property (nonatomic,weak) id target;
@property (nonatomic)SEL action;
@property (nonatomic,weak) id obj;

@end

@implementation MyThread

- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(id)argument {
    if (self = [super init]) {
        self.target = target;
        self.action = selector;
        self.obj    = argument;
    }
    return self;
}

- (void)main {
    NSLog(@"Mythread test %d", [NSThread isMainThread]);
    //执行线程的操作都是在线程的main 函数执行
    //在 线程的main函数执行 调用任务
    if ([self.target respondsToSelector:self.action]) {
        //执行
        [self.target performSelector:self.action withObject:self.obj];
    }
}

@end



三.NSOperationQueue
/**
 任务 NSOperation
 NSOperation是一个抽象类(当前类只声明方法不实现,由子类实现方法),创建任务我们需要创建NSOperation的子类对象
 创建任务之后,需要把任务放入任务队列/线程池中 才会异步执行任务
 
 将需要执行的操作封装到NSOperation对象中
 将NSOperation对象添加到NSOperationQueue中
 系统自动将NSOperationQueue中的NSOperation取出来
 将取出的NSOperation封装的操作放到一条新线程中执行
 
 NSOperation是个抽象类,并不具备封装操作的能力,要使用它的子类
 使用NSOperation子类有3种:
 NSInvocationOperation
 NSBlockOperation
 自定义子类继承NSOperation,实现内部相应的方法
 */
            - (void)viewDidLoad {
    [super viewDidLoad];
    
    [self invocationOperation];
}

- (void)invocationOperation {
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    queue.maxConcurrentOperationCount = 1;
    // 最大并发数
    // 如果设置为1 那么线程池中的子任务之间就是串行执行(子线程之间同步)
    
    // 1. NSInvocationOperation
    NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download:) object:@"任务1"];
    NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download:) object:@"作务2"];
    
    [queue addOperation:op1];
    [queue addOperation:op2];
    
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        [self download:@"任务3"];
    }];
    [queue addOperation:op3];
    
    NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
        [self download:@"任务4"];
    }];
    [op4 setCompletionBlock:^{
        NSLog(@"任务4结束");
    }];
    [queue addOperation:op4];
    
    [queue addOperationWithBlock:^{
        [self download:@"任务5"];
    }];
}

- (void)download:(id)obj {
    NSLog(@"Thread: %@ func: %s obj: %@", [NSThread currentThread], __func__, obj);
    for (int i = 0; i < 5; i++) {
        NSLog(@"%@ -> %d", obj, i);
        [NSThread sleepForTimeInterval:0.5];
    }
    NSLog(@"%@即将结束", obj);
}

@end

线程的取消和本互关系
- (void)testCancel {
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue setSuspended:YES]; // 暂停
    [self performSelector:@selector(start:) withObject:queue afterDelay:3];
    // queue.maxConcurrentOperationCount = 2;
    
    NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operation1:) object:@"任务1"];
    // [op1 start];
    [queue addOperation:op1];
    
    NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operation2:) object:@"任务2"];
    [queue addOperation:op2];
    
    op3 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operation3:) object:@"任务3"];
    [queue addOperation:op3];
    /*
     任务中的cancel
     可以取消在线程池中 还没有执行的任务
     如果线程池中的任务 已经执行了那么这时发送cancel 是取消不了正在执行的任务的。
     2. 如果 给一个正在执行的任务发送cancel  取消信号,这时 正在执行的任务取消不取消 取决于 自己,内部可以判断一下 是否被取消过
     */
    // [_queue cancelAllOperations];//-->可以取消 所有没有执行的任务
    // [queue cancelAllOperations];
}
        if ([op isCancelled]) {
            NSLog(@"%@被取消", obj);
            return;
        }

依赖关系:
- (void)testDependency {
    NSInvocationOperation *operation1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download1) object:nil];
    NSInvocationOperation *operation2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download2) object:nil];
    NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"Block operation 1");
    }];
    [operation3 addExecutionBlock:^{
        NSLog(@"Block operation 2");
    }];
    operation3.queuePriority = NSOperationQueuePriorityNormal;
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperationWithBlock:^{
        NSLog(@"Block operation 3");
    }];
    queue.maxConcurrentOperationCount = 3;
    [operation1 addDependency:operation2]; // op2执行结束后再执行op3
    
    [queue addOperation:operation1];
    [queue addOperation:operation2];
    [queue addOperation:operation3];
}

3.3 自定义的operation

@interface MyOperation : NSOperation

+ (instancetype)myBlockOperation:(void (^)(void))myBlock;

@end

#import "MyOperation.h"

@interface MyOperation ()

@property (nonatomic, copy) void (^myBlock)(void);
@end

@implementation MyOperation

+ (instancetype)myBlockOperation:(void (^)(void))myBlock {
    MyOperation *operation = [[MyOperation alloc] init];
    operation.myBlock = myBlock;
    return operation;
}

- (void)main {
    if (self.myBlock) {
        self.myBlock();
    }
}

@end
            
@implementation ZZCustomOperationController

- (void)viewDidLoad {
    [super viewDidLoad];

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    MyOperation *op1 = [MyOperation myBlockOperation:^{
//        for (int i = 0; i < 10; i++) {
//            NSLog(@"i:%d",i);
//            [NSThread sleepForTimeInterval:0.5];
//        }
        // http://static.shufawu.com/data/attachment/forum/201411/03/160032i0700ppp00pbu0m0.jpg
        NSString *url = @"http://static.shufawu.com/data/attachment/forum/201411/03/160032i0700ppp00pbu0m0.jpg";
        NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:url]];
//        dispatch_async(dispatch_get_main_queue(), ^{
//            UIImage *image = [UIImage imageWithData:data];
//            UIImageView *imageView = [[UIImageView alloc] initWithFrame:self.view.bounds];
//            imageView.image = image;
//            [self.view addSubview:imageView];
//        });
        
        //NSOperationQueue mainQueue 获取主线程的队列,在上面添加的operation会再主线程中执行
        NSInvocationOperation *updateUIOp = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(updateUI:) object:data];
        [[NSOperationQueue mainQueue] addOperation:updateUIOp];
    }];
    
    [queue addOperation:op1];
}

- (void)updateUI:(NSData *)data {
    UIImage *image = [UIImage imageWithData:data];
    UIImageView *imageView = [[UIImageView alloc] initWithFrame:self.view.bounds];
    imageView.image = image;
    [self.view addSubview:imageView];
    
}

@end





深入:自定义operation图片下载 复用

四.GCD
//GCD是一种底层高效的并发技术他提供了3中队列供我们使用
//1:主队列:放在主队列上的任务是在主线程中执行的
//2:全局并行队列:放在该队列上的任务,在子线程中并行执行
//3:串行队列:放到该队列上的任务,在子线程中串行执行
    4.1GCD种类
    /第一种,取得主线程:ios自带
    _mainQueue = dispatch_get_main_queue();
    
    //第二种,取得全局的一个线程:ios自带
    _globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    //第三种GCD多线程,用户自己创建的线程
    //第一个参数:myQueue   是一个字符串,作标识
    //第二个参数:这个GCD队列是串行还是并行
    //DISPATCH_QUEUE_SERIAL  串行
    //DISPATCH_QUEUE_CONCURRENT  并行
    //串行  就是队列里面,挨个执行.先来的先执行
    //并行  一起上
    _userQueue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);

    4.2 GCD调度方式
    //同步方式进行调用==串行
    dispatch_sync(<#dispatch_queue_t queue#>, <#^(void)block#>)
    //异步方式进行调用==并行
    dispatch_async(<#dispatch_queue_t queue#>, <#^(void)block#>)

    //  _mainQueue  不管你用何种方式去调度它,它都是串行
    //主线程不要使用同步的方式进行调度,会阻塞UI
    dispatch_async(_mainQueue, ^{
        NSLog(@"1");
    });
    dispatch_async(_mainQueue, ^{
        NSLog(@"2");
    });

- (void)createMainQueue {
    //获取 主线程队列(主线程队列不用创建)
    //主线程队列内部的任务是串行 执行
    //一般在子线程中才需要获取主线程队列
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    // 给主线程队列 添加任务
    dispatch_async(queue, ^{
        //主线程任务
        for (NSInteger i = 0 ; i < 10; i++) {
            NSLog(@"main1:_i = %ld",i);
            [NSThread sleepForTimeInterval:0.5];
        }
    });
    
    dispatch_async(dispatch_get_main_queue(), ^{
        //主线程任务
        for (NSInteger i = 0 ; i < 10; i++) {
            NSLog(@"main2:_i = %ld",i);
            [NSThread sleepForTimeInterval:0.5];
        }
    });
}

    //_globalQueue 全局队列
    //如果使用dispatch_sync  同步方式去调度,就是串行
    //如果使用dispatch_async 异步方式进行调度,就是并行
    - (void)createGlobalQueue {
    /*
     // 并行队列(全局)不需要我们创建,通过dispatch_get_global_queue()方法获得
     // 三个可用队列
     // 第一个参数是选取按个全局队列,一般采用DEFAULT,默认优先级队列
     // 第二个参数是保留标志,目前的版本没有任何用处(不代表以后版本),直接设置为0就可以了
     // DISPATCH_QUEUE_PRIORITY_HIGH
     // DISPATCH_QUEUE_PRIORITY_DEFAULT
     // DISPATCH_QUEUE_PRIORITY_LOW
     */
    
    //全局队列相对于主线程是异步的,并且内部的多个任务之间也是异步(并行)执行的
    //全局队列就一个不需要创建只用获取
    //给全局队列增加一个任务
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        for (NSInteger i = 0 ; i < 10; i++) {
            NSLog(@"全局队列任务1:_i = %ld",i);
            [NSThread sleepForTimeInterval:0.5];
        }
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        for (NSInteger i = 0 ; i < 10; i++) {
            NSLog(@"全局队列任务2:_i = %ld",i);
            [NSThread sleepForTimeInterval:0.5];
        }
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        for (NSInteger i = 0 ; i < 10; i++) {
            NSLog(@"全局队列任务3:_i = %ld",i);
            [NSThread sleepForTimeInterval:0.5];
        }
    });
}

 //得到全局并行队列
    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(globalQueue, ^{
       //在globalqueue上下载网络图片
        NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:MY_PIC_URL]];
        UIImage *image = [UIImage imageWithData:imageData];
        
        //切换到主线程上更新UI
        dispatch_async(dispatch_get_main_queue(), ^{
            //刷新UI
            imageView.image = image;
        });
    });

用户自己创建的队列(私有队列)
    //用户自己创建的队列,只要是用同步方式进行调度(dispatch_sync),则一定是串行
    //只有异步调用(dispatch_async)+ DISPATCH_QUEUE_CONCURRENT(并行)才是并行调用
 /*
     // C接口,创建一个私有队列 ,队列名是一个C字符串,没有特别的要求,Apple建议用倒装的标识符来表示(这个名字,更多用于调试)
     私有队列内部也是串行操作
     */
    //串行队列:内部的任务 都是串行执行
    //串行队列相对于主线程是异步 ,只不过内部的任务之间是串行的类似于 NSOperationQueue 最大并发个数1
- (void)createPrivateQueue {
    /*
     // C接口,创建一个私有队列 ,队列名是一个C字符串,没有特别的要求,Apple建议用倒装的标识符来表示(这个名字,更多用于调试)
     私有队列内部也是串行操作
    // dispatch_sync   同步提交,等到提交的任务结束,函数才返回,不要再主线程队列上进行同步提交,会造成死锁
    //dispatch_async  异步提交:只管提交,提交后直接返回
     */
    //串行队列:内部的任务 都是串行执行
    //串行队列相对于主线程是异步 ,只不过内部的任务之间是串行的类似于 NSOperationQueue 最大并发个数1
    
    dispatch_queue_t queue = dispatch_queue_create("com.1512", NULL);
    // dispatch_queue_t queue = dispatch_queue_create("com.1512", DISPATCH_QUEUE_CONCURRENT);
    //给串行队列添加任务
    dispatch_async(queue, ^{
        for (NSInteger i = 0 ; i < 10; i++) {
            NSLog(@"xxxxx串行队列任务1:_i = %ld",i);
            [NSThread sleepForTimeInterval:0.5];
        }
    });
    
    //增加任务
    dispatch_async(queue, ^{
        for (NSInteger i = 0 ; i < 10; i++) {
            NSLog(@"串行队列任务2:_i = %ld",i);
            [NSThread sleepForTimeInterval:0.5];
        }
    });
}

注意:
dispatch_sync(dispatch_get_main_queue(),^{
            NSLog(@"主线程队列上使用同步提交的方式,会造成死锁,所以在主线程队列上使用异步提交");
        });

GCD刷UI
    _progress = [[UIProgressView alloc]initWithFrame:CGRectMake(50, 100, 200, 30)];
    _progress.progress = 0;
    [self.view addSubview:_progress];

    dispatch_async(_globalQueue, ^{
        for ( int i = 1; i<11; i++)
        {
            float value = i*0.1;
            [NSThread sleepForTimeInterval:1.0];
            dispatch_async(_mainQueue, ^{
                    [_progress setProgress:value animated:YES];
            });
        }
    });

QueueGroup
- (void)createQueueGroup {
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_async(group, queue, ^{
        for (NSInteger i = 0 ; i < 10; i++) {
            NSLog(@"下载:_i = %ld",i);
            [NSThread sleepForTimeInterval:0.5];
        }
    });
    
    dispatch_group_async(group, queue, ^{
        for (NSInteger i = 0 ; i < 10; i++) {
            NSLog(@"******* 下载:_i = %ld",i);
            [NSThread sleepForTimeInterval:0.5];
        }
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"刷新UI");
    });
}

GCD创建单例:
#import "Student.h"

@implementation Student

static id _instance = nil;

+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _instance = [super allocWithZone:zone];
    });
    return _instance;
}

+ (instancetype)init {
    __block id _self = self;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        if ((_self = [super init])) {
            // ...
        }
    });
    return _self;
}

+ (instancetype)sharedInstance {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _instance = [[self alloc] init];
    });
    return _instance;
}

@end

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值