常用操作
多行注释: Command + option + /
获取当前的时间:CACurrentMediaTime()
后台运行程序:[self performSelectorInBackground:@selector(longOperation) withObject:nil];
创建子线程,让longOperation
方法在子线程异步执行
target-select:
多线程基础
- 空的for循环不耗时
- 操作栈区内存空间不耗时,因为栈区的内存空间是连续的,不需要寻址
- 操作常量区内存空间不耗时,但是比操作栈区空间耗时一些,寻址只做一次
- 操作堆区的内存空间相对于栈区和常量区是耗时的,因为堆区的内存空间不是连续的,需要寻址
- I/O操作也是非常耗时的
- 耗时操作对UI影响:会卡死UI
- 如何解决耗时操作卡死UI的问题:使用多线程技术
- 多线程的核心思想: 就是把耗时操作放在后台执行,避免耗时操作卡死UI
- 在实际开发中,网络操作时非常耗时的,一般会把网络操作(下载,上传...)放在后台执行
- 学习多线程就是为了学习网络做准备的
I/O操作:是把内存的数据输出到外接设备(屏幕,磁盘)output,把外接设备的数据输入到内存input
同步&异步
同步
和 异步
是任务执行的两种方式
同步:多个任务按序依次执行
异步:多个任务同时执行,就是异步执行(后台执行就是异步执行)
进程&线程
进程:
在系统中 正在运行
的一个应用程序就是一个进程
通过 活动监视器
可以查看MAC系统中 正在运行 的所有应用程序
每个进程之间都是 独立
的,均运行在其 专用
且 受保护
的内存空间
两个进程之间是无法通信的,迅雷无法帮助我们下载正在播放的音乐
进程可以类比成正在 正常运行 的公司
线程:
线程可以类比成公司中的员工
进程要想执行任务,必须要有线程,且每个进程至少有一个线程
线程是进程的 基本执行单元
,进程中的所有任务都在线程中执行
程序启动(进程开启)会默认开启一条线程
1个进程中可以有多个线程
多线程:
一个进程中可以开启多条线程,多条线程可以同时执行不同的任务
进程-公司,线程-员工
多线程可以解决程序阻塞的问题
多线程可以提高程序的执行效率,给用户良好的使用体验
多线程执行原理:
单核CPU同一时间,CPU只能处理1个线程,只有1个线程在执行任务
多线程的同时执行:其实就是CPU在多线程之间快速切换(调度任务)
如果CPU调度线程的速度足够快,就造成了多线程同时 执行 的假象
如果线程非常多,CPU会在多线线程之间不断的调度任务,结果就是消耗了大量的CPU资源,CPU会趴下
每个线程调度的频率会降低
线程的执行效率会下降
多线程的优缺点
实际开发中,能不用多线程就不用,主线程够用
如果必须使用,就简单使用
优点
- 能够适当提高程序的执行效率
- 能适当提高CPU和内存的利用率
- 线程上的任务执行完成后,线程会自动销毁,节省内存
缺点
- 开启线程需要占用一定的内存空间,如果开启的线程过多,会占用大量的CPU资源,降低程序的性能
2.占用内存空间:默认情况下,子线程512K,主线程1M,PS:IOS8中,主线程521K - 线程越多,CPU调度线程的开销就越大
时间开销
空间开销 - 程序设计更加复杂:比如多线程之间的通信,多线程的数据共享
主线程:
- 程序一起动就会创建主线程,主线程会执行main函数,
- 一个程序运行后,默认会开启1个线程,称为
主线程
或UI
线程 - 主线程一般用来刷新UI界面,处理UI事件
- 处理UI事件:点击 滚动 拖拽
- 主线程使用注意:
别将耗时的操作放在主线程中
耗时操作会卡主主线程,严重影响UI的流畅度,给用户一种卡的坏体验,影响UI交互质量
凡是跟UI相关的都是在主线程执行的(子线程创建就有,不创建就没有)
pthread
pthread创建线程
/*
创建一个线程
参数1:子线程的ID(标识)
在C语言中,一般带`_t`/`_ref`标识数据类型
参数2:子线程的一个属性,一般传入NULL
NULL:表示空地址,一般在C语言中使用
nil:表示空对象,一般在OC中使用
其实,NULL和nil本质上没有半点区别
参数3:子线程需要执行的函数
void *(*)(void *)表示指向函数的指针,即函数名:函数名就是表示函数地址;数组地址就是数组名或者数组第0个角标元素的地址
void*:表示可以指向任何地址的指针,代表任意数据类型,类似于OC的id
void * (*) (void *):
返回值 函数名 函数参数
参数4:子线程需要执行的函数的参数
返回值:int;在很多C语言框架中,不是遵守非零即真,因为成功的结果只有一个,0是唯一的;失败的原因有很多
线程调试:查看方法执行的线程是否是主线程或者子线程
{number = 1, name = main}:表示主线程
{number = 3, name = (null)}:表示子线程,主要number != 1,就表示子线程
提示:千万不要纠结number到底等于几,系统分配的
__bridge:在c语言和oc语言混合开发时,需要做数据类型转换,有时候需要使用桥接
桥接作用:在做数据类型转换时,告诉编译器如何管理c语言的内存
提示:在ARC环境下,编译器在编译时,不会自动管理C语言申请的内存空间
提示:加上__bridge 表示告诉编译器,c语言申请的内存也是自动管理的,因为大环境是ARC的
MRC环境下,不需要使用__bridge,因为手动管理内存
*/
NSLog(@"%@",[NSThread currentThread]);
//参数1
pthread_t ID;
//参数4
NSString *str = @"hello";
//创建了一个子线程
int result = pthread_create(&ID, NULL, demo, (__bridge void *)(str));
//判断子线程创建是否成功
if(result == 0){
NSLog(@"创建子线程成功");
}else{
NSLog(@"创建子线程失败");
}
/**
子线程执行的函数
*/
void *demo(void *param){
NSString *str = (__bridge NSString *)(param);
//currentThread:查看当前线程
NSLog(@"demo %@ - %@",[NSThread currentThread],str);
return NULL;
}
NSThread
创建线程的3种方式
//分类方法创建子线程
//不可以拿到线程对象
//不需要手动启动线程
-(void)threadDemo3{
//方便所有继承自NSObject的对象,可以直接调用线程的方法(swift里面没有这个分类,也没有GCD)
[self performSelectorInBackground:@selector(demo:) withObject:@"perform"];
}
//构造方法创建子线程
//不可以拿到线程对象
//不需要手动启动线程
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self threadDemo2];
}
-(void)threadDemo2{
[NSThread detachNewThreadSelector:@selector(demo:) toTarget:self withObject:@"detach"];
}
//构造方法创建子线程
//可以拿到线程对象
//需要手动启动线程
-(void)threadDemo1{
//创建线程对象
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo:) object:@"alloc"];
//启动线程
[thread start];
}
-(void)demo:(id)param{
//子线程执行的方法
NSLog(@"%@-%@",param,[NSThread currentThread]);
}
target和selector的关系
- 执行哪个对象的哪个方法
- 需求:执行Person对象的run方法
NSThread *thread = [[NSThread alloc] initWithTarget:_p selector:@selector(run:) object:@"alloc"];
线程生命周期/线程状态
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSLog(@"喜大普奔,神舟十一");
//注意:千万不要在主线中,使用这个方法,会使主线程死亡
//[NSThread exit];
[self threadDemo];
}
-(void)threadDemo{
//提示:程序员只能够做新建和就绪,其他的都由系统来处理
//创建线程对象:新建状态
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
//启动线程:就绪状态(把线程对象添加到可调度线程池,等待被CPU调度执行)
[thread start];
}
-(void)demo{
//NSLog(@"%@",[NSThread currentThread]);
for (NSInteger i = 0; i < 5; i++) {
//线程每次执行到这里就休眠1秒钟:for循环,每循环一次就休息1秒钟
//sleepForTimeInterval:使当前的线程休眠到指定时长
//sleepForTimeInterval:使用场景就是模拟网络延迟操作,仅仅是模拟,开发中不会使用的
[NSThread sleepForTimeInterval:1.0];
NSLog(@"%zd - %@",i,[NSThread currentThread]);
if(i == 2){
//sleepUntilDate:使当前线程休眠到指定日期
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.0]];
NSLog(@"蓝瘦,香菇");
}
if(i == 3){
//使当前线程强制死亡
[NSThread exit];
}
}
}
线程属性
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSLog(@"主:%tu",[NSThread currentThread].stackSize/1024);
//新建线程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
//设置线程对象的name属性,标识一个唯一的线程对象,方便跟踪
thread.name = @"t1";
//设置线程对象的优先级:浮点数0.0-1.0,最高是1.0,默认是0.5
//线程优先级不能决定线程执行的先后顺序,只能决定某个线程有更多机会被CPU先调度执行完,概率事件
//注意:实际开发中,千万不要设置优先级,或者服务器质量,会出现意想不到的问题;使用默认的,让系统自己来处理
//thread.threadPriority = 1.0;
//threadPriority:在目前即将被废弃,使用qualityOfService替代
thread.qualityOfService = NSQualityOfServiceUserInteractive;
//就绪状态
[thread start];
//新建线程
NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
thread2.name = @"t2";
thread2.threadPriority = 0.1;
//就绪状态
[thread2 start];
}
-(void)demo{
//stackSize:线程占用内存空间大小
NSLog(@"子:%tu",[NSThread currentThread].stackSize/1024);
for (NSInteger i = 0; i < 5; i++) {
NSLog(@"%zd -- %@",i,[NSThread currentThread]);
}
/*模拟崩溃:演示name属性
NSMutableArray *arrM = [NSMutableArray array];
NSObject *obj = nil;
[arrM addObject:obj];
*/
}
资源共享-线程安全
共享资源
资源:一个全局对象,一个全局变量,一个文件
共享:可以被多个对象访问
共享资源:可以被多个对象访问的资源,比如全局对象,变量,文件
在多线程的环境下,共享的资源,可能被多个线程共享,也就是多个线程可能会操作同一块资源
@interface ViewController ()
//总票数:共享资源
@property (assign,nonatomic) NSInteger tickets;
@end
//需求驱动开发:没有需求,无从开发
//需求:开发买票系统
//先要有开发逻辑,后有开发代码
//开发逻辑:先干什么,后干什么
//分析需求,得到发的逻辑
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//初始化总票数
self.tickets = 20;
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
//[self sellTickers];
//售票口1
NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickers) object:nil];
thread1.name = @"t1";
[thread1 start];
//售票口1
NSThread * thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickers) object:nil];
thread2.name = @"t2";
[thread2 start];
}
//卖票主方法
-(void)sellTickers{
while (YES) {
//互斥锁/同步锁:使用了线程同步技术
//特点:可以保证被锁定的代码,同一时间只有一个线程可以访问
//self:表示互斥锁的参数,互斥锁的参数,又叫做锁对象
//锁对象:任何继承自NSObject的对象,都可以作为互斥锁的参数,内部有把锁,默认是开启的
//锁对象必须是全局的对象;self是最方便获取的全局的锁对象
//局部锁对象是锁不住的,因为每次线程进来之前会新建一把锁
//提示:加锁的事情,不能再客户端操作;是服务器加锁的,多线程资源共享,绝大多数是在服务器发生;
//提示:加锁是牺牲了性能,保证了安全,客户端的性能不能轻易牺牲
//创建一个局部的锁对象
NSObject *obj = [[NSObject alloc] init];
@synchronized (self) {
//判断是否有余票
if(self.tickets > 0 ){
//模拟网络延迟:没有实际意义,仅仅是模拟延迟而已,可以忽略
//[NSThread sleepForTimeInterval:1.0];
//如果有余票,卖一张
self.tickets = self.tickets - 1;
//提醒余票
NSLog(@"%zd--%@",self.tickets,[NSThread currentThread]);
}else{
//如果没有余票,提醒用户无票
NSLog(@"无票了");
break;
}
}
}
}
原子属性
@interface ViewController ()
//非原子属性
@property (nonatomic,strong) NSObject *obj1;
//原子属性
@property (strong) NSObject *obj2;//一旦重写了getter和setter方法,系统不会自动生成带下划线的成员变量
//需要自己合成带下划线的成员变量
@end
/*
原子属性:单写多读
单写多读:同一时间只有一个线程可以访问setter方法,但是可以有多个线程访问getter方法
注意:原子属性的setter方法是线程安全的,getter方法是线程非安全的
setter方法内部有自旋锁
自旋锁:看不见的,由系统封装的
可以保证被锁定的代码,同一时间只能有一个线程可以访问
一旦外面的线程,发现代码被自旋锁锁定,外面的线程会以死循环的方式等待开锁
互斥锁:
可以保证锁定的代码,同一时间只能有一个线程可以访问
一旦外面的线程,发现代码被互斥锁锁定,外面的线程就会进入就绪状态,
*/
@implementation ViewController
@synthesize obj2 = _obj2;
-(void)setObj2:(NSObject *)obj2{
//由于自旋锁看不见,所以可以使用互斥锁替换,演示单写多读
@synchronized (self) {
_obj2 = obj2;
}
}
-(NSObject *)obj2{
return _obj2;
}
异步下载网络图片
@interface ViewController ()
//根视图
@property (strong,nonatomic) UIScrollView *scrollView;
//图片子视图
@property (nonatomic,weak) UIImageView *imgView;
@end
//需求:异步下载网络图片,图片可以滚动,滚动视图要是根视图
//分析需求:下载是耗时的操作,需要在子线程异步执行
//准备控件的工作UIImageView/UIScrollView(根视图)
//
@implementation ViewController
/*
loadView:优先于viewDidLoad
loadView:当self.view == nil 时调用
loadView:不需要调用super
*/
-(void)loadView{
//创建根视图
self.scrollView = [[UIScrollView alloc] initWithFrame:[UIScreen mainScreen].bounds];
//把根视图替换成scrollView
self.view = self.scrollView;
self.scrollView.backgroundColor = [UIColor redColor];
//创建图片子视图
UIImageView *imgView = [[UIImageView alloc] init];
[self.view addSubview:imgView];
//给属性赋值:一定不能少
self.imgView = imgView;
}
- (void)viewDidLoad {
[super viewDidLoad];
//[self laodImageData];
[self performSelectorInBackground:@selector(loadImageData) withObject:nil];
}
//在子线程下载图片,在主线程更新UI,是线程间通信的一种
//线程间通信:一个线程把他执行的结果,传递到另外的一个线程
//下载图片的主方法
-(void)loadImageData{
//URL
NSURL *url = [NSURL URLWithString:@"http://img05.tooopen.com/images/20150202/sy_80219211654.jpg"];
//发送网络请求,获取图片二进制数据,是个耗时的操作
NSData *data = [NSData dataWithContentsOfURL:url];
//image就是子线程执行的结果,需要传递到主线程
UIImage *image = [UIImage imageWithData:data];
//下载完成后,通知主线程刷新UI
//waitUntilDone:是否等到updateUI执行完,再执行后面的代码,一般传入NO
[self performSelectorOnMainThread:@selector(updataUI:) withObject:image waitUntilDone:NO];
NSLog(@"后面的代码");
}
-(void)updataUI:(UIImage *)image{
//下载完成后,刷新UI
self.imgView.image = image;
[self.imgView sizeToFit];
self.scrollView.contentSize = image.size;
}
异步下载
info.plist中添加代码,允许请求网络
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>