线程问题我会分成三篇文章来给大家做个详细的讲解
一、线程的概念
1.线程是进程的组成部分,一个进程可以拥有多个线程,一个线程必须有一个父线程;
2.线程拥有自己的堆栈、自己的程序计数器和自己的局部变量,但不再拥有系统资源,它与父线程中的其他线程共享该进程所拥有的全部资源
3.线程是独立运行的,它并不知道进程中是否还有其他线程存在,线程的执行是抢占式的,也就是说当前的运行的线程在任何时候都可能被挂起,以便另外一个线程可以运行。
使用多线程编程的优点
1、进程间不能共享内存,但线程之间共享内存非常容易
2、系统创建进程需要重新为该进程分配资源,但是创建线程则代价小得多,因为它与父线程中的其他线程共享该进程所拥有的全部资源
3、处理异步任务的主要手段,可防止UI界面假死
线程的创建方法:
1)创建线程并执行线程
+ (void)detachNewThreadSelector: toTarget: withObject:
2)创建一个线程并返回一个线程对象但是不会执行,需要手动调用start
- (instancetype)initWithTarget: selector: object:
二、线程的状态
1、当程序创建了一个线程后,该程序就处于新建状态,此时它与其他OC对象一样,仅仅由系统为其分配了内存,并初始化了其成员变量的值,此时的线程对象没有表现任何线程的动态特征,程序也不会执行线程的线程执行体
2、当线程对象调用了start方法之后,该线程处于就绪状态,系统会为其调用方法调用栈和程序计数器,处于这种状态的线程并没有开始运行,他只是表示该线程可以运行了,至于线程何时开始运行,取决于系统的调度;倘若希望调用子线程的start方法之后子线程立即开始执行,可以在程序中加上
[NSThread sleepForTimeInterval:0.001]; //写在哪个线程中代表哪个线程
让当前运行的线程睡眠一毫秒,因为在这一毫秒内CPU不会空闲,他会去执行另一个处于就绪状态的线程,这就可以让子线程立即获得执行
三、终止子线程
线程会以以下三种方式之一结束,结束之后就处于死亡状态
1、线程执行体方法执行完成,线程正常结束
2、线程执行过程中出现了错误
3、直接调用NSThread类的exit方法来终止当前正在执行的线程
对象方法
isExecuting、isFinishing判断线程当前是否处于执行状态或者执行完成状态
如果希望在主线程中终止子线程,NSThread并没有提供方法来终止某个子线程,为了在子线程中终止子线程,可以向子线程发送一个信号(比如调用子线程的cancel方法),然后在子线程的线程执行体重进行判断,如果子线程收到过终止信号,程序应该调用NSThread类的exit方法来终止当前正在执行的循环
四、线程睡眠
如果需要让当前正在执行的线程暂停一段时间,并进入阻塞状态,则可以通过调用NSThread类的sleepXXX类方法来完成
+ (void)sleepForTimeInterval:(NSTimeInterval)ti; //每隔多久执行一次
+ (void)sleepUntilDate:(NSDate *)date; //指定睡眠指导某个时间执行
1 - (void)viewDidLoad { 2 [super viewDidLoad]; 3 // NSThread 两种创建线程的方式 4 #if 1 5 //1.该方法创建的线程需要手动调用启动,才会去执行线程指定的方法 6 // 线程需要一个函数作为线程的入口函数,这个函数称为线程的入口函数 7 // 线程主要用来做并发操作,例如执行耗时的代码 8 NSThread *tread = [[NSThread alloc] initWithTarget:self selector:@selector(doSomething) object:nil]; 9 for (int i = 0; i < 100; i++) { 10 if (i == 20) { 11 //需要手动执行开始,才会调用doSomething的方法 12 [tread start]; 13 [NSThread sleepForTimeInterval:0.001]; 14 } 15 NSLog(@"%d",i); 16 } 17 //给线程取别名 18 //tread.name = @"something"; 19 20 #elif 0 21 //2.该方式创建的线程会自动执行线程的入口函数 22 [NSThread detachNewThreadSelector:@selector(doSomething) toTarget:self withObject:nil]; 23 #endif 24 } 25 -(void)doSomething 26 { 27 // 耗时代码 28 29 // 获取当前线程 30 //NSLog(@"%@",[NSThread currentThread]); 31 int index = 10; 32 33 while (index--) { 34 35 if (index == 5) { 36 //1⃣️cancel 取消线程,并不是真正意义上的取消线程,是给线程打上取消的标志,等待取消 37 [[NSThread currentThread] cancel]; 38 } 39 40 //2⃣️.判断线程是否有取消的标志 41 if ([[NSThread currentThread] isCancelled]) { 42 //3⃣️.退出线程,线程销毁,系统会自动回收资源 43 //[NSThread exit]; 44 //注意return与退出线程的区别 45 //return; 46 } 47 48 NSLog(@"%d",index); 49 //两种睡眠方式 50 //1.睡眠 51 //[NSThread sleepForTimeInterval:1]; 52 //2.指定睡眠知道某个时间执行 53 //dateByAddingTimeInterval 追加的时间 54 // NSDate *curDate = [[NSDate date] dateByAddingTimeInterval:1]; 55 // [NSThread sleepUntilDate:curDate]; 56 } 57 }
【注意】iOS规定只能在UI线程(即主线程)中修改UI控件的属性,因为如果程序允许任意子线程访问、修改UI控件的属性,这就需要对多个新线程的并发访问进行同步控制;否则,多个线程将会破坏UI控件内部状态的完整性
实例:下载网络图片(如果程序在UI线程中访问网络数据,由于网络速度的不确定性,当网络传输速度比较慢时,UI线程就会被阻塞,从而导致应用失去响应,因此程序将通过网络下载图片的操作放在多线程中完成)
1 #import "ViewController.h" 2 3 @interface ViewController () 4 @property (weak, nonatomic) IBOutlet UIImageView *imageV; 5 6 @end 7 8 @implementation ViewController 9 10 - (void)viewDidLoad { 11 [super viewDidLoad]; 12 13 } 14 - (IBAction)BtnAction:(id)sender { 15 //图片地址 16 NSString *url = @"http://image.baidu.com/search/down?tn=download&ipn=dwnl&word=download&ie=utf8&fr=result&url=http%3A%2F%2Fh.hiphotos.baidu.com%2Fzhidao%2Fwh%253D450%252C600%2Fsign%3D3dc4538262d0f703e6e79dd83dca7d0b%2F7a899e510fb30f24f570e996c895d143ac4b03b8.jpg&thumburl=http%3A%2F%2Fimg4.imgtn.bdimg.com%2Fit%2Fu%3D2019970444%2C20888940%26fm%3D21%26gp%3D0.jpg"; 17 //创建线程 18 NSThread *downloadImage = [[NSThread alloc] initWithTarget:self selector:@selector(downLoadImage:) object:url]; 19 //启动线程 20 [downloadImage start]; 21 22 23 } 24 -(void)downLoadImage:(NSString *)url 25 { 26 NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:url]]; 27 UIImage *image = [UIImage imageWithData:data]; 28 //返回主线程刷新UI 29 30 [self performSelectorOnMainThread:@selector(reloadUI:) withObject:image waitUntilDone:NO]; 31 } 32 -(void)reloadUI:(UIImage *)image 33 { 34 _imageV.image = image; 35 } 36 @end