/*---------------------------------------卡住主线程------------------------------------------*/
重点:1.线程进程区别(面试)!2.串行执行!
{
1.问题演示 :
为什么在执行打印输出(执行耗时代码)的时候, UITextView 不能滚动?按钮不能点击?
因为在同一条线程中,代码按顺序执行!所以在执行打印输出(执行耗时代码)的时候,卡住了主线程!
如何解决这个问题? NSThread类开启新线程!
//开启一条新的线程: NSThread
//1.创建一条线程;
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(longTimeOperation)object:nil];
//2.启动线程;调用start方法,告诉CPU线程准备就绪;线程被 CPU 调度之后会自动执行@selector()中的方法;
[thread start];
2.线程
应用程序中的代码是由线程来执行的!
一个进程至少包含一条线程!
在一个应用程序启动之后,会默认开启一条线程 ---->主线程!
主线程之外的线程---->子线程
问题:线程是如何执行应用程序中的代码的?
串行执行:
在线程中的代码是按顺序执行的!同一时间内,只能有一个代码块执行!
3.进程
就是在系统中'正在运行'的应用程序!
进程为应用程序开辟独立的内存空间;
//这块内存空间是独立的,受保护的!进程和进程之间是互不干扰的!
}
/*------------------------------------- 多线程实现原理 ---------------------------------------*/
重点:并发执行! 并行执行:多核CPU同时执行来.
{
1.问题又来了!为什么开启一条新线程之后就能解决卡住主线程这个问题了呢?
答:因为线程和线程之间是并发执行(同时执行)!
2.多线程
进程是由许多条线程组成的!
一个进程可以包含很多条线程,每条线程都可以执行不同的代码!
并发执行(同时执行):
线程和线程之间是同时执行的! ---->提高程序的运行效率!
为什么多条线程之间可以并发(同时)执行呢?
线程是由 CPU来执行的,同一时间只能有一条线程被执行!CPU在多条线程之间快速的切换!
由于 CPU的执行速度非常快!就给我们造成了多条线程并发执行的'假象' ------- 多线程实现原理!
既然多线程这么爽,线程是不是越多越好呢?
<1>开启线程需要消耗一定的内存(默认情况下,线程占用512KB的栈区空间);
<2>会使应用程序增加很多代码!代码变多之后,程序复杂性就会提高!
<3> CPU在多条线程之间来回切换!线程越多, CPU就越累!
建议:在移动应用的开发中;一般只开3~5条线程!
}
/*---------------------------------------UI线程 -------------------------------------------*/
重点:用户体验(面试)!
{
在 iOS开发中,多线程开发的知识点://只有知道了这些知识点,大家才能进行多线程的开发!
1.主线程又称为 UI线程!主线程的作用://有关UI操作,建议都放在主线程中执行!
<1>更新UI/刷新UI界面;
<2>处理 UI事件(点击/拖拽/滚动等)
2.耗时操作会卡住主线程!,影响 UI操作的流畅度!给用户一种'卡顿'的坏体验!
注意点:别将耗时操作放在主线程中执行!
}
/*----------------------------iOS中多线程实现方案 1.pthread------------------------------------*/
重要知识点:
在 C语言中的void *就等同于 OC中的id;
C语言数据类型一般以 _t/Ref结尾
{
添加 pthread.h
#import<pthread.h>
看一遍代码!有时间看,没时间就别看!
}
/*--------------------------------------桥接 (__bridge)------------------------------------*/
重点:为什么要使用桥接?你是怎么进行混合开发的?
{
桥接 (__bridge) :C和 OC之间传递数据的时候需要使用桥接! why?为什么呢?
1.内存管理:
在 OC中,如果是在 ARC环境下开发,编译器在编译的时候会根据代码结构,自动为 OC 代码添加 retain/release/autorelease等. ----->自动内存管理(ARC)的原理!
但是, ARC只负责 OC部分的内存管理!不会负责 C语言部分代码的内存管理!
也就是说!即使是在 ARC的开发环境中!如果使用的 C语言代码出现了 retain/copy/new/create等字样呢!我们都需要手动为其添加 release操作!否则会出现内存泄露!
在混合开发时(C和 OC代码混合),C和 OC之间传递数据需要使用__bridge桥接,目的就是为了告诉编译器如何管理内存
在 MRC中不需要使用桥接!因为都需要手动进行内存管理!
2.数据类型转换:
Foundation和 Core Foundation框架的数据类型可以互相转换的
Foundation : OC
Core Foundation : C语言
NSString *str =@"123";// Foundation
CFStringRef str2 = (__bridgeCFStringRef)str;// Core Foundation
NSString *str3 = (__bridge NSString*)str2;
CFArrayRef ---- NSArray
CFDictionaryRef ---- NSDictionary
CFNumberRef ---- NSNumber
Core Foundation中手动创建的数据类型,都需要手动释放
CGPathRef path = CGPathCreateMutable();
CGPathRetain(path);
CGPathRelease(path);
CGPathRelease(path);
3.桥接的添加:
利用 Xcode提示自动添加! --简单/方便/快速
/**
凡是函数名中带有create\copy\new\retain等字眼,都应该在不需要使用这个数据的时候进行release
GCD的数据类型在ARC环境下不需要再做release
CF(Core Foundation)的数据类型在ARC\MRC环境下都需要再做release
*/
}
/*------------------------- iOS中多线程实现方案2.NSThread - 1基本使用 ---------------------------*/
重点:1.三种创建线程!2.常用方法!
{
1.NSThread:一个 NSThread就代表一个线程对象!
//OC语言 /使用面向对象 /需要手动管理线程生命周期(创建/销毁等)
2.三种多线程实现方案:
1>先创建,后启动
//创建
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(download:)object:nil];
//启动
[thread start];
2>创建完自动启动
[NSThread detachNewThreadSelector:@selector(download:)toTarget:selfwithObject:nil];
3>隐式创建(自动启动)
[self performSelectorInBackground:@selector(download:)withObject:nil];
3.常用方法:
名字/获得主线程/获得当前线程/阻塞线程/退出线程
//不常用:栈区大小/优先级
1>获得当前线程
+ (NSThread *)currentThread;
2>获得主线程
+ (NSThread *)mainThread;
3>睡眠(暂停)线程
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
4>设置线程的名字
- (void)setName:(NSString *)n;
- (NSString *)name;
}
/*------------------------- iOS中多线程实现方案2.NSThread - 2线程状态 ---------------------------*/
重点:1."Crash,P0级别 Bug(面试)!"2.理解线程状态!
{
//创建线程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
//启动线程
[thread start];
线程池:存放线程的池子!分为:
可调度线程池: CPU只会调度可调度线程池中的线程!下面蓝色状态都位于可调度线程池中!'就绪' ,'运行'!
不可调度线程池:下面红色状态都位于不可调度线程池中!"新建" ,"阻塞" ,"死亡"!
线程状态:
start CPU调度当前线程 运行结束/强制退出(exit)
"新建" ---------->'就绪' ----------------->'运行' ----------------------->"死亡";
CPU调度其他线程 CPU调度当前线程
'运行' ------------------>'就绪'----------------->'运行'
调用sleep/等待互斥锁 sleep时间到/得到互斥锁
'运行' ----------------------->"阻塞"----------------------->'就绪';
线程运行结束或者强制退出(exit)就进入"死亡"状态;
"注意:一旦线程停止(死亡),就不可以再次开启任务!程序会挂掉: Crash!
面试语句:平时开发中,要特别关注Crash! :"PO"级别的"Bug";
}
/*------------------------- iOS中多线程实现方案2.NSThread - 3资源共享 ---------------------------*/
重点:1.线程同步技术!2.理解资源共享
{
当多条线程访问同一块资源的时候,就会出现数据错乱和数据安全的问题!
1.ATM机取钱;卖票;
2.解决方案:互斥锁@synchronized(锁对象self){ /*需要锁住的代码,越少越好!*/ } ------- 厕所加锁!
注意:锁定一份代码只用一把锁,用多把锁是无效的!
优点:能有效防止因多线程抢夺资源而引起的数据安全问题!
缺点:需要消耗大量的CPU资源!
结论:尽量少加锁!互斥锁的使用前提是多条线程抢夺同一块资源!
3.添加互斥锁技巧: [[NSUserDefaultsstandardUserDefaults] synchronize];
4.线程同步技术: -----互斥锁使用了线程同步技术!
多条线程在同一条线上按顺序执行任务!
5.线程安全:保证多条线程进行读写操作,都能够得到正确的结果!
用'锁'来实现线程安全!
}
/*---------------------------------原子属性和非原子属性---------------------------------------*/
重点:1.面试问题:为什么要在主线程更新UI?2.原子和非原子属性选择!
{
1.原子属性和非原子属性:
OC在定义属性时有 atomic和 nonatomic两种选择!
atomic(默认属性):原子属性,自动为setter方法加锁!线程安全的,需要消耗大量的 CPU 资源!
nonatomic:非原子属性,不会为 setter方法加锁!非线程安全的,适合内存小的移动设备!
我们在声明属性的时候该如何选择?
面试问题:为什么要在主线程更新UI?
因为UIKit框架都不是线程安全的!为了得到更好的用户体验,UIKit框架牺牲了线程安全;
所以我们要在主线程更新UI;
2.iOS开发建议:
<1>所有属性都声明为 nonatomic!
<2>尽量避免多线程抢夺同一块资源!
<3>尽量将加锁,资源抢夺等业务逻辑交给服务器端处理,减小移动客户端的压力!
}
/*------------------------- iOS中多线程实现方案2.NSThread - 4线程间通信 -------------------------*/
1.下载图片?更新 UI?
{
1.后台线程(子线程)下载图片;
[self performSelectorInBackground:@selector(downloadImage)withObject:nil];
2.主线程更新 UI.
线程间通信常用方法:
//最后一个参数:是否等待调用方法执行结束!
<1>[self performSelectorOnMainThread:@selector(setImageWithImage:)withObject:nil waitUntilDone:YES];
<2>[self performSelector:@selector(setImageWithImage:) onThread:[NSThread mainThread]withObject:nil waitUntilDone:YES];
}
/*-------------------------------------------知识点补充 --------------------------------------*/
1.项目支持ARC
{
-fobjc-arc :就可以让旧项目支持arc
-fno-objc-arc :让原来支持arc的不使用arc
零碎知识点:
1.C语言中void * = id,所以用NSLog的时候可以用%@
2.[NSThread currentThread]方法打印出来的number只要不是1,就是子线程
3.C语言一般都是以Ref结尾或者_t结尾
4.NSThead的一些常用方法
//创建
NSThread *thread = [[NSTreadalloc] initWIthTarget:self selector:@selsecor(..) object:@"Helloworld"];
//起名字
thread.name = @"HMIOS8";
//设置优先级0~1
thread.threadPriority= 0.5;
//线程在栈区大小(1M)
thread.stackSzie= 1024*1024
[NSThread currentThread]获得当前线程
[threadstart];
//阻塞,休眠3s
[NSThreadsleepForTimeInterval:3];
//同上,另一种方法,效果完全一样
NSDate*date = [NSDated dateWithTimeIntervalSinceNow:3];
[NSThreadsleepUntilDate:date];
//线程退出(强制退出),退出之后线程就会死亡,如果再次执行线程,程序会崩溃
[NSThreadexit];
//获得主线程
[NSTreadmainThread];
5.可调度线程池(子线程)
新建:创建线程后的状态
就绪:当新建状态的子线程调用start方法后,就被放入了可调度线程池,即随时可被CPU调用,处于就绪状态
运行:CPU调度当前线程
死亡: 线程中任务执行完毕或exit、意外、强制退出,将线程从可调度线程池中移除,并且销毁
阻塞:调用sleep方法时,此子线程将会从可调度线程中移除,但是并不销毁,当sleep时间到或者枷锁状态结束,就会又放入可调度线程池并处于就绪状态。
所以,可调度线程池中线程的状态只有就绪和运行两种
6.互斥锁:需要一个所对象(一把锁),锁对象需要一个唯一的锁,锁对象需要具有唯一性,否则是没有加锁效果的。加锁(互斥锁)能够保证线程按顺序执行,但是非常耗费性能,锁住的代码块越多,越耗费性能,尽量锁定小的范围
@synchronized(self){}放入大括号,self为锁对象,只要是具有惟一性的对象就行,如self.view也行
7.优先级反转:现在情况:三个线程优先级高中低,高和低访问同一块加锁,那线程池里应该一直有两个线程,那么总有时候会高的执行完锁内容然后被放在线程池外处于阻塞状态,此时低优先级进入线程池执行锁内容,但是中线程一直在线程池中,且优先级比低线程优先级高,所以一直执行中线程,而不执行低线程,这样低线程一直无法执行完锁内容无法脱出,高线程就一直阻塞状态进不去线程池了。。
8.测试出来的bug是按照严重程度分等级的,P0级别的BUG是到时崩溃的最严重的,需要优先解决的