UI进阶 多线程

时间:2023-03-08 17:48:34
UI进阶 多线程

一、多线程概述

  • 程序、进程、线程
    • 程序:由源代码生成的可执行应用。(例如:QQ.app)
    • 进程:一个正在运行的程序可以看做一个进程。(例如:正在运行的QQ就是一个进程),进程拥有独立运行所需的全部资源。
    • 线程:程序中独立运行的代码段。(例如:接收QQ消息的代码)

  一个进程是由一或多个线程组成。进程只负责资源的调度和分配,线程才是程序真正的执行单元,负责代码的执行。

  • 单线程
    • 每个正在运行的程序(即进程),至少包含一个线程,这个线程叫主线程。
    • 主线程在程序启动时被创建,用于执行main函数。
    • 只有一个主线程的程序,称作单线程程序。
    • 在单线程程序中,主线程负责执行程序的所有代码(UI展现以及刷新,网络请求,本地存储等等)。这些代码只能顺序执行,无法并发执行。
  • 多线程
    • 拥有多个线程的程序,称作多线程程序。
    • iOS允许用户自己开辟新的线程,相对于主线程来讲,这些线程,称作子线程。
    • 可以根据需要开辟若干子线程
    • 子线程和主线程 都是 独立的运行单元,各自的执行互不影响,因此能够并发执行。
  • 单线程、多线程区别
    • 单线程程序:只有一个线程,即主线程,代码顺序执行,容易出现代码阻塞(页面假死)。
    • 多线程程序:有多个线程,线程间独立运行,能有效的避免代码阻塞,并且提高程序的运行性能。
    • 注意:iOS中关于UI的添加和刷新必须在主线程中操作。

二、iOS平台下的多线程

  • iOS多线程实现种类
    • NSThread
    • NSOperationQueue
    • NSObject
    • GCD

三、NSThread

  • NSThread是一个轻量级的多线程,它有以下两种创建方法
    方法 功能
    - (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument 初始化一个子线程,但需要手动开启
    + (void)detachNewThreadSelector:(SEL)aSelector toTarget:(id)aTarget withObject:(id)anArgument 初始化一个子线程并自动开启
    -start 开启子线程
    -cancel 取消当前子线程,不是真正的取消,而是给子线程发送了一个“取消”消息,标记为canceled
    +exit

    直接将线程退出

    • 第一种:手动开辟一个子线程
       - (void)viewDidLoad {
      #pragma mark - NSThread手动开辟子线程
      // 参数1: target
      // 参数2: 方法
      // 参数3: 要传的参数
      NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(sayHello:) object:@"说你好!"];
      #pragma mark - 启动子线程
      [thread start]; // 判断一个线程是否正在执行
      NSLog(@"是否正在执行%d",[thread isExecuting]);
      // 判断一个线程是否完成了任务(是否执行完毕)
      NSLog(@"是否执行完毕%d", [thread isFinished]); #pragma mark - 释放子线程的两种方式
      // 第一种:取消线程,不是真正的取消,而是给子线程发送了一个“取消”消息,标记为canceled
      // 判断线城是否被标记为canceled
      // NSLog(@"%d", [thread isCancelled]);
      // [thread cancel];
      // // 第二种:直接将线程退出
      // [NSThread exit]; } - (void)sayHello:(NSString *)hello {
      NSLog(@"%@", hello);
      // 打印当前线程
      NSLog(@"当前线程:%@", [NSThread currentThread]);
      NSLog(@"主线程:%@", [NSThread mainThread]);
      }

      Thread手动开辟一个子线程

    开辟子线程成功

    UI进阶 多线程

    • 第二种:

       - (void)viewDidLoad {
      #pragma mark - NSThread自动开辟子线程
      // 线程自动开启,不需要创建NSThread的对象
      // 与手动开启线程的方法相比,target 和 selector 两个参数顺序颠倒
      [NSThread detachNewThreadSelector:@selector(sayHello:) toTarget:self withObject:@"说你好!"];
      } - (void)sayHello:(NSString *)hello {
      NSLog(@"%@", hello);
      // 打印当前线程
      NSLog(@"当前线程:%@", [NSThread currentThread]);
      NSLog(@"主线程:%@", [NSThread mainThread]);
      }

      Thread自动开辟子线程

  • 开辟子线程成功
    UI进阶 多线程
  • 其他方法
    UI进阶 多线程
  • 注意:
    • 每个线程都维护着与自己对应的NSAutoreleasePool对象,将其放在线程栈的栈顶。当线程结束时,会清空自动释放池。
    • 为保证对象的及时释放,在多线程方法中需要添加自动释放池。
    • 在应用程序打开的时候,系统会自动为主线程创建一个自动释放池。
    • 我们手动创建的子线程需要我们手动添加自动释放池。

四、NSObject实现异步后台执行

UI进阶 多线程

 - (void)viewDidLoad {
#pragma mark - NSObject开辟子线程
/**
* 开启线程的方式之二:NSObject
*/
// 使用performSelectorInBackground开辟子线程
// 参数1:方法
// 参数2:要传的参数
[self performSelectorInBackground:@selector(sayHello:) withObject:@"说你好!"];
self.view.backgroundColor = [UIColor yellowColor]; } - (void)sayHello:(NSString *)hello {
NSLog(@"%@", hello);
// 打印当前线程
NSLog(@"当前线程:%@", [NSThread currentThread]);
NSLog(@"主线程:%@", [NSThread mainThread]); // 回到主线程修改view的背景颜色
// 参数1:方法
// 参数2:要传的参数
// 参数3:是否等待子线程完成后再进入主线程
[self performSelectorOnMainThread:@selector(changeColor) withObject:nil waitUntilDone:NO];
} - (void)changeColor {
self.view.backgroundColor = [UIColor cyanColor];
// 打印当前线程
NSLog(@"==当前线程:%@", [NSThread currentThread]);
NSLog(@"==主线程:%@", [NSThread mainThread]);
}

NSObject异步后台执行,开辟子线程

开辟子线程成功

UI进阶 多线程

UI进阶 多线程

NSThread和NSObject结合使用:同步请求延迟加载图片

 @interface ViewController ()
/// 声明一个ImageView用来显示图片
@property (nonatomic, strong) UIImageView *showImageView;
/// 声明一个NSData类型的数据用于接收图片的数据
@property (nonatomic, strong) NSData *imageData;
@end @implementation ViewController - (void)viewDidLoad {
[super viewDidLoad];
// 创建imageView
_showImageView = [[UIImageView alloc] initWithFrame:CGRectMake(, , , )];
_showImageView.backgroundColor = [UIColor greenColor];
[self.view addSubview:_showImageView]; } #pragma mark - 触发子线程去加载数据
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self performSelectorInBackground:@selector(loadImageData) withObject:nil];
} // 加载图片数据
- (void)loadImageData {
NSLog(@"当前线程:%@", [NSThread currentThread]);
NSLog(@"主线程:%@", [NSThread mainThread]);
NSURL *url = [NSURL URLWithString:@"http://img4q.duitang.com/uploads/item/201506/13/20150613185209_nHy5E.jpeg"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
self.imageData = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
// 创建主线程用于刷新UI
[self performSelectorOnMainThread:@selector(renovateUI) withObject:nil waitUntilDone:YES]; } // 主线程刷新UI
- (void)renovateUI {
// 安全判断
if ([NSThread mainThread]) {
_showImageView.image = [UIImage imageWithData:self.imageData];
}
}
@end

同步请求实现延迟加载图片

点击触发事件,开辟子线程成功,在主线程中刷新UI,图片显示成功

UI进阶 多线程

UI进阶 多线程

五、NSOperation和NSOperationQueue

  • NSOperation
    • NSOperation类,在MVC中属于M,是用来封装单个任务相关的代码和数据的抽象类
    • 因为它是抽象的,不能够直接使用这个类,而是使用子类( NSInvocationOperation或NSBlockOperation )来执行实际任务。
    • NSOperation(含子类),只是一个操作,本身无主线程、子线程之分,可在任意线程中使用。通常与NSOperationQueue结合使用。
  • NSInvocationOperation
    • NSInvocationOperation是NSOperation的子类
    • 封装了执行操作的target和要执行的action。
 - (void)viewDidLoad {
/**
* NSOperation不能直接进行多线程的创建,需要借助:NSOperationQueue。
NSOperation的子类本身和多线程没有任何关系,它只是封装了一定的代码段和数据去实现一个功能。
在单独使用NSOperation的子类去创建线程的时候,实际上线程没有真正被创建。
*/
// 使用NSOperation的第一个子类去创建子线程 -- NSInvocationOperation
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(sayHello:) object:@"