1.网络多线程基础
1.1 学习多线程的目的
学习多线程最主要的目的是将耗时的操作放在后台处理,保证UI界面的正常显示与交互.
提示 : 网络操作是非常非常耗时的.在做网络开发时,所有网络访问都是耗时操作.需要在后台线程中执行.
多线程开发的原则是 : 越简单越好
1.2 模拟耗时操作
空的for循环不耗时
操作内存的栈区速度很快;栈区存储空间地址是连续的;
操作内存的常量区速度很快;内存空间只开辟一次;
操作内存的堆区速度相对栈区和常量区要慢些;堆区内存空间不连续,需要寻址;
I/O操作是很耗时的; (把数据从内存输出到外接设备,或者由外接设备输入到内存)
- 耗时操作对UI交互的影响 : 卡死了主屏幕,直到耗时操作执行完,屏幕的交互才能正常进行;
- 解决耗时操作卡顿UI的办法 : 多线程技术;
- 学习多线程的目的 : 把耗时操作放在后台执行,不让耗时操作卡顿UI;
1.3.多线程基本概念
1.3.1 同步和异步
同步和异步是任务 / 代码 执行的两种方式
-
同步
- 多个任务按顺序依次执行,就是同步执行
-
异步
- 多个任务同时执行,就是异步执行
- 如何保证多个任务同时执行?
- 开线程,开多个线程,就可以保证多个任务同时执行
- 提示 : 凡是遇到异步 / 多线程 / 耗时操作 第一反应就是需要开启新的子线程
- 学习多线程就是为了如何让任务在子线程异步执行
1.3.2 进程和线程
-
进程
- 系统中
正在运行
的应用程序叫做进程 - 进程可以类必成公司
- 系统中
线程 / 多线程
程序一启动就会默认开启一个线程,称之为主线程
线程是进程最基本的执行单元,进程里面所有的任务都在线程中执行的
一个进程,可以开启多个线程,称之为多线程
1.3.3 多线程执行原理
- CPU在多个线程之间,快速来回的切换,调度线程执行任务,如果切换的速度足够快,就造成多个任务
同时
执行的假象
1.3.4 多线程优缺点
-
优点
- 可以
适当
提高程序执行的效率 (开启多个线程下载视频) - 能
适当
提高CPU和内存的利用率. - 线程上的任务执行完成后,线程会自动销毁,节省内存.
- 可以
-
缺点
- 前提 : 当线程非常多的时候,就暴露缺点
- 会消耗大量的CPU资源
- 时间开销 / 空间开销
- 多线程的使用原则 : 能不用就不用,如果非要用,就简单的使用,少用
1.3.5 主线程
- 作用 : 刷新UI / 处理UI事件
- 处理UI事件 : 点击、滚动、拖拽等事件
- 使用时的注意点 :
- 不要把耗时操作放到主线程中执行
- 耗时操作会卡住主线程,严重影响UI的流畅度,给用户一种卡的坏体验,影响UI交互质量.
1.4多线程的实现方案
POSIX 表示可移植操作系统接口(Portable Operating System Interface ) —— pthread
2. pthread
- 学习pthread的目的 : 就是为了复习C语言相关的知识点
- 在C语言中,一般带
_t
/_ref
标识数据类型 - NULL : 表示空地址,一般在C语言使用;
- nil : 表示空对象,一般在OC使用;
- 其实,NULLh和nil本质上没有半点儿区别
-
void *(*)(void *)
: 表示指向函数的指针,即函数名;函数名就是表示函数地址; - 数组地址就是数组名或者数组第0个角标元素的地址
-
void *
表示可以指向任何地址的指针,代表任意数据类型;类似于OC的id;
返回值 函数名 函数参数
void * (*) (void *)
2.1 桥接
- 使用场景 : 在C语言和OC语言混合开发时,需要做数据类型转换,有时候需要使用桥接;
- 桥接作用 : 在做数据类型转换时,告诉编译器如何管理C语言的内存
- 提示 : 在ARC环境下,编译器在编译时,不会自动管理C语言申请的内存空间
- 提示: 在ARC环境下,加上__bridge 表示告诉编译器C语言申请的内存也是自动管理的,因为大环境是ARC的
- 提问 : MRC环境下,需要使用__bridge 吗? 不需要,因为本来就是手动管理的
3.NSThread
3.1 NSThread创建线程三种方式
3.1.1 构造方法
- 可以拿到线程对象
- 需要自己启动线程
// 创建线程对象
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo:) object:@"alloc"];
// 启动线程
[thread start];
3.1.2 类方法
- 不可以拿到线程对象
- 不需要自己启动线程
[NSThread detachNewThreadSelector:@selector(demo:) toTarget:self withObject:@"detach"];
3.1.3 NSObject分类方法
- 不可以拿到线程对象
- 不需要自己启动线程
[self performSelectorInBackground:@selector(demo:) withObject:@"perform"];
3.2 target和selector的关系
- 执行哪个对象的哪个方法
- 需求 : 执行Person对象的run方法,run方法需要在子线程执行
// 创建线程对象
NSThread *thread = [[NSThread alloc] initWithTarget:_p selector:@selector(run:) object:@"person"];
// 启动线程
[thread start];
3.3 线程生命周期 / 线程状态
- 新建状态
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
- 就绪状态
[thread start];
- 运行状态 : 程序员无法干预
- 阻塞状态 : 调用sleep方法 / 添加互斥锁(同步锁)
- 死亡状态
- 正常死亡 : 任务执行结束
- 异常死亡 : exit
3.4 线程属性
- name : 标识唯一的线程对象,方便定位线程对象
- threadPriority : 决定了线程有更多的机会被CPU调度执行;等同于qualityOfService;实际开发中千万不要随意修改
- stackSize : 线程对象占用内存空间大小.主线程 / 子线程 512KB
3.5 多线程访问共享资源 (会造成线程安全问题)
- 当多个线程同时操作共享资源,就会出现线程安全问题
- 解决办法 : 加锁 (互斥锁 / 同步锁)
- 互斥锁 / 同步锁 : 使用了线程同步技术
- 特点 : 可以保证被锁定的代码,同一时间只有一个线程可以访问
- self : 表示互斥锁的参数;互斥锁的参数,又叫做锁对象;
- 锁对象 : 任何继承自NSObject的对象,都可以作为互斥锁的参数;内部有把锁,默认是开启的
- 锁对象必须是全局的对象;self是最方便获取的全局的锁对象
- 局部锁对象是锁不住的,因为每次线程进来之前会新建一把锁
- 提示 : 加锁的事情,不是再客户端操作的;是服务器加锁的,多线程资源共享绝大多数是在服务器发生;
- 提示 : 加锁是牺牲了性能,保证安全.客户端的性能不能轻易牺牲
3.6 异步下载网络图片
- 在 iOS 开发中,使用多线程只有一个目的:将耗时操作放在后台工作,待工作完成后,通知主线程更新 UI
- 在视图加载前设置根视图为scrollView
- (void)loadView {
//将根视图换为scrollview
self.scrollView = [[UIScrollView alloc] initWithFrame:[UIScreen mainScreen].bounds];
self.view = self.scrollView;
self.scrollView.backgroundColor = [UIColor blueColor];
//添加图片容器
UIImageView *imageView = [[UIImageView alloc] init];
[self.view addSubview: imageView];
self.imageView = imageView;
}
- 耗时的下载操作放在子线程
- (void)viewDidLoad {
[super viewDidLoad];
// [self loadImageData];
// 在子线程执行耗时操作
[self performSelectorInBackground:@selector(loadImageData) withObject:nil];
}
// 下载图片的主方法
- (void)loadImageData
{
// URL
NSURL *URL = [NSURL URLWithString:@"http://pic.sc.chinaz.com/files/pic/pic9/201508/apic14052.jpg"];
// 发送网络请求,获取图片二进制数据,是个耗时操作
NSData *data = [NSData dataWithContentsOfURL:URL];
// image : 就是子线程执行的结果,需要传递到主线程
UIImage *image = [UIImage imageWithData:data];
// 下载完成之后,通知主线程刷新UI
// waitUntilDone : 是否等待updateUI执行完,再执行后面的代码,一般传入NO
[self performSelectorOnMainThread:@selector(updateUI:) withObject:image waitUntilDone:NO];
NSLog(@"后面的代码");
}
- 更新UI的操作在主线程
// 回到主线程更新UI
- (void)updateUI:(UIImage *)image
{
self.imgView.image = image;
[self.imgView sizeToFit];
self.scrollView.contentSize = image.size;
}
- 在子线程下载图片,在主线程更新UI,是线程间通信的一种;
- 线程间通信 : 一个线程把他执行的结果,传递到另外的一个线程