2.多线程—IOS

3.0 多线程

(1)基本概念
- 进程:系统中正常运行的一个应用程序(每个进程之间都是独立,且均运行在专用并受保护的内存空间中)。
- 线程:线程是进程的基本执行单元(每个进程至少有一个线程)。
- 线程的串行:一个线程执行多个任务时,只能单个执行(同一时间内,一个线程只能执行一个任务、线程是进程中一条执行路径)。
- 多线程:一个进程开启多条线程,每个线程可并行(同时)执行不同任务(多线程技术可提高程序的执行效率)。
- 线程同步/同步锁/互斥锁:多条线程在同一条线上(按顺序)执行。
- 线程通信:在一个进程中,线程往往不是孤立存在的,多个线程之间是存在有通信关系的(体现:1.数据传递;2.任务转接)

(2)多线程原理
- 1.同一时间,CPU只能处理一条线程,只有一条线程在工作
- 2.多线程并发执行时,其实是CPU快速的在线程之间调度
- 3.如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象

(3)多线程的优缺点
- 优点:
- 1.能够适当的提高程序的执行效率
- 2.能够适当提高资源的利用率(CPU、内存的利用率)
- 缺点:
- 线程会占用一定内存控件(默认主线程占1M,子线程占512KB)。线程过多将降低程序的性能。
- 线程越多,CPU在调度线程上的开销越大。
- 增加程序设计复杂度:如线程间通信、多线程的数据共享等。

(4)主线程
- 主线程:一个IOS程序运行后,默认开启一条线程,该线程称“主线程”或“UI线程
- 主线程作用:1.显示/刷新UI界面;2.处理UI事件(点击、滚动、拖拽等)。
- 主线程使用的注意事项:不要将比较耗时的操作放置在主线程中(会严重影响UI的流畅度,造成”卡”的感觉,体验度下降)。

(5)多线程的使用方案
这里写图片描述

(6)其他
- 线程阻塞:调用sleep、等待同步锁会进入阻塞(sleep到时、等到同步锁会进入就绪状态等待调度)
- 线程死亡:线程任务执行完毕、异常、强制退出会造成线程死亡(一旦线程死亡,不能再开启任务)
这里写图片描述

3.1 NSThreaed

  • initWithTarget:创建线程
  • detachNewThreadSelector:类方法创建并启动线程
  • lock:线程加锁
  • unlock:线程解锁
  • sleepForTimeInterval:线程休眠

1.. 同步锁/互斥锁/线程同步
- (1)优点
- 有效的防止因多线程抢夺资源造成的数据安全问题;
- (2)缺点
- 因为线程等待,需要消耗大量的CPU资源;

@interface NSThreaedViewController ()
{
    NSThread *_thread01;
    NSThread *_thread02;
    NSInteger _counter;
    NSLock *_lock;
}

@end

@implementation NSThreaedViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];

    _lock = [[NSLock alloc] init];

    NSArray *ary = @[@"启动线程01", @"启动线程02", @"启动线程03", @"线程休眠01", @"线程休眠02", @"强制退出线程"];
    NSUInteger number = [ary count];
    for(int i=0; i<number; i++){
        UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];

        btn.frame = CGRectMake(100, 100+80*i, 80, 40);

        [btn addTarget:self action:@selector(pressBtn:) forControlEvents:UIControlEventTouchDown];

        btn.tag = 101 + i;

        [btn setTitle:ary[i] forState:UIControlStateNormal];

        [self.view addSubview:btn];
    }
    // Do any additional setup after loading the view from its nib.
}

- (void)pressBtn :(UIButton *)btn
{
    switch (btn.tag) {
        case 101:
        {
            /*创建一个线程对象
             P1:线程对象运行函数的拥有者
             P2:线程处理函数
             P3:线程参数*/
            NSThread *newT = [[NSThread alloc] initWithTarget:self selector:@selector(createThread:) object:@"线程01"];

            //启动并且运行线程;
            [newT start];
            break;
        }
        case 102:
        {
            //创建并启动线程
            //不能赋值,因为没有返回值
            [NSThread detachNewThreadSelector:@selector(createThread:) toTarget:self withObject:@"线程02"];
            break;
        }
        case 103:
        {
            //开一个后台线程(子线程)
            [self performSelectorInBackground:@selector(createThread:) withObject:@"线程03"];
            break;
        }
        case 104:
        {
            [self performSelectorInBackground:@selector(createThreadSleep:) withObject:@"休眠线程01"];
            break;
        }
        case 105:
        {
            [self performSelectorInBackground:@selector(createThreadSleep:) withObject:@"休眠线程02"];
            break;
        }
        case 106:
        {
            [self performSelectorInBackground:@selector(createThreadSleep:) withObject:@"强制退出线程"];
            break;
        }
        default:
            break;
    }
}

-(void)createThread :(NSString *)str{
    if(![str isEqualToString:@"线程03"]){
        //第一种方法加同步锁
        for(int i=0; i<2500; i++){
            [_lock lock];

            _counter++;

            [_lock unlock];

            NSLog(@"%ld", _counter);
        }

        //第二种方法加同步锁
//        @synchronized(self) {
//            for(int i=0; i<2500; i++){
//                
//                _counter++;
//                
//                NSLog(@"%ld", _counter);
//            }
//        }


        NSLog(@"thread01: %ld", _counter);
    }
    else if([str isEqualToString:@"线程03"]){
        NSLog(@"线程03 %@", str);

        //waitUntilDone: YES:回调函数执行完成,再执行下面代码
        //                NO:同步执行下面代码
        [self performSelectorOnMainThread:@selector(returnThread:) withObject:@"回调函数" waitUntilDone:YES];

        NSLog(@"回调函数成功");
    }

    NSLog(@"线程:%@, 输出参数:%@", [NSThread currentThread], str);
}

-(void) returnThread:(NSString *)str
{
    NSLog(@"在回调函数中");
}



-(void) createThreadSleep : (NSString *)str
{
    //线程休眠
    NSLog(@"线程:%@,参数:%@", [NSThread currentThread], str);

    if([str isEqualToString:@"休眠线程01"]){
        //第一种方法
        //P1:时间(秒)
        [NSThread sleepForTimeInterval:2];
    }
    else if([str isEqualToString:@"休眠线程02"]){
        //第二种方法
        //P1:传入定制的时间
        [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:5]];
    }
    else if([str isEqualToString:@"强制退出线程"]){
        for(int i=0; i<1000; i++){

            NSLog(@"%d", i);

            if(i == 200){
                [NSThread exit];
            }
        }

    }

    NSLog(@"再次执行线程");
}

3.2 NSOperation

  • NSOperationQueue:任务队列
  • NSInvocationOperation:任务对象类型
  • initWithTarget:创建任务
  • addOperation:operation:添加任务到队列中
  • setMaxConcurrentOperationCount:设置同时并发的任务数量

1) 步骤
- 1.将需要执行的操作封装到NSOperation中
- 2.将NSOperation对象添加到NSOperationQueue中
- 3.系统将自动将NSOperationQueue’中的NSOperation取出来
- 4.将取出来的NSOperation封装的操作放到一条线程中执行

2) 作用
- 如果将NSOperation添加到NSOperationQueue中,系统会异步执行NSOperation的操作

3) 挂起和取消
- 挂起:需要暂停队列的任务的执行时候使用(Suspended为”YES”即可)
- 注意:1.当任务处于执行状态,设置队列挂起不会影响其执行,受影响的是那些还没有执行的任务; 2.当队列设置为挂起状态后,可以通过修改其状态再次恢复任务。
- 取消:当取消任务后,任务不可恢复。(- (void)cancelAllOperations)
- 注意:若任务操作是自定义NSOperation类型的话,执行完一个耗时操作后,需加是否取消任务的判断,再去执行另外一个耗时操作。

4) 程序
- OperationViewController.m

#import "OperationViewController.h"
#import "customOperation.h"

#define IMAGE_URL @"http://pic33.nipic.com/20130916/3420027_192919547000_2.jpg"

@interface OperationViewController ()

@property (nonatomic, strong) NSOperationQueue *queueOne;

@end

@implementation OperationViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    [self.view setBackgroundColor:[UIColor whiteColor]];

    NSArray *arr = @[@"invocation", @"BlockOperation", @"自定义", @"任务队列", @"依赖关系", @"最大并发数", @"任务挂起/恢复(先按“最大。。”)", @"任务取消"];
    NSUInteger count = [arr count];
    for (int i=0; i<count; i++) {
        UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];

        btn.frame = CGRectMake(80, 80+50*i, 60, 40);

        [btn setTitle:arr[i] forState:UIControlStateNormal];

        btn.tag = 100 + i;

        [btn sizeToFit];

        [btn addTarget:self action:@selector(btnAcntion:) forControlEvents:UIControlEventTouchDown];

        [self.view addSubview: btn];
    }

    // Do any additional setup after loading the view.
}

-(void)btnAcntion :(UIButton *)btn
{
    switch (btn.tag) {
        case 100:   //invocation
            [self invocationOperation];
            break;
        case 101:
            [self BlockOperation];
            break;
        case 102:
            [self custom];
            break;
        case 103:
            [self mainQueue];
            break;
        case 104:
            [self depenDency];
            break;
        case 105:
            [self maxConcurreatCount];
            break;
        case 106:
            [self suspdendAndCancel:@"suspdend"];
            break;
        case 107:
            [self suspdendAndCancel:@"cancel"];
            break;
        default:
            break;
    }
}

//invocationOperation
- (void) invocationOperation
{
    //默认情况下,调用start方法后,并不回开启新的线程去执行操作,而是在当线程中进行同步操作
    //只有讲NSOperation放到NSOperationQueue中才会执行异步操作

    NSInvocationOperation *ip = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperationAction) object:nil];

    //开启任务
    [ip start];

//    [self per]
}
- (void) invocationOperationAction
{
    NSLog(@"----1---%@", [NSThread currentThread]);
}


//BlockOperation
- (void) BlockOperation
{
    //只要NSBlockOperation封装的操作数大于1,就回开启线程,异步执行

    //在主线程中完成
    NSBlockOperation *bp = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"----2---%@", [NSThread currentThread]);
    }];

    //额外任务,在子线程中执行
    [bp addExecutionBlock:^{
        NSLog(@"----3 ---%@", [NSThread currentThread]);
    }];

    [bp start];
}


// 自定义(需要重写main方法)
- (void) custom {
    [super didReceiveMemoryWarning];

    customOperation *co = [[customOperation alloc] init];

    [co start];
}


// 主队列
- (void) mainQueue
{
    // 创建队列(默认创建并行队列)
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(mainAction:) object: @"op"];

    NSInvocationOperation *opT = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(mainAction:) object: @"opT"];

    // 将操作放置于队列中, 不需要用start开启
    [queue addOperation: op];
    [queue addOperation: opT];

    [queue addOperationWithBlock:^{
        NSLog(@"-----6---- %@", [NSThread currentThread]);
    }];
}

-(void)mainAction :(NSString *)str
{
    if([str isEqualToString: @"op"]){
        NSLog(@"-----4---- %@", [NSThread currentThread]);
    }
    else if([str isEqualToString: @"opT"]){
        NSLog(@"-----5---- %@", [NSThread currentThread]);
    }

}

// 任务依赖  --- 如果一依赖二, 等二执行完成,再执行一
- (void)depenDency
{
    // 任务之间不能相互依赖
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    NSBlockOperation *bp = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任务一:%@", [NSThread currentThread]);
    }];

    NSBlockOperation *bpTwo = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任务二:%@", [NSThread currentThread]);
    }];

    NSBlockOperation *bpThree = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任务三:%@", [NSThread currentThread]);
    }];

    // 前者依赖于后者,先执行后者,再执行前者
    [bpTwo addDependency: bp];

    [queue addOperation: bp];
    [queue addOperation: bpTwo];
    [queue addOperation: bpThree];
}

// 最大并发数量
- (void)maxConcurreatCount
{
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    queue.maxConcurrentOperationCount = 2;

    [queue addOperationWithBlock:^{
        for(int i=0; i<1000; i++){
            NSLog(@"线程一,%@, %d", [NSThread currentThread], i);
        }
    }];

    [queue addOperationWithBlock:^{
        for(int i=0; i<1000; i++){
            NSLog(@"线程二,%@, %d", [NSThread currentThread], i);
        }
    }];

    [queue addOperationWithBlock:^{
        for(int i=0; i<1000; i++){
            NSLog(@"线程三,%@, %d", [NSThread currentThread], i);
        }
    }];

    self.queueOne = queue;
}

// 任务挂起与取消
- (void)suspdendAndCancel : (NSString *)str
{
    if([str isEqualToString: @"suspdend"]){
        if(!self.queueOne.suspended){
            self.queueOne.suspended = YES;
        }
        else{
            self.queueOne.suspended = NO;
        }
    }
    //如果是自定义的队列需要在自定义中使用self.cancelled判断是否已经取消.若无判断将继续执行
    else if([str isEqualToString:@"cancel"]){
        [self.queueOne cancelAllOperations];
    }
}
  • customOperation.m
    - (void) main{
    NSLog(@"---4-----%@", [NSThread currentThread]);
}

3.3 GCD

(1)基本概念
- Grand Central Dispatch(伟大的中枢调度器)(纯C语言)
- GCD会自动利用更多的CPU内核(如双核、四核)
- GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
- 程序员只需要告诉GCD执行的任务,不需要编写任何线程管理代码
- 队列:存放任务
- 使用步骤:1.定制任务;2.将任务添加到队列中–GCD会自动将队列的任务取出,放到对应的线程中执行(任务取出遵守队列的FIFO原则:先进先出,后进后出)
- 任务执行方式:同步任务、异步方式(同步:在当前线程中执行任务,不能开启新线程能力;异步:可在新线程中执行任务,能开启新线程的能力)
- 队列类型:并发队列、串行队列(并发队列:在异步函数下,可多个任务并发执行,会自动开启多个线程同事执行任务;串行队列:任务一个一个执行(需要等前一个线程完成)。

(2)队列的创建
这里写图片描述
这里写图片描述

(3) 程序

#import "GCDViewController.h"

#define IMAGE_URL @"http://pic33.nipic.com/20130916/3420027_192919547000_2.jpg"

@interface GCDViewController ()

@property (nonatomic, strong) IBOutlet UIImageView *imageView;
@end

@implementation GCDViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    [self.view setBackgroundColor: [UIColor whiteColor]];

    self.imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 300, 200, 150)];

    NSArray *arr = @[@"并行队列", @"线程通信", @"串行队列", @"主队列", @"全程队列"];
    NSUInteger count = [arr count];
    for(int i=0; i<count; i++){
        UIButton *btn = [UIButton buttonWithType: UIButtonTypeSystem];

        btn.frame = CGRectMake(80, 80 + i * 50, 70, 40);

        [btn setTitle:arr[i] forState: UIControlStateNormal];

        btn.tag = 100 + i;

        [btn sizeToFit];

        [btn addTarget:self action:@selector(btnAcntion:) forControlEvents:UIControlEventTouchDown];

        [self.view addSubview: btn];
    }
    // Do any additional setup after loading the view.
}

-(void)btnAcntion: (UIButton *)btn
{
    switch (btn.tag) {
        case 100:   //并行同步队列
            [self concurrentSync];
            break;
        case 101:   //线程通信
            [self concurrentAsync];
            break;
        case 102:   //串行队列
            [self serialSync];
            break;
        case 103:   //主队列
            [self mainSync];
            break;
        case 104:   //全程队列
            [self globalSync];
            break;
        default:
            break;
    }
}

#pragma mark -
#pragma mark -- 并发任务 --
- (void)concurrentSync
{
    //并发队列 + 同步任务 = 不会开启新的线程,任务是逐个执行
    //并发队列 + 异步任务 = 会开启新的线程,任务是并发执行

    // 创建并发队列
    dispatch_queue_t queue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);

    // 在队列里面添加任务
    // 同步任务:dispatch_sync
    // 异步任务:dispatch_async
    dispatch_sync(queue, ^{
        for(int i=0; i<5; i++){
            NSLog(@"第一个任务:%@", [NSThread currentThread]);
        }
    });

    dispatch_sync(queue, ^{
        for(int i=0; i<5; i++){
            NSLog(@"第二个任务:%@", [NSThread currentThread]);
        }
    });
}

#pragma mark -
#pragma mark -- 串行队列 --
- (void) serialSync
{
    //串行队列 + 同步任务 = 不会开启新的线程,任务是逐个执行
    //串行队列 + 异步任务 = 会开启新的线程,任务是逐个执行

    //创建串行队列
    dispatch_queue_t queue = dispatch_queue_create("queue", NULL);

    dispatch_sync(queue, ^{
        for(int i=0; i<5; i++){
            NSLog(@"第一个任务:%@", [NSThread currentThread]);
        }
    });

    dispatch_sync(queue, ^{
        for(int i=0; i<5; i++){
            NSLog(@"第二个任务:%@", [NSThread currentThread]);
        }
    });
}


#pragma mark -
#pragma mark -- 线程通信 --
-(void)concurrentAsync
{
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //耗时操作
        NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString: IMAGE_URL]];
        UIImage *image = [UIImage imageWithData: data];

        // 回归到主线程
        // 同步:先打印“回归主线程”, 再打印“子线程”。
        // 异步:先打印“子线程”,再打印“回归主线程”;
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"回归主线程");
            self.imageView.image = image;
            [self.view addSubview: self.imageView];
        });

        NSLog(@"子线程");
    });
}

#pragma mark -
#pragma mark -- 全局队列 --
-(void)globalSync
{
    //全局队列 + 同步任务 = 不会开启新的线程,任务是逐个执行
    //全局队列 + 异步任务 = 不会开启新的线程,任务是并发执行
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    dispatch_sync(queue, ^{
        for(int i=0; i<5; i++){
            NSLog(@"第一个任务:%@", [NSThread currentThread]);
        }
    });

    dispatch_sync(queue, ^{
        for(int i=0; i<5; i++){
            NSLog(@"第二个任务:%@", [NSThread currentThread]);
        }
    });
}

#pragma mark -
#pragma mark -- 主队列 --
-(void) mainSync
{
    //全局队列 + 同步任务 = 会造成死锁的线程
    //全局队列 + 异步任务 = 不会开启新的线程,任务是逐个执行

    //获取主队列
    dispatch_queue_t queue = dispatch_get_main_queue();

    dispatch_async(queue, ^{
        for(int i=0; i<5; i++){
            NSLog(@"第一个任务:%@", [NSThread currentThread]);
        }
    });

    dispatch_async(queue, ^{
        for(int i=0; i<5; i++){
            NSLog(@"第二个任务:%@", [NSThread currentThread]);
        }
    });
}

3.4 Runloop

  • 内部实现:do-while循环实现
  • 作用:1.保证程序的持续运行;2.处理APP的各种事件(滑动、定时器);3.节省CPU资源,提高程序性能

(1)RunLoop与与线程
- 1.每条线程都有唯一的一个与之对应的RunLoop对象。
- 2.主线程的RunLoop随着程序已自动创建好,但是子线程的RunLoop需要手动创建。
- 3.获得主线程的RunLoop的方法是[NSRunLoop mainRunLoop]。
- 4.创建子线程的RunLoop的方法是[NSRunLoop currentRunLoop];(原理需在CFRunLoop中查看)
- 【注意】:苹果不允许创建RunLoop,只提供上述两种获得RunLoop方法。
- 逻辑处理
这里写图片描述

(2)CFRunLoopModeRef
- 1.CFRunLoopModeRef的代表了RunLoop的运行模式。
- 2.一个RunLoop可以包含多个Mode,每个Mode包含多个Source/Timer/Observer。
- 3.每次RunLoop启动时,只能指定其中的一个Mode,这个Mode被称为currentMode。
- 4.如果需要切换Mode,需要退出RunLoop再重新指定Mode进入(原因:分离不同组的Source/Timer/Observer,让其互不影响)。
- 状态
这里写图片描述
- 类型
这里写图片描述

(3)程序

    - (void)viewDidLoad {
    [super viewDidLoad];

    [self performSeector];
    [self timer];
    // Do any additional setup after loading the view from its nib.
}

-(void)performSeector{
    [self performSelector:@selector(run) withObject:nil afterDelay:2 inModes:@[NSRunLoopCommonModes]];
}

- (IBAction)btnAction:(UIButton *)sender {
    // 如果给runLoop 添加观察者,需要CF类

    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        NSLog(@"----%lu", activity);
    });

    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
}

- (void) timer{
    // 自动加在runLoop下,可以直接运行
    //    [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];

    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];

    // 只应用于默认模式下, TextView滚动模式下不执行
    //    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];

    // 只能运行于滚动模式下
    //    [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];

    // 滚动和不滚动都能运行
    // NSRunLoopCommonModes它只是一个标记, 模式有:UITrackingRunLoopMode、NSDefaultRunLoopMode
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}

-(void)run
{
    NSLog(@"Run");
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
状态栏20键盘高度216导航44 最少2位 补0 // UIColor *color2 = [[UIColor alloc] initWithRed:0 green:1 blue:0 alpha:1]; // button setTitle:@"点我吧" forState:UIControlStateNormal]; // [button addTarget:self action:@selector(buttonClick) forControlEvents:UIControlEventTouchUpInside]; Target目标 action行动 [button setTitleColor:[UIColor whiteColor] forState:UIControlStateSelected]; //字体在点击时候的颜色 button.selected=YES;//激活forState:UIControlStateSelected状态 [UIButton buttonWithType:UIButtonTypeRoundedRect];按钮样式 button1.tag = [str intValue]; 标记 //initWithNibName将控制器绑定xib的方法,如果xib的名称和控制器的类名称相同的时候,直接写init(会自动绑定同名的xib)就可以,如果xib的名称和类名称不同的话,必须手动调用此方法来进行绑定 ZYTwoViewController *two=[[ZYTwoViewController alloc]initWithNibName:@"Empty" bundle:nil]; //因为UIImageView的用户可交互性默认是关闭的,所以把按钮放在他身上时,按钮是不能点击的,把可交互性打开以后,按钮就能够被点击 imgView.userInteractionEnabled=YES; enabled授权给 Interaction交互 //将序列帧数组赋给UIImageView的animationImages属性 imageview.animationImages = imageArray; //设置动画时间 imageview.animationDuration = 2; Duration持续时间 //设置动画次数 0 表示无限 imageview.animationRepeatCount = 0; Repeat重复次数 //开始播放动画 [imageview startAnimating]; / //要动画的对象和他的状态 // imageView.frame = CGRectMake(200, 200, 50, 50); // //设置透明度 // imageView.alpha = 0.2; //创建一个window对象,window的frame跟屏幕大小一致 self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; Screen 屏 //让window显示到屏幕上 [self.window makeKeyAndVisible]; Visible可见的 //得到全局的屏幕对象 UIScreen *screen = [UIScreen mainScreen]; //获得屏幕的大小 CGRect rect = screen.bounds; //判断btn这个指针指向的是UIButton的对象的时候才清空 if([btn isKindOfClass:[UIButton class]]){ [btn removeFromSuperview]; } //判断这个视图是否是他的父视图有 if([_imgView isDescendantOfView:cell]){ [_imgView removeFromSuperview]; } //让键盘放弃第一响应,也就是让textfield不再处于活动状态,键盘就会下去 //resignFirstResponder 这个方法的功能就是让属于textfield的键盘下去 [_textField resignFirstResponder]; resign失去 responder响应 //成为第一响应者 [_textField becomeFirstResponder]; become 变成 //enabled 可用的,textfield 不响应事件 //_salaryField.enabled = NO; // _textField.placeholder = @"请输入您的银行卡账号"; placeholder 占位符 // _textField.keyboardType = UIKeyboardTypeNumberPad; keyboard键盘 /secure 安全 text 文本 entry 输入 //textField.secureTextEntry = YES; //点击键盘的return键绑定当前类对象的down这个方法 //此方法可以有参数,也可以没有参数,如果没有参数系统不会给你穿参数,如果有参数,只能有一个参数,无论你所指定的参数类型是什么,系统只会把tf本身给传过去 [tf addTarget:self action:@selector(down:) forControlEvents:UIControlEventEditingDidEndOnExit]; //因为tag为999的本来就是UITextField类型所以可以强制转换成UITextField类型,如果他本来就不是UITextField,非要强转语法不会报错,但运行时就会出现问题(例如披着羊皮的郎) // UITextField *tf=(UITextField *)[self.window viewWithTag:999]; //让父视图取消编辑会让其身上的所有文本框都取消相应 [self.window endEditing:YES]; //将子视图在前面 [self.window bringSubviewToFront:_taiyang]; //超出这个view的边界的控件不再显示 [_infoView setClipsToBounds:YES]; //UIView 静态方法,开始一个动画 [UIView beginAnimations:nil context:nil]; begin 开始 //animation 动画 duration 间隔时间 [UIView setAnimationDuration:1]; //从当前状态设置动画开始 [UIView setAnimationBeginsFromCurrentState:YES]; //设置动画的重复次数,0表示1次 [UIView setAnimationRepeatCount:MAXFLOAT]; //设置动画的自动反转,出去以后原路返回 [UIView setAnimationRepeatAutoreverses:YES]; //设置动画的效果,慢入慢出 [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut]; //只有设置了context,并且设置代理和动画结束后调用的方法,系统会将context传过去 [UIView beginAnimations:nil context:imgView]; //设置代理(委托) [UIView setAnimationDelegate:self]; //设置动画结束以后调用的方法,我们只需要把context设置为imgView,系统就会调用此方法给传参数 [UIView setAnimationDidStopSelector:@selector(animationDidStop:finished:context:)]; -(void)animationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context{ NSLog(@"1111"); UIImageView *imgView=(UIImageView *)context; [imgView removeFromSuperview]; } //Transition 转换,变换 Curl 卷 //1.动画的样式 //2.要在哪个视图上做动画,一般是动画视图的父视图 [UIView setAnimationTransition:UIViewAnimationTransitionCurlUp forView:_window cache:YES]; //commit 提交 [UIView commitAnimations]; //索引为0表示先添加的子视图,跟子视图的tag没有关系 //交换两个子视图的先后位置 [self.window exchangeSubviewAtIndex:0 withSubviewAtIndex:1]; exchange交换 Subview 代替 //remove 移除 from 从 superview 父视图 //把自身从父视图中移除 //[_loginControl removeFromSuperview]; //设置数组容量为0,可变数组随便设置只是个初始化的值 _diJiArr=[[NSMutableArray alloc]initWithCapacity:0]; // CGAffineTransform a = {1,2,3,4,1,1}; //CGAffineTransformMakeRotation 方法的作用就是传进去一个角度(计量单位不是度,是弧度),方法内部帮你生成一个这个角度所对应的旋转矩阵 //rotate 旋转 CGAffineTransform a = CGAffineTransformMakeRotation(-3.1415926 / 2); //在原有imageView.transform的基础上再转多少度 CGAffineTransform c = CGAffineTransformRotate(imageView.transform, 3.1415926 / 2); //scale 缩放 // CGAffineTransform b = CGAffineTransformMakeScale(2, 2); //修改uiview 的矩阵,仿射变换 imageView.transform = a; //设置阴影偏移量(正值往右偏,正值往下偏移) label.shadowOffset=CGSizeMake(5, 10); //在oc中,空对象调用方法或属性不会引起程序报错或崩溃,但是也不会有任何事件发生 // NSString *str = nil; // [str length]; //判断两个字符串是否相等,不能使用==,使用等号是判断两个对象是否是一个对象,也就是是否是一个内存地址。 //判断字符串的内容是否相同应该使用nsstring的isEqualToString:方法 //在低版本的时候,如果直接点击注册按钮,没有点击具体的输入框,得到输入框中的内容为nil,如果点击输入框,但是没有输入任何内容,这个时候点击注册按钮获得的内容为@"".这是系统懒加载的结果。 // if ([registName isEqualToString:@""] || registName == nil) // { // NSLog(@"不能为空"); // return; // } //可以这样写,把上述两种情况都涵盖 //registName.length 获得字符串的长度 if (registName.length <= 0) //获取手指在屏幕上的坐标 UITouch * touch = [touches anyObject]; CGPoint point = [touch locationInView:self.view]; NSLog(@"ppp===%@",NSStringFromCGPoint(point)); CGPoint point=[[touches anyObject] locationInView:self.window]; //判断此时手指在屏幕上的坐标是否在飞机上,也就是说手指是否按在飞机上,如果是的话,改变飞机的中心点坐标到手指的位置上 if(CGRectContainsPoint(_planeView.frame, point)){ _planeView.center=point; } UIActionSheet *action=[[UIActionSheet alloc]initWithTitle:@"提示" delegate:self cancelButtonTitle:@"红" destructiveButtonTitle:@"绿" otherButtonTitles:@"蓝", nil]; [action showInView:self.view]; //弹出框 Alert 警告,alertView是局部变量,他的作用域只在if这个大括号内 UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"警告" message:@"用户名不能为空" delegate:nil cancelButtonTitle:@"取消" otherButtonTitles:nil]; //show 展现 ,显示alertview [alertView show]; return; if (![_registPasswordField.text isEqualToString:_registConfirmPasswordField.text]) self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; self.window.backgroundColor = [UIColor whiteColor]; //frame是相对于其父视图而言的 //bounds是相对于自身而言的,bounds的xy是设置自己坐标系的左上角坐标,会影响其子视图 //一般使用bounds不设置x和y只设置宽和高 //center是相对于其父视图而言的,是CGpoint类型 _vi.bounds=CGRectMake(0, 0, 200, 200); // vi.center=CGPointMake(160, 240); _vi.center=_window.center; //arc4random()%256取一个随机值,随机值的范围为0-255 _vi.backgroundColor=[UIColor colorWithRed:arc4random()%256/255.0 green:arc4random()%256/255.0 blue:arc4random()%256/255.0 alpha:1]; 屏幕触发事件 -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event -(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event -(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event TestViewController *test=[[TestViewController alloc]init]; //实际作用就是将test的view放在window上 self.window.rootViewController=test; //initWithNibName将控制器绑定xib的方法,如果xib的名称和控制器的类名称相同的时候,直接写init(会自动绑定同名的xib)就可以,如果xib的名称和类名称不同的话,必须手动调用此方法来进行绑定 ZYTwoViewController *two=[[ZYTwoViewController alloc]initWithNibName:@"Empty" bundle:nil]; self.window.rootViewController=two; //每隔0.05秒调用当前类对象的boFang方法,不传参数,一直重复 _tim=[NSTimer scheduledTimerWithTimeInterval:.1 target:self selector:@selector(boFang) userInfo:nil repeats:YES]; } scheduled 固定时间 Interval间隔时间 repeats重复 //bool orClose; -(void)btnPress{ // orClose=!orClose; //判断定时器的指针是否存在(定时器的对象是否存在) if(_tim){ //必须在定时器失效以后将定时器的指针至为空 [_tim invalidate]; invalidate使…无效 _tim=nil; }else{ _tim=[NSTimer scheduledTimerWithTimeInterval:.1 target:self selector:@selector(boFang) userInfo:nil repeats:YES]; } /* int a=5,b=3; //三目运算符 int c=a>b?1:2; //不能深度赋值 //相当于 //imgView.frame.origin.y+=5; if(a>b){ c=1; }else{ c=2; } */ CGRectIntersectsRect(zidan.frame, diji.frame) //有交叉就怎么怎么样 //Activity 活动 Indicator指示器 // UIActivityIndicatorView *ai = [[UIActivityIndicatorView alloc] init]; // ai.activityIndicatorViewStyle = UIActivityIndicatorViewStyleWhiteLarge; // ai.backgroundColor = [UIColor blackColor]; // ai.frame = CGRectMake(100, 200, 100, 100); // ai.hidesWhenStopped = NO; // [_loginControl addSubview:ai]; // [ai startAnimating]; //使用系统的协议方法:1、.h文件实现协议2、.m中设置委托3、.m实现协议方法 //点击alert身上的按钮的时候会调用此方法,并且会传参数 //switch创建对象的时候必须要加{},否则会编译不通过 //表示要实现协议,必须实现方法(什么也不写的情况下,默认是@required) @required -(void)doSomething:(NSString *)str; //表示要实现协议,可以实现也可以不实现方法 @optional -(void)doSomething2; 类相互交叉//告诉系统Man是一个类,此时用到Man不要再报错了,但是是不知道这个类有什么属性,和什么方法的 @class Man; //又因为.m中要用到属性和方法,所以仅仅用@class不行,再在.m中导入一下#import "Man.h" #import "Man.h" // MyView*vi=[[MyView alloc]initWithFrame:CGRectMake(<#CGFloat x#>, <#CGFloat y#>, <#CGFloat width#>, <#CGFloat height#>)] NSLog(@"111111"); //系统的init方法会去调用initwithFrame方法,此时的frame是0,0,0,0,所以无法确定按钮的位置 // MyView *vi=[[MyView alloc]init]; // MyView *vi=[[MyView alloc]initWithFrame:CGRectMake(0, 0, 0, 0)]; /* 1.找到应用程序的委托对象,这个对象的window就是手机屏幕上显示的window ZYAppDelegate *app=[UIApplication sharedApplication].delegate; app.window.rootViewController=reg; 2.因为委托对象的window是应用程序的主窗口,所以我们通过应用程序的对象调用keyWindow也可以找到手机屏幕上显示的widow [UIApplication sharedApplication].keyWindow.rootViewController=reg; */ 7T5A59~4UDE99@OU)WN0T`6.jpg ¬ //延时函数,调用refreash方法,传递参数,让这个方法在0.1秒以后调用 [self performSelector:@selector(refreash:) withObject:pickerView afterDelay:.1]; //只要控制器的视图将要显示都会执行此方法,所以这个方法不止执行一次 -(void)viewWillAppear:(BOOL)animated{ [super viewWillAppear:YES]; } -(void)viewDidDisappear:(BOOL)animated{ [super viewWillAppear:YES]; } //这2个方法都是不止执行一次 pickerView //设置pickerView分为几个区,此方法有一个参数,即设置的pickView - (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView{ NSLog(@"分区%d",pickerView.tag); //返回值是设置一共多少个区的 return 2; } //设置pickerView每个区分多少行,此方法有两个参数,分别为设置的哪个pickView和要设置的是哪个区 - (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component{ NSLog(@"分行%d",pickerView.tag); if(component==0){ //返回值设置每个区的行数 return [_leftArr count]; } return [_rightArr count]; } //设置哪个pickView的哪个区的哪一行是什么标题, - (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component{ if(component==0){ //返回值设置每个行显示的内容 return [_leftArr objectAtIndex:row]; } return [_rightArr objectAtIndex:row]; } /当选中pickView某个区的某一行的时候会调用,第一个参数表示当前选择的是哪个pickView,第二个参数表示选择的是哪一行,第三个参数表示的是选择的哪一个区 - (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component{ NSString *leftStr=nil; NSString *rightStr=nil; if(component==0){ leftStr=[_leftArr objectAtIndex:row]; int rightRow=[pickerView selectedRowInComponent:1]; rightStr=[_rightArr objectAtIndex:rightRow]; }else{ rightStr=[_rightArr objectAtIndex:row]; int leftRow=[pickerView selectedRowInComponent:0]; leftStr=[_leftArr objectAtIndex:leftRow]; } NSString *message=[NSString stringWithFormat:@"左边为:%@,右边为%@",leftStr,rightStr]; [[[UIAlertView alloc] initWithTitle:@"提示" message:message delegate:nil cancelButtonTitle:@"取消" otherButtonTitles:nil] show];CocoaLigature1 - (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component{ if(component==0){ //延时函数,调用refreash方法,传递参数,让这个方法在0.1秒以后调用 [self performSelector:@selector(refreash:) withObject:pickerView afterDelay:.1]; } } -(void)refreash:(UIPickerView *)pickerView{ [pickerView reloadComponent:1]; //加载每行相对应的行里的内容 [pickerView selectRow:0 inComponent:1 animated:YES]; }CocoaLigature1 UIDatePicker //UIDatePicker是处理时间的一个控件,继承于UIControl ,UIPickView继承于UIView,两者没有直接联系 UIDatePicker *pick=[[UIDatePicker alloc]initWithFrame:CGRectMake(0, 0, 320, 240)]; pick.tag=98; //控件的4种样式 pick.datePickerMode=UIDatePickerModeDateAndTime; //[NSDate date]获取当前日期 //pick.minimumDate=[NSDate date]; //显示多久以后的日期 pick.minimumDate=[NSDate dateWithTimeInterval:5*60*60 sinceDate:[NSDate date]]; [pick addTarget:self action:@selector(getTime:) forControlEvents:UIControlEventValueChanged]; [self.view addSubview:pick]; } -(void)getTime:(UIDatePicker *)pick{ NSDate *date=[pick date]; //格式化日期类,用这个类将nsdate转换成nsstring,也可以按反向转换 NSDateFormatter *formater=[[NSDateFormatter alloc]init]; //设置上午下午标示符,默认是上午和下午 [formater setAMSymbol:@"morning"]; [formater setPMSymbol:@"evening"]; //yyyy表示年 MM表示月 dd表示日 eeee表示星期几 HH表示24时制得小时,hh表示12时制得小时,mm表示分钟,ss表示秒 a表示上午下午 [formater setDateFormat:@"yyyy年MM月dd日 eeee hh:mm:ss a"]; NSString *str=[formater stringFromDate:date]; NSLog(@"%@",str); } -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ NSString *str=@"2015年06月10日 08:53:52"; NSDateFormatter *formater=[[NSDateFormatter alloc]init]; [formater setDateFormat:@"yyyy年MM月dd日 hh:mm:ss"]; ((UIDatePicker *)[self.view viewWithTag:98]).date=[formater dateFromString:str]; ck];CocoaLigature1 UIScrollView //设置内容大小 sc.contentSize=CGSizeMake(200*8, 200); //设置成单页滑动 sc.pagingEnabled=YES; //设置内容偏移量 sc.contentOffset=CGPointMake(200, 0); //设置水平滚动条是否显示 sc.showsHorizontalScrollIndicator=NO; //设置垂直滚动条是否显示 sc.showsVerticalScrollIndicator=NO; //因为此时我们没有用手拨动,所以这个方法不会调用 - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{ NSLog(@"1111"); int num= scrollView.contentOffset.x/200; UIPageControl *page=(UIPageControl *)[self.view viewWithTag:2]; page.currentPage=num; } //只要scrollView发生偏移就会调用 - (void)scrollViewDidScroll:(UIScrollView *)scrollView{ _pageControl.currentPage=_topView.contentOffset.x/320; } // self.automaticallyAdjustsScrollViewInsets=NO; //因为当前的控制器在导航器中,又在这个控制器的view身上先放的是UIScrollView,就会导致所有放在这个UIScrollView身上的空间向下偏移 //两种解决方法:1、不先放置这个UIScrollView //2、将控制器的automaticallyAdjustsScrollViewInsets这个属性设置成NO UIPageControl //设置page有多少页 page.numberOfPages=8; //设置page的当前页,索引从0开始 page.currentPage=1; //设置点的颜色 page.pageIndicatorTintColor=[UIColor redColor]; //设置当前页的点对应的颜色 -(void)change:(UIPageControl *)page{ int num=page.currentPage; UIScrollView *sc=(UIScrollView *)[self.view viewWithTag:1]; //sc.contentOffset=CGPointMake(200*num, 0); [sc setContentOffset:CGPointMake(200*num, 0) animated:YES]; } 导航栏 //压栈,出栈 ZYOneViewController *one=[[ZYOneViewController alloc]init]; //创建一个导航器,并且指定他的根控制器 //导航栏高度44 UINavigationController *nav=[[UINavigationController alloc]initWithRootViewController:one]; self.window.rootViewController=nav;CocoaLigature1 //这是控制器的属性,对这个属性赋值,并不会影响导航器的所有返回按钮,只是代表,当这个控制器在导航器中显示的时候,导航栏上的项目 self.navigationItem.leftBarButtonItem=[[UIBarButtonItem alloc]initWithTitle:@"回去" style:UIBarButtonItemStyleBordered target:self action:@selector(goBack)]; 模态跳转 -(void)gotoNext{ ZYThreeViewController *three=[[ZYThreeViewController alloc]init]; //设置翻转动画,是枚举类型 three.modalTransitionStyle=UIModalTransitionStyleFlipHorizontal; //控制器与控制器之间跳转,模态跳转 [self presentViewController:three animated:YES completion:nil]; } - (IBAction)goBack:(UIButton *)sender { [self dismissViewControllerAnimated:YES completion:nil]; } //a模态b(a和b都不在导航其中)那么 //a的presentedViewController就是b //b的presentingViewController就是a UIWebView _web=[[UIWebView alloc]initWithFrame:CGRectMake(0, 74, 320, 430-44)]; //设置自适应大小 _web.scalesPageToFit=YES; [self.view addSubview:_web]; [_web loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:_urlArr[num-1]]]]; NSDictionary 字典 NSDictionary *dic=[NSDictionary dictionaryWithObjectsAndKeys:btn.titleLabel.font,NSFontAttributeName, nil]; float width= [_titleArr[num-1] sizeWithAttributes:dic].width; UITableView 用TableViewController创建 //注册单元格,表示此单元格可以被重复使用 [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"cell"]; tab=[[UITableView alloc]initWithFrame:self.view.bounds style:UITableViewStylePlain]; 用ViewController创建 _tab.delegate=self; _tab.dataSource=self; //设置单元格点击状态为无 cell.selectionStyle = UITableViewCellSelectionStyleNone; //设置单元格的样式为无 _tab.separatorStyle = UITableViewCellSeparatorStyleNone; //设置单元格的行高,在此设置会导致所有的行高都为80 _tab.rowHeight=80; [self.view addSubview:_tab]; //方法的返回值就是设置这个表有多少个区的,默认是1个区 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{ return 2; } //设置特定的行高,当两者都设置时,以此为准 -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{ return 40; } //设置某个区的行数,方法有两个参数,第一个参数就是设置委托的表,第二个参数就是要设置的是哪个区,此方法的返回值表示要设置这个区的行数 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{ return 5*(section+1); } //设置区头标题 -(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section{ return [NSString stringWithFormat:@"第%d区",section]; } //设置区头的高 -(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{ return 30; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ //设置一个重用标示符 static NSString *cellIndentifier=@"cell"; //每次代码执行的时候先从tableView的重用队列里面去寻找有没有可以重复使用的单元格 UITableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:cellIndentifier]; //如果没有找到可以重用的单元格,那么就去创建单元格 if(!cell){ cell=[[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIndentifier]autorelease]; //把每个单元格共有的东西放在此处 cell.detailTextLabel.text=@"你好"; cell.imageView.image=[UIImage imageNamed:@"c_item0.jpg"]; //按钮样式 cell.accessoryType=UITableViewCellAccessoryDisclosureIndicator; //设置单元格选择样式 cell.selectionStyle=UITableViewCellSelectionStyleNone; } //把每个单元格不同的东西放在此处 cell.textLabel.text=[NSString stringWithFormat:@"第%d区,第%d行",indexPath.section,indexPath.row]; return cell; } xib //获取资源目录,加载xib文件,返回值类型为数组,数组中存放的是xib中除了fileOwner 和firstResponder以外的控件 cell= [[NSBundle mainBundle] loadNibNamed:@"ZYMyTwoTableViewCell" owner:nil options:nil][0]; MyTableViewCell *cell=(MyTableViewCell *)btn.superview.superview.superview; //通过单元格找到其对应的indexPath对象 NSIndexPath *indexPath=[_tab indexPathForCell:cell]; 分线程 //开启一个线程调用当前对象的update2方法,不传参数,当update2方法执行完成后分线程就结束了 [NSThread detachNewThreadSelector:@selector(update2) toTarget:self withObject:nil]; //因为分线程中不能直接改变UI,所以要从分线程中调到主线程去修改UI //回到主线程去执行其refreshUI这个方法,yes表示先阻碍当前的分线程,等到主线程的refreshUI这个方法执行完成后,回到分线程 //NO表示不等到主线程完成后才回来 [self performSelectorOnMainThread:@selector(refreshUI) withObject:nil waitUntilDone:YES]; 有时候配合延迟函数使用 [NSThread sleepForTimeInterval:.05];

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值