免费开源的iOS开发学习平台

线程(Thread)基础知识

现代的操作系统,包括iOS,都支持线程Thread的概念,每个进程可以包含多个线程,借助多个CPU的内核,可以同时处理多个任务。即便是在早期单核CPU的时代,操作系统也可以把CPU资源在多个线程之间进行切换,给用户的感觉是多个任务在“同时”处理。

进程与线程

每个应用程序都有一个进程,例如在Mac系统中,可以通过活动监视器来查看当前Mac电脑正在运行的进程。每个进程可以包含多个线程,这些线程可以同时运行,并各自处理一些任务。早期的CPU只有一个核心,因此会交替处理各个线程中的任务,让用户产生一种多任务同时处理的“感觉”。当CPU拥有多个内核时,各个线程就可以分配到不同的CPU内核中进行处理,即为真正意义上的多任务同时处理。

对于任意一个iOS应用,程序运行起来后,默认会产生一个主线程(MainThread),主线程专门用来处理UIKit对象的操作,如界面的显示与更新、处理用户交互事件等等。(请牢牢记忆这点,所有与UI相关的操作都要在主线程中进行)

对于一个App应用来说,之所以需要引入多个线程,很大程度上是由于存在一些操作是非常耗时的,例如:发送网络请求并等待服务器的响应,这种耗时操作是不能够放在主线程中进行操作的,因为在等待的时间内,主线程被使用,用户是不能做任何交互动作的,因而会极大影响用户体验。对于耗时的操作,需要再另外创建一个线程,放到后台处理,当处理完成得到结果后,再返回主线程去设置UI界面,这就涉及到线程间通信的相关知识。

多线程同时处理任务,还会涉及到线程安全(thread-safe)的问题,当多个线程对一个对象进行同时操作时,就会影响结果的正确性。因此,线程对某个对象操作时,需要使用到“锁”的机制,即当一个线程操作一个对象的过程中,会给这个对象上锁,其他线程就不能够访问该对象了。加锁虽然解决了线程安全的问题,但带来的另外一个弊端就是影响了程序运行的效率。

当我们给一个自定义类中添加属性的时候,属性关键字其中就有:atomic和nonatomic的区分,其中:atomic是线程安全的,当有线程访问这个属性时,会为该属性的setter方法加锁,atomic是默认值。但是,我们在实际的开发中,都会把给属性设置nonatomic关键字,因为对于移动设备来说,效率更加重要,但也需要程序员注意线程安全问题。

NSThread类

NSThred类是苹果提供的管理线程的类,提供了一些线程管理的方法。但是随着GCD和NSOperation的推出,NSThread的使用场景已经大大减少。在实际的开发中,偶尔会使用NSThread类来获取一些线程信息。常用的一些方法如下:

  • 获取当前线程对象
+ (NSThread *)currentThread; 
  • 获取主线程对象
+ (NSThread *)mainThread;
  • 使主线程休眠ti秒
+ (void)sleepForTimeInterval:(NSTimeInterval)ti; 

示例代码

下方的示例代码中,点击【显示内容】按钮,会对3个Label上显示的文字进行设置,每个label上显示的文字,都是从一个方法中获取。但在获取文字的方法实现中,都会对主线程进行阻塞3秒,在阻塞的过程中,用户是无法进行任何操作的,除了点击Home键退出应用。

  • 实现获取字符串的方法,在每个方法中都会阻塞主线程3秒;
-(NSString *) generateString1{
    //阻塞主进程
    [NSThread sleepForTimeInterval:3.0];
    NSString *string = @"hello world!";
    return string;
}
-(NSString *) generateString2{
    //阻塞主进程
    [NSThread sleepForTimeInterval:3.0];
    NSString *string = @"www.99iOS.com!";
    return string;
}
-(NSString *) generateString3{
    //阻塞主进程
    [NSThread sleepForTimeInterval:3.0];
    NSString *string = @"2017!";
    return string;
}
  • 实现【显示内容】按钮的点击操作。
- (IBAction)showContent:(id)sender {
    //记录开始时间
    NSLog(@"\n Start Time:%@, \n Current Thread: %@, \n Main Thread: %@", [NSDate date], [NSThread currentThread], [NSThread mainThread]);
    self.label1.text = [self generateString1];
    self.label2.text = [self generateString2];
    self.label3.text = [self generateString3];
    //记录结束时间
    NSLog(@"\n End Time:%@, \n Current Thread: %@, \n Main Thread: %@", [NSDate date], [NSThread currentThread], [NSThread mainThread]);
 }

运行结果如下。我们可以看到程序执行的时间总共约有9秒钟,并且都是在主线程中执行(线程ID = 1),在运行期间,按钮是不能够被点击的。

示例代码

https://github.com/99ios/11.1