NSThread

创建线程的方式

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

方式1:alloc / init - start

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

代码小结

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

方式2:detachNewThreadSelector

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

代码小结

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

方式3:分类方法

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

代码小结

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

NSThread 的 Target

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

代码演练

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

三种线程调度方法

  • alloc / init
NSThread *thread = [[NSThread alloc] initWithTarget:self.person selector:@selector(longOperation:) object:@"THREAD"];

[thread start];
  • detach
[NSThread detachNewThreadSelector:@selector(longOperation:) toTarget:self.person withObject:@"DETACH"];
  • 分类方法
[self.person performSelectorInBackground:@selector(longOperation:) withObject:@"PERFORM"];

代码小结

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

线程状态

线程状态

  • 新建

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

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

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

代码演练

- (void)statusDemo {

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

代码小结

阻塞

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

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

死亡

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

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

线程属性

代码演练

// MARK: - 线程属性
- (void)threadProperty {
NSThread *t1 = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil]; // 1. 线程名称
t1.name = @"Thread AAA";
// 2. 优先级
t1.threadPriority = 0; [t1 start]; NSThread *t2 = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil]; // 1. 线程名称
t2.name = @"Thread BBB";
// 2. 优先级
t2.threadPriority = 1; [t2 start];
} - (void)demo {
for (int i = 0; i < 10; ++i) {
// 堆栈大小
NSLog(@"%@ 堆栈大小:%tuK", [NSThread currentThread], [NSThread currentThread].stackSize / 1024);
} // 模拟崩溃
// 判断是否是主线程
// if (![NSThread currentThread].isMainThread) {
// NSMutableArray *a = [NSMutableArray array];
//
// [a addObject:nil];
// }
}

属性

1. name - 线程名称

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

2. threadPriority - 线程优先级

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

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

3. stackSize - 栈区大小

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

4. isMainThread - 是否主线程

资源共享

资源共享-卖票

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

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

卖票逻辑

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
self.tickets = 20; [self saleTickets];
} /// 卖票逻辑 - 每一个售票逻辑(窗口)应该把所有的票卖完
- (void)saleTickets {
while (YES) {
if (self.tickets > 0) {
self.tickets--;
NSLog(@"剩余票数 %d %@", self.tickets, [NSThread currentThread]);
} else {
NSLog(@"没票了 %@", [NSThread currentThread]);
break;
}
}
}

添加线程

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

添加休眠

- (void)saleTickets {
while (YES) {
// 模拟休眠
[NSThread sleepForTimeInterval:1.0]; if (self.tickets > 0) {
self.tickets--;
NSLog(@"剩余票数 %d %@", self.tickets, [NSThread currentThread]);
} else {
NSLog(@"没票了 %@", [NSThread currentThread]);
break;
}
}
}

运行测试结果

互斥锁

添加互斥锁

- (void)saleTickets {

    while (YES) {
[NSThread sleepForTimeInterval:1.0]; @synchronized(self) {
if (self.tickets > 0) {
self.tickets--;
NSLog(@"剩余票数 %d %@", self.tickets, [NSThread currentThread]);
continue;
}
} NSLog(@"没票了 %@", [NSThread currentThread]);
break;
}
}

互斥锁小结

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

互斥锁参数

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

原子属性

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

代码演练

  • 定义属性
@property (nonatomic, strong) NSObject *obj1;
@property (atomic, strong) NSObject *obj2;
@property (nonatomic, strong) NSObject *obj3;
  • 模拟原子属性
@synthesize obj3 = _obj3;
- (void)setObj3:(NSObject *)obj3 {
@synchronized(self) {
_obj3 = obj3;
}
} - (NSObject *)obj3 {
return _obj3;
} * 性能测试 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
int largeNumber = 1000 * 10000; NSLog(@"非原子属性");
CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
for (int i = 0; i < largeNumber; i++) {
self.obj1 = [[NSObject alloc] init];
}
NSLog(@"%f", CFAbsoluteTimeGetCurrent() - start); NSLog(@"原子属性");
start = CFAbsoluteTimeGetCurrent();
for (int i = 0; i < largeNumber; i++) {
self.obj2 = [[NSObject alloc] init];
}
NSLog(@"%f", CFAbsoluteTimeGetCurrent() - start); NSLog(@"模拟原子属性");
start = CFAbsoluteTimeGetCurrent();
for (int i = 0; i < largeNumber; i++) {
self.obj3 = [[NSObject alloc] init];
}
NSLog(@"%f", CFAbsoluteTimeGetCurrent() - start);
}

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

自旋锁 & 互斥锁

  • 共同点

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

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

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

线程安全

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

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

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

iOS 开发建议

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

线程间通讯

主线程实现

定义属性

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

loadView

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

如果重写了 loadViewStoryboard & XIB 都无效

- (void)loadView {
_scrollView = [[UIScrollView alloc] init];
_scrollView.backgroundColor = [UIColor orangeColor];
self.view = _scrollView; UIImageView *iv = [[UIImageView alloc] init];
[self.view addSubview:iv];
_imageView = iv;
}

viewDidLoad

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

下载网络图片

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

设置图片

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

设置滚动视图的缩放

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

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

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

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

3> 跟踪 scrollView 缩放效果

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

线程间通讯

  • 在后台线程下载图像
[self performSelectorInBackground:@selector(downloadImage) withObject:nil];
  • 在主线程设置图像
[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. windows下的Redis安装:

    windows下的Redis安装: 百度网盘地址:https://pan.baidu.com/s/1yYED2pXLWolPXvWaABtF2Q 提取密码:xshu 1.解压文件并且创建start.b ...

  2. 解决React路由跳转时出现的红色警告: Warning: Failed prop type: Invalid prop `component` of type `object` supplied to `Route`, expected `function`.

    一.报警如图: 二.查找路由版本 我使用路由版本是4.3.1的,然后我测试所有4.0+版本都会出现以上警告. 三.未解决前的代码 三.我又解读了一下报警告内容的大致意思:就是props需要通过函数返回 ...

  3. 机器学习:支持向量机(SVM)

    SVM,称为支持向量机,曾经一度是应用最广泛的模型,它有很好的数学基础和理论基础,但是它的数学基础却比以前讲过的那些学习模型复杂很多,我一直认为它是最难推导,比神经网络的BP算法还要难懂,要想完全懂这 ...

  4. ClickHouse 参数配置

    转载自:https://xw.qq.com/cmsid/20200806A0PQ7X00?ADTAG=amp 在 ClickHouse 进程中,CPU 的主频越高越好,通常建议使用 32 以上的机型, ...

  5. SwitchyOmega 配置

    1.google 扩展程序里面的chrome 网上应用店里面安装Proxy SwitchyOmega 2.新建情景模式 3.配置代理 4.自动切换添加新建的情景模式,最后保存

  6. 团队作业5:Alpha版本测试和发布(歪瑞古德小队)

    目录 一.项目文档和代码 二.Alpha版本测试报告 2.1 功能测试 2.1.1 功能列表 2.1.2 场景测试 2.1.3 测试结果 2.1.4 bug清单 2.2 兼容性测试 2.3 性能测试 ...

  7. 还在问什么是JavaScript构造函数、实例、原型对象以及原型链?看完这篇你就懂

    1概述 ES6, 全称 ECMAScript 6.0 ,2015.06 发版.在ES6之前,对象不是基于类创建的,而是用一种称为构造函数的特殊函数来定义对象和它们的特征. 2构造函数 构造函数是一种特 ...

  8. Nuxt.js 踩坑记录(3) Net.connection xxxx

    浏览器报的是Net.connection啥的错误 项目运行时报错这个 [HPM] Error occurred while trying to proxy request article from l ...

  9. 区块链入门到实战(36)之Solidity – 运算符

    Solidity – 算术运算符 Solidity 支持的算术运算符,如下表所示: 假设变量A的值为10,变量B的值为20. 序号 运算符与描述 1 + (加)求和例: A + B = 30 2 – ...

  10. koa-graphql express-graphql 中如何 定义每一个字段resolver执行函数

    第一种方式:  首先来看一下,官方给出的koa-graphql的例子, ```js var express = require('express'); var {graphqlHTTP} = requ ...