iOS多线程及其感悟
感觉每天都是匆匆忙忙的,每天似乎都是时间不够用一样,可是等真的想要动手敲代码的时候才发现,原来还有好多好多的知识点不是太熟练,所以,人不可以一直感觉自我良好, 有时间就是那种自我感觉良好的心态毁了自己的光明前途!
今天我们真真正正的结束了iOS课程,但是这并不意味着结束,而是说有了新的开始,进入了新的阶段,所以努力与奋斗是丝毫不能减少的,我们将要面对的是更加严峻的考验,朋友们一起加油哦!
下面我来为大笨蛋简单介绍下我们今天所讲的知识点吧。
程序:代码编译过后, 形成的可执行文件(例如:*.app)
进程:获取一个程序运行所需要的资源(例如:内存)
线程:最小的执行单位,分配和调度资源
注:一个进程至少有一个线程(主线程)
那么什么为多线程呢顾名思义就是说效率高咱举一个例子, 就假如说程序是一个餐馆, 餐馆需要的资源(比如房子, 桌子等)由进程来获取, 线程相当于服务员, 是真正的执行者, 来分配资源(安排客户做那张餐桌,吃什么饭)等,多以多线程更加注重的应该是提高效率吧(个人理解哈)。
下面我们来举一个例子吧:
现在屏幕上放一个图片(UIImageview)和一个UIButton
self.view.backgroundColor = [UIColor yellowColor];
self.iamgeView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"taiji"]];
_iamgeView.center = self.view.center;
[self.view addSubview:_iamgeView];
[_iamgeView release];
[NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(begin) userInfo:nil repeats:YES];
UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
button.frame = CGRectMake(0, 0, 100, 40);
[button setTitle:@"开始搬砖" forState:UIControlStateNormal];
[button addTarget:self action:@selector(moveBrick) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
其中NSTimer关联的一个方法begin是让他开始旋转,还有上面所提到的UIimageView是个全局变量,因为在下面的时候还要用到,下面附上NSTimer和button所关联的方法
- (void)begin
{
[UIView beginAnimations:@"开始旋转" context:NULL];
//2D旋转
// _iamgeView.transform = CGAffineTransformRotate(self.iamgeView.transform, M_PI_4);
//3D旋转
self.iamgeView.layer.transform = CATransform3DRotate(self.iamgeView.layer.transform, -M_PI_4, 0, 0, 1);
[UIView commitAnimations];
}
- (void)moveBrick
{
//造成主线程阻塞
for (int i = 1; i < 10000; i++) {
NSString *string = [NSString stringWithFormat:@"终于搬了%d快砖", i];
NSLog(@"%@", string);
}
}
其中上面提到的begin方法一个是在二维平面上进行运动, 而另外一个则是在三维中运动,其中3D旋转中(-M_PI_4, 0, 0, 1) 参数的涵义分别为旋转角度,是否在x、y、z轴上旋转,其中如果想改变方向的话,只需要在旋转角度上面添加负号就OK了。但是这个有一种弊端,他会造成主线程阻塞,也就是说在执行搬砖的时候,主线程(图片的运动)会停止,直到搬砖结束为止。那么大家肯定会问,怎么才能实现同步运动呢,好,下面我就为大家讲解下一种做法,这种做法便是多线程方式,如有不理解可随时问我,代码中已有注释下面为大家插上代码
self.iamgeView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"taiji"]];
_iamgeView.center = self.view.center;
[self.view addSubview:_iamgeView];
[_iamgeView release];
UIButton *proButton = [UIButton buttonWithType:UIButtonTypeSystem]; proButton.frame = CGRectMake(150, 500, 100, 40); [proButton setTitle:@"高级搬砖" forState:UIControlStateNormal]; [proButton addTarget:self action:@selector(moveBrickWithThread) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:proButton];
关联方法如下:
- (void)moveBrickWithThread { //多线程方式 //1.NSObject 后台执行 //自动帮你创建一个线程(子线程), 在后台执行代码 // [self performSelectorInBackground:@selector(moveBrickPro) withObject:nil]; //2.NSThread, 系统提供的线程类 //创建的子线程 // NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(moveBrickPro) object:nil]; // thread.name = @"码农1"; // [thread start]; // [thread release]; // [NSThread mainThread]; // NSLog(@"主线程:%@", [NSThread mainThread]); // [NSThread currentThread]; // NSLog(@"当前线程:%@", [NSThread currentThread]); //3.NSOperationQueue, 操作队列, 自动创建一个线程, 操作队列中存放的是NSOperation(操作单元) //栈:先进后出 //队列:先进先出 //NSOperation, 操作单元, 只能执行一次, 是抽象类 NSInvocationOperation *invocation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(moveBrickPro) object:nil]; //执行操作(只能够执行一次, 在主线程中执行) // [invocation start]; // [invocation release]; __block RootViewController *blockSelf = self; NSBlockOperation *block = [NSBlockOperation blockOperationWithBlock:^{ [blockSelf moveBrickPro]; }]; //创建操作队列, 队列自带一个子线程 NSOperationQueue *quera = [[NSOperationQueue alloc] init]; //设置队列的最大并发数, 默认为-1, 表示并发数为无穷大 quera.maxConcurrentOperationCount = 1; //往队列里添加操作单元 [quera addOperation:invocation]; [quera addOperation:block]; [quera release]; [invocation release]; [NSThread mainThread]; NSLog(@"主对列:%@", [NSOperationQueue mainQueue]); [NSThread currentThread]; NSLog(@"当前队列:%@", [NSOperationQueue currentQueue]); //把操作单元添加到 主队列 中, 操作单元在 主线程 中执行 //把操作单元添加到 子队列 中, 草错单元在 子线程 中执行 }
- (void)moveBrickPro { //主线程和子线程的区别 //1.主线程占用内存:1M, 子线程占用内存:512k //2.子线程不会释放内存, 需要手动加自动释放池 //3.主线程中NSRunLoop自动开启, 子线程的NSRunLoop默认是关闭的, 不能够接受事件, 需要手动打开 //4.子线程中更新UI有可能会失败, 需要在主线程中更新UI @autoreleasepool { for (int i = 1; i < 1000; i++) { NSString *string = [NSString stringWithFormat:@"终于搬了%d快砖", i]; NSLog(@"%@", string); } //NSRunLoop, 每一个线程都有一个NSRunLoop, 来监控和接受事件 //开启子线程的事件循环 [[NSRunLoop currentRunLoop] run]; [self performSelectorOnMainThread:@selector(refreshUI) withObject:nil waitUntilDone:YES]; } [NSThread mainThread]; NSLog(@"主线程:%@", [NSThread mainThread]); [NSThread currentThread]; NSLog(@"当前线程:%@", [NSThread currentThread]); }
⭐️⭐️⭐️接下来就是今天的最后一个知识点了那便是多线程 的终极版了 这次我们来通过一个现实生活中的例子来让大家对多线程的了解更加深刻吧,想必大家都知道车站售票吧,如果只有一个窗口,那车站还不等爆了,所以这就类似于我们的多线程,引入多个子线程来进行管理,首先前两部还是一样的在跟试图控制器上添加一个按钮UIButton和图片然后让button关联方法,具体方法如下(重中之重)
//开始卖票 - (void)startSale { currentTicket = 100; saleCount = 0; //创建售票窗口 NSThread *windowOne = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil]; windowOne.name = @"A01"; NSThread *windowTwo = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil]; windowTwo.name = @"B02"; //开始售票 [windowOne start]; [windowOne release]; [windowTwo start]; [windowTwo release]; } - (void)saleTicket { //多线程可以提高执行效果, 相反的带来的是, 开销大喝资源的抢夺 while (YES) { [NSThread sleepForTimeInterval:0.2]; //加锁 // [_lock lock]; if (currentTicket > 0) { currentTicket--; saleCount++; NSLog(@"窗口:%@, 卖了第%d张 剩余%d张", [[NSThread currentThread] name], saleCount, currentTicket); } else { NSLog(@"票已售空"); break; } // [_lock unlock]; } }
大家看到了我注释掉得两句话了把[_lock lock],[_lock unlock],首先既然是售票,总得知道总共有多少张票,和已售多少吧,所以呢,在我们先开始时间我们先要初始化两个全局变量,并赋值,这里我赋的总票数为100,已售为0。这样就算是基本完成了, 但是千万不要大意,刚才那两句话如果不加上的画,执行顺序就会出错,下面给大家看个例子:
这就是部分效果, 大家会发现完全发生了紊乱,更甚至票都为负数了还在继续卖,所以那两句话一定要加上,当一个窗口开始卖票时将此票短暂占为己有,等售出后再打开,也就是说不让他们互相抢夺资源,下面的是加上那两句话后的效果。
注:但是如果要加上那两句话的话,就必须要初始化个NSLock并且一定要记得释放,但是他的释放和其他的不一样,要格外的谨慎,稍不留神,程序就会崩溃,所以应该写到dealloc里面最为安全。
通过对比大家肯定会发现这个明显很有顺序,没有你争我夺的那种架势,所以朋友们,那两句话一定要加上哦,保护资源。
今天下课时间我还还对GCD做了一个小小的了解,等明天了我再好好钻研下再给友友们分享哈,今天就先到这里了,下周就要开始新的项目了,好紧张有木有,不过还是安奈不住心中那小小的激动啊,加油,come on!