NSThread

创建线程的方式

  • 准备在后台线程调用的方法 longOperation:
  1. - (void)longOperation:(id)obj {
  2. NSLog(@"%@ - %@", [NSThread currentThread], obj);
  3. }

方式1:alloc / init - start

  1. - (void)threadDemo1 {
  2. NSLog(@"before %@", [NSThread currentThread]);
  3. NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(longOperation:) object:@"THREAD"];
  4. [thread start];
  5. NSLog(@"after %@", [NSThread currentThread]);
  6. }

代码小结

  • [thread start];执行后,会在另外一个线程执行 longOperation: 方法
  • 在 OC 中,任何一个方法的代码都是从上向下顺序执行的
  • 同一个方法内的代码,都是在相同线程执行的(block除外)

方式2:detachNewThreadSelector

  1. - (void)threadDemo2 {
  2. NSLog(@"before %@", [NSThread currentThread]);
  3. [NSThread detachNewThreadSelector:@selector(longOperation:) toTarget:self withObject:@"DETACH"];
  4. NSLog(@"after %@", [NSThread currentThread]);
  5. }

代码小结

  • detachNewThreadSelector 类方法不需要启动,会自动创建线程并执行 @selector 方法

方式3:分类方法

  1. - (void)threadDemo3 {
  2. NSLog(@"before %@", [NSThread currentThread]);
  3. [self performSelectorInBackground:@selector(longOperation:) withObject:@"PERFORM"];
  4. NSLog(@"after %@", [NSThread currentThread]);
  5. }

代码小结

  • performSelectorInBackgroundNSObject 的分类方法
  • 会自动在后台线程执行 @selector 方法
  • 没有 thread 字眼,隐式创建并启动线程
  • 所有 NSObject 都可以使用此方法,在其他线程执行方法

NSThread 的 Target

  • NSThread 的实例化方法中的 target 指的是开启线程后,在线程中执行 哪一个对象@selector 方法

代码演练

  • 准备对象
  1. @interface Person : NSObject
  2. @property (nonatomic, copy) NSString *name;
  3. @end
  4. @implementation Person
  5. + (instancetype)personWithDict:(NSDictionary *)dict {
  6. id obj = [[self alloc] init];
  7. [obj setValuesForKeysWithDictionary:dict];
  8. return obj;
  9. }
  10. - (void)longOperation:(id)obj {
  11. NSLog(@"%@ - %@ - %@", [NSThread currentThread], self.name, obj);
  12. }
  13. @end
  • 定义属性
  1. @property (nonatomic, strong) Person *person;
  • 懒加载
  1. - (Person *)person {
  2. if (_person == nil) {
  3. _person = [Person personWithDict:@{@"name": @"zhangsan"}];
  4. }
  5. return _person;
  6. }

三种线程调度方法

  • alloc / init
  1. NSThread *thread = [[NSThread alloc] initWithTarget:self.person selector:@selector(longOperation:) object:@"THREAD"];
  2. [thread start];
  • detach
  1. [NSThread detachNewThreadSelector:@selector(longOperation:) toTarget:self.person withObject:@"DETACH"];
  • 分类方法
  1. [self.person performSelectorInBackground:@selector(longOperation:) withObject:@"PERFORM"];

代码小结

  • 通过指定不同的 target 会在后台线程执行该对象的 @selector 方法
  • 提示:不要看见 target 就写 self
  • performSelectorInBackground 可以让方便地在后台线程执行任意 NSObject 对象的方法

线程状态

线程状态

  • 新建

    • 实例化线程对象
  • 就绪
    • 向线程对象发送 start 消息,线程对象被加入 可调度线程池 等待 CPU 调度
    • detach 方法和 performSelectorInBackground 方法会直接实例化一个线程对象并加入 可调度线程池
  • 运行
    • CPU 负责调度可调度线程池中线程的执行
    • 线程执行完成之前,状态可能会在就绪运行之间来回切换
    • 就绪运行之间的状态变化由 CPU 负责,程序员不能干预
  • 阻塞
    • 当满足某个预定条件时,可以使用休眠或锁阻塞线程执行

      • sleepForTimeInterval:休眠指定时长
      • sleepUntilDate:休眠到指定日期
      • @synchronized(self):乎斥锁
  • 死亡
    • 正常死亡

      • 线程执行完毕
    • 非正常死亡
      • 当满足某个条件后,在线程内部中止执行
      • 当满足某个条件后,在主线程中止线程对象

代码演练

  1. - (void)statusDemo {
  2. NSLog(@"先睡会");
  3. [NSThread sleepForTimeInterval:1.0];
  4. for (int i = 0; i < 20; i++) {
  5. if (i == 9) {
  6. NSLog(@"再睡会");
  7. [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];
  8. }
  9. NSLog(@"%d %@", i, [NSThread currentThread]);
  10. if (i == 16) {
  11. NSLog(@"88");
  12. // 终止线程之前,需要记住释放资源
  13. [NSThread exit];
  14. }
  15. }
  16. NSLog(@"over");
  17. }
  1. - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
  2. // 注意不要在主线程上调用 exit 方法
  3. // [NSThread exit];
  4. // 实例化线程对象(新建)
  5. NSThread *t = [[NSThread alloc] initWithTarget:self selector:@selector(statusDemo) object:nil];
  6. // 线程就绪(被添加到可调度线程池中)
  7. [t start];
  8. }

代码小结

阻塞

  • 方法执行过程,符合某一条件时,可以利用 sleep 方法让线程进入 阻塞 状态

    • sleepForTimeInterval 从现在起睡多少
    • sleepUntilDate 从现在起睡到指定的日期

死亡

  1. [NSThread exit];
  • 一旦强行终止线程,后续的所有代码都不会被执行
  • 注意:在终止线程之前,应该注意释放之前分配的对象!

注意:线程从就绪运行状态之间的切换是由 CPU 负责的,程序员无法干预

线程属性

代码演练

  1. // MARK: - 线程属性
  2. - (void)threadProperty {
  3. NSThread *t1 = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
  4. // 1. 线程名称
  5. t1.name = @"Thread AAA";
  6. // 2. 优先级
  7. t1.threadPriority = 0;
  8. [t1 start];
  9. NSThread *t2 = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
  10. // 1. 线程名称
  11. t2.name = @"Thread BBB";
  12. // 2. 优先级
  13. t2.threadPriority = 1;
  14. [t2 start];
  15. }
  16. - (void)demo {
  17. for (int i = 0; i < 10; ++i) {
  18. // 堆栈大小
  19. NSLog(@"%@ 堆栈大小:%tuK", [NSThread currentThread], [NSThread currentThread].stackSize / 1024);
  20. }
  21. // 模拟崩溃
  22. // 判断是否是主线程
  23. // if (![NSThread currentThread].isMainThread) {
  24. // NSMutableArray *a = [NSMutableArray array];
  25. //
  26. // [a addObject:nil];
  27. // }
  28. }

属性

1. name - 线程名称

  • 在大的商业项目中,通常需要在程序崩溃时,获取程序准确执行所在的线程

2. threadPriority - 线程优先级

  • 优先级,是一个浮点数,取值范围从 0~1.0

    • 1.0表示优先级最高
    • 0.0表示优先级最低
    • 默认优先级是0.5
  • 优先级高只是保证 CPU 调度的可能性会高
  • 刀哥个人建议,在开发的时候,不要修改优先级
  • 多线程的目的:是将耗时的操作放在后台,不阻塞主线程和用户的交互!
  • 多线程开发的原则:简单

3. stackSize - 栈区大小

  • 默认情况下,无论是主线程还是子线程,栈区大小都是 512K
  • 栈区大小可以设置
  1. [NSThread currentThread].stackSize = 1024 * 1024;

4. isMainThread - 是否主线程

资源共享

资源共享-卖票

多线程开发的复杂度相对较高,在开发时可以按照以下套路编写代码:

  1. 首先确保单个线程执行正确
  2. 添加线程

卖票逻辑

  1. - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
  2. self.tickets = 20;
  3. [self saleTickets];
  4. }
  5. /// 卖票逻辑 - 每一个售票逻辑(窗口)应该把所有的票卖完
  6. - (void)saleTickets {
  7. while (YES) {
  8. if (self.tickets > 0) {
  9. self.tickets--;
  10. NSLog(@"剩余票数 %d %@", self.tickets, [NSThread currentThread]);
  11. } else {
  12. NSLog(@"没票了 %@", [NSThread currentThread]);
  13. break;
  14. }
  15. }
  16. }

添加线程

  1. - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
  2. self.tickets = 20;
  3. NSThread *t1 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil];
  4. t1.name = @"售票员 A";
  5. [t1 start];
  6. NSThread *t2 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil];
  7. t2.name = @"售票员 B";
  8. [t2 start];
  9. }

添加休眠

  1. - (void)saleTickets {
  2. while (YES) {
  3. // 模拟休眠
  4. [NSThread sleepForTimeInterval:1.0];
  5. if (self.tickets > 0) {
  6. self.tickets--;
  7. NSLog(@"剩余票数 %d %@", self.tickets, [NSThread currentThread]);
  8. } else {
  9. NSLog(@"没票了 %@", [NSThread currentThread]);
  10. break;
  11. }
  12. }
  13. }

运行测试结果

互斥锁

添加互斥锁

  1. - (void)saleTickets {
  2. while (YES) {
  3. [NSThread sleepForTimeInterval:1.0];
  4. @synchronized(self) {
  5. if (self.tickets > 0) {
  6. self.tickets--;
  7. NSLog(@"剩余票数 %d %@", self.tickets, [NSThread currentThread]);
  8. continue;
  9. }
  10. }
  11. NSLog(@"没票了 %@", [NSThread currentThread]);
  12. break;
  13. }
  14. }

互斥锁小结

  1. 保证锁内的代码,同一时间,只有一条线程能够执行!
  2. 互斥锁的锁定范围,应该尽量小,锁定范围越大,效率越差!
  3. 速记技巧 [[NSUserDefaults standardUserDefaults] synchronize];

互斥锁参数

  1. 能够加锁的任意 NSObject 对象
  2. 注意:锁对象一定要保证所有的线程都能够访问
  3. 如果代码中只有一个地方需要加锁,大多都使用 self,这样可以避免单独再创建一个锁对象

原子属性

  • 原子属性(线程安全),是针对多线程设计的,是默认属性
  • 多个线程在写入原子属性时(调用 setter 方法),能够保证同一时间只有一个线程执行写入操作
  • 原子属性是一种单(线程)写多(线程)读的多线程技术
  • 原子属性的效率比互斥锁高,不过可能会出现脏数据
  • 在定义属性时,必须显示地指定 nonatomic

代码演练

  • 定义属性
  1. @property (nonatomic, strong) NSObject *obj1;
  2. @property (atomic, strong) NSObject *obj2;
  3. @property (nonatomic, strong) NSObject *obj3;
  • 模拟原子属性
  1. @synthesize obj3 = _obj3;
  2. - (void)setObj3:(NSObject *)obj3 {
  3. @synchronized(self) {
  4. _obj3 = obj3;
  5. }
  6. }
  7. - (NSObject *)obj3 {
  8. return _obj3;
  9. }
  10. * 性能测试
  11. - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
  12. int largeNumber = 1000 * 10000;
  13. NSLog(@"非原子属性");
  14. CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
  15. for (int i = 0; i < largeNumber; i++) {
  16. self.obj1 = [[NSObject alloc] init];
  17. }
  18. NSLog(@"%f", CFAbsoluteTimeGetCurrent() - start);
  19. NSLog(@"原子属性");
  20. start = CFAbsoluteTimeGetCurrent();
  21. for (int i = 0; i < largeNumber; i++) {
  22. self.obj2 = [[NSObject alloc] init];
  23. }
  24. NSLog(@"%f", CFAbsoluteTimeGetCurrent() - start);
  25. NSLog(@"模拟原子属性");
  26. start = CFAbsoluteTimeGetCurrent();
  27. for (int i = 0; i < largeNumber; i++) {
  28. self.obj3 = [[NSObject alloc] init];
  29. }
  30. NSLog(@"%f", CFAbsoluteTimeGetCurrent() - start);
  31. }

原子属性内部的锁是自旋锁自旋锁的执行效率比互斥锁高

自旋锁 & 互斥锁

  • 共同点

    • 都能够保证同一时间,只有一条线程执行锁定范围的代码
  • 不同点

    • 互斥锁:如果发现有其他线程正在执行锁定的代码,线程会进入休眠状态,等待其他线程执行完毕,打开锁之后,线程会被唤醒
    • 自旋锁:如果发现有其他线程正在执行锁定的代码,线程会以死循环的方式,一直等待锁定代码执行完成
  • 结论

    • 自旋锁更适合执行非常短的代码
    • 无论什么锁,都是要付出代价

线程安全

  • 多个线程进行读写操作时,仍然能够得到正确结果,被称为线程安全
  • 要实现线程安全,必须要用到
  • 为了得到更佳的用户体验,UIKit 不是线程安全的

约定:所有更新 UI 的操作都必须主线程上执行!

  • 因此,主线程又被称为UI 线程

iOS 开发建议

  1. 所有属性都声明为 nonatomic
  2. 尽量避免多线程抢夺同一块资源
  3. 尽量将加锁、资源抢夺的业务逻辑交给服务器端处理,减小移动客户端的压力

线程间通讯

主线程实现

定义属性

  1. /// 根视图是滚动视图
  2. @property (nonatomic, strong) UIScrollView *scrollView;
  3. /// 图像视图
  4. @property (nonatomic, weak) UIImageView *imageView;
  5. /// 网络下载的图像
  6. @property (nonatomic, weak) UIImage *image;

loadView

  1. 加载视图层次结构
  2. 用纯代码开发应用程序时使用
  3. 功能和 Storyboard & XIB 是等价的

如果重写了 loadViewStoryboard & XIB 都无效

  1. - (void)loadView {
  2. _scrollView = [[UIScrollView alloc] init];
  3. _scrollView.backgroundColor = [UIColor orangeColor];
  4. self.view = _scrollView;
  5. UIImageView *iv = [[UIImageView alloc] init];
  6. [self.view addSubview:iv];
  7. _imageView = iv;
  8. }

viewDidLoad

  1. 视图加载完成后执行
  2. 可以做一些数据初始化的工作
  3. 如果用纯代码开发,不要在此方法中设置界面 UI
  1. - (void)viewDidLoad {
  2. [super viewDidLoad];
  3. // 下载图像
  4. [self downloadImage];
  5. }

下载网络图片

  1. - (void)downloadImage {
  2. // 1. 网络图片资源路径
  3. NSURL *url = [NSURL URLWithString:@"http://c.hiphotos.baidu.com/image/pic/item/4afbfbedab64034f42b14da1aec379310a551d1c.jpg"];
  4. // 2. 从网络资源路径实例化二进制数据(网络访问)
  5. NSData *data = [NSData dataWithContentsOfURL:url];
  6. // 3. 将二进制数据转换成图像
  7. UIImage *image = [UIImage imageWithData:data];
  8. // 4. 设置图像
  9. self.image = image;
  10. }

设置图片

  1. - (void)setImage:(UIImage *)image {
  2. // 1. 设置图像视图的图像
  3. self.imageView.image = image;
  4. // 2. 按照图像大小设置图像视图的大小
  5. [self.imageView sizeToFit];
  6. // 3. 设置滚动视图的 contentSize
  7. self.scrollView.contentSize = image.size;
  8. }

设置滚动视图的缩放

1> 设置滚动视图缩放属性

  1. // 1> 最小缩放比例
  2. self.scrollView.minimumZoomScale = 0.5;
  3. // 2> 最大缩放比例
  4. self.scrollView.maximumZoomScale = 2.0;
  5. // 3> 设置代理
  6. self.scrollView.delegate = self;

2> 实现代理方法 - 告诉滚动视图缩放哪一个视图

  1. - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
  2. return self.imageView;
  3. }

3> 跟踪 scrollView 缩放效果

  1. - (void)scrollViewDidZoom:(UIScrollView *)scrollView {
  2. NSLog(@"%@", NSStringFromCGAffineTransform(self.imageView.transform));
  3. }

线程间通讯

  • 在后台线程下载图像
  1. [self performSelectorInBackground:@selector(downloadImage) withObject:nil];
  • 在主线程设置图像
  1. [self performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:NO];

NSThread线程对象的更多相关文章

  1. iOS开发Swift篇(02) NSThread线程相关简单说明

    iOS开发Swift篇(02) NSThread线程相关简单说明 一 说明 1)关于多线程部分的理论知识和OC实现,在之前的博文中已经写明,所以这里不再说明. 2)该文仅仅简单讲解NSThread在s ...

  2. swift开发多线程篇 - NSThread 线程相关简单说明(一些使用和注意点)

    一 说明 本文涉及代码可以从https://github.com/HanGangAndHanMeimei/Code地址获得. 二 NSThread的基本使用和创建 1)基本用法(主线程|当前线程) 1 ...

  3. java-并发-线程对象

    浏览以下内容前,请点击并阅读 声明 每个线程都和类Thread的实例相关,有两种基本的使用Thread对象来创建并发应用的方法: 直接控制线程的创建和管理,每次需要开始一个异步任务使简单地实例化Thr ...

  4. java 22 - 5 多线程之获取和设置线程对象的名称

    如何获取线程对象的名称呢? public final String getName():获取线程的名称.如何设置线程对象的名称呢? public final void setName(String n ...

  5. 【翻译三】java-并发之线程对象和实现

    Thread Objects Each thread is associated with an instance of the class Thread. There are two basic s ...

  6. java多线程之:线程对象一些api

    一:wait()方法,wait(long timeout)--->锁对象调用wait()方法,让当前线程小a进入等待状态,阻塞住,并让出当先线程拥有的锁.--->直到其他线程用锁对象调用n ...

  7. JAVA之旅(十二)——Thread,run和start的特点,线程运行状态,获取线程对象和名称,多线程实例演示,使用Runnable接口

    JAVA之旅(十二)--Thread,run和start的特点,线程运行状态,获取线程对象和名称,多线程实例演示,使用Runnable接口 开始挑战一些难度了,线程和I/O方面的操作了,继续坚持 一. ...

  8. lua 源码分析之线程对象lua_State

    lua_State 中放的是 lua 虚拟机中的环境表.注册表.运行堆栈.虚拟机的上下文等数据. 从一个主线程(特指 lua 虚拟机中的线程,即 coroutine)中创建出来的新的 lua_Stat ...

  9. [Xcode 实际操作]八、网络与多线程-(21)延时启动画面:使用Thread线程对象的延时方法

    目录:[Swift]Xcode实际操作 本文将演示如何使用线程对象的延时方法,让线程休眠一段时间,暂停动作的执行. 在项目导航区,打开启动画面的故事板[LaunchScreen.storyboard] ...

随机推荐

  1. 一个基于 Beego 的,能快速创建个人博客,cms 的系统

    学习beego时候开发的一个博客系统,在持续完善,有不足之处,望大佬们多多体谅,并且指出.感谢! Go Blog 一个基于Beego的,能快速创建个人博客,cms 的系统 包含功能 查看 Go Blo ...

  2. jupyter 安装 卸载 包

    # 安装 !pip install 库名 # 卸载 !pip uninstall 库名 -y

  3. Ubuntu18.04 安装 Fabric & 使用 Fabric 测试网络

    前言: 本文介绍在 Ubuntu 18.04 中安装 Fabric, 并对 官方文档中的一个小案例(Using the Fabric test network)进行测试. 目的: 初步了解 Fabri ...

  4. Java中实现十进制数转换为二进制的三种方法

    第一种:除基倒取余法 这是最符合我们平时的数学逻辑思维的,即输入一个十进制数n,每次用n除以2,把余数记下来,再用商去除以2...依次循环,直到商为0结束,把余数倒着依次排列,就构成了转换后的二进制数 ...

  5. java引用传递还是值传递?

    首先,不要纠结于 Pass By Value 和 Pass By Reference 的字面上的意义,否则很容易陷入所谓的“一切传引用其实本质上是传值”这种并不能解决问题无意义论战中.更何况,要想知道 ...

  6. Magento add product attribute and assign to all group

    $attributes = array( 'product_type' => array( 'type' => 'int', 'input' => 'select', 'source ...

  7. NET Core Kestrel部署HTTPS 一个服务器绑一个证书 一个服务器绑多个证书

    .net core 3.0 网站发布到centos后,绑定ssl证书,一个服务器绑一个证书,一个服务器绑多个证书 开始之前:对于windows服务器不存在这个问题,在iis中绑定证书是非常简单的一件事 ...

  8. 区块链入门到实战(22)之以太坊(Ethereum) – 账号(地址)

    作用: 外部账号 – 用户使用的账号,账户余额. 合约账号 – 智能合约使用的账号,每个智能合约都有一个账号,内存和账户余额 以太坊(Ethereum)网络中,有2种账号: 外部账号 – 用户使用的账 ...

  9. Java基础的基础,花1h看这一篇就够了!

    ------------恢复内容开始------------ Java笔记 ​ 一直以来,总想着Java会点基础就可以写后端程序了,但越到后来越发现Java基础的重要性.更不必说在面试时,Java基础 ...

  10. Erlang中的宏定义应该在什么时候用

    读<Erlang OTP并发编程实战>中看到这么一句话,遂做笔记以记录: 宏不是函数的替代品,当你所需的抽象无法用普通函数来实现时,宏给出了一条生路,比如,必须确保在编译期展开某些代码的时 ...