iOS进阶之多线程--NSThread详解

2024-04-15 00:48

本文主要是介绍iOS进阶之多线程--NSThread详解,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

iOS进阶之多线程–NSThread详解

https://www.jianshu.com/p/686dbf4bbb52

一个默默无闻的程序猿
1
2018.04.02 17:06:56
字数 1,856
阅读 8,643
NSThread简介
NSThread是苹果官方提供面向对象操作线程的技术,简单方便,可以直接操作线程对象,不过需要自己控制线程的生命周期。在平时使用很少,最常用到的无非就是 [NSThread currentThread]获取当前线程。

NSThread使用
1、 实例初始化、属性和实例方法
初始化
//创建线程
NSThread *newThread = [[NSThread alloc]initWithTarget:self selector:@selector(demo:) object:@“Thread”];
//或者
NSThread newThread=[[NSThread alloc]init];
NSThread newThread= [[NSThread alloc]initWithBlock:^{
NSLog(@“initWithBlock”);
}];
属性
线程字典
/

每个线程都维护了一个键-值的字典,它可以在线程里面的任何地方被访问。
你可以使用该字典来保存一些信息,这些信息在整个线程的执行过程中都保持不变。
比如,你可以使用它来存储在你的整个线程过程中 Run loop 里面多次迭代的状态信息。
NSThread实例可以使用一下方法
*/
@property (readonly, retain) NSMutableDictionary threadDictionary;
NSMutableDictionary dict = [thread threadDictionary];
优先级
@property double threadPriority ; //优先级
线程优先级
/
NSQualityOfService:
NSQualityOfServiceUserInteractive:最高优先级,主要用于提供交互UI的操作,比如处理点击事件,绘制图像到屏幕上
NSQualityOfServiceUserInitiated:次高优先级,主要用于执行需要立即返回的任务
NSQualityOfServiceDefault:默认优先级,当没有设置优先级的时候,线程默认优先级
NSQualityOfServiceUtility:普通优先级,主要用于不需要立即返回的任务
NSQualityOfServiceBackground:后台优先级,用于完全不紧急的任务
*/
@property NSQualityOfService qualityOfService;
线程名称
@property (nullable, copy) NSString name;
线程使用栈区大小,默认是512K
@property NSUInteger stackSize ;
线程正在执行
@property (readonly, getter=isExecuting) BOOL executing;
线程执行结束
@property (readonly, getter=isFinished) BOOL finished;
线程是否可以取消
@property (readonly, getter=isCancelled) BOOL cancelled;
实例方法
-(void)start; 启动线程
实例化线程需要手动启动才能运行
[thread start];
-(BOOL)isMainThread; 是否为主线程
isMain=[thread isMainThread];
-(void)setName:(NSString )n; 设置线程名称
[thread setName=@“The Second Thread”];
-(void)cancel ; 取消线程
[thread cancel];
-(void)main ; 线程的入口函数
[thread main];
-(void)isExecuting; 判断线程是否正在执行
BOOL isRunning=[thread isExecuting];
-(void)isFinished;判断线程是否已经结束
BOOL isEnd=[thread isFinished];
-(void)isCancelled; 判断线程是否撤销
isCancel=[thread isCancelled];
2、类方法
创建子线程并开始,注意以下两个类方法创建后就可执行,不需手动开启
/

block方式
*/

  • (void)detachNewThreadWithBlock:(void (^)(void))block;
    /**
    SEL方式
    */
  • (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;
    +(void)currentThread;获取当前线程
    [NSThread currentThread]
    +(BOOL)isMultiThreaded; 当前代码运行所在线程是否是子线程
    BOOL isMulti = [NSThread isMultiThreaded];
    +(void)sleepUntilDate:(NSDate *)date; 当前代码所在线程睡到指定时间
    [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];
    +(void)sleepForTimeInterval:(NSTimeInterval)ti; 当前线程睡多长时间
    [NSThread sleepForTimeInterval:1.0];
    +(void)exit; 退出当前线程
    [NSThread exit];
    +(double)threadPriority; 设置当前线程优先级
    double dPriority=[NSThread threadPriority];
    +(BOOL)setThreadPriority:(double)p; 给当前线程设定优先级,调度优先级的取值范围是0.0 ~ 1.0,默认0.5,值越大,优先级越高。
    BOOL isSetting=[NSThread setThreadPriority:(0.0~1.0)];
    +(NSArray *)callStackReturnAddresses;线程的调用都会有函数的调用函数的调用就会有栈返回地址的记录,在这里返回的是函 数调用返回的虚拟地址,说白了就是在该线程中函数调用的虚拟地址的数组
    NSArray *addressArray=[NSThread callStackReturnAddresses];
    +(NSArray )callStackSymbols 同上面的方法一样,只不过返回的是该线程调用函数的名字数字
    NSArray
    nameNumArray=[NSThread callStackSymbols];
    注意:callStackReturnAddress和callStackSymbols这两个函数可以同NSLog联合使用来跟踪线程的函数调用情况,是编程调试的重要手段

3、隐式创建&线程间通讯
以下方法位于NSObject (NSThreadPerformAdditions)分类中,所有继承NSObject 实例化对象都可调用以下方法

/**
指定方法在主线程中执行
参数1. SEL 方法
2.方法参数
3.是否等待当前执行完毕
4.指定的Runloop model
*/

  • (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
  • (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
    // equivalent to the first method with kCFRunLoopCommonModes
    /**
    指定方法在某个线程中执行
    参数1. SEL 方法
    2.方法参数
    3.是否等待当前执行完毕
    4.指定的Runloop model
    */
  • (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
  • (void)performSelector:(SEL)aSelector onThread:(NSThread )thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
    // equivalent to the first method with kCFRunLoopCommonModes
    /
    *
    指定方法在开启的子线程中执行
    参数1. SEL 方法
    2.方法参数
    */
  • (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
    注意:我们经常提到的“线程间通讯”其实就是上面几个方法,并不是多高大上,也没有多复杂!!!

再注意:苹果声明UI更新一定要在UI线程(主线程)中执行,虽然不是所有后台线程更新UI都会出错。
4、线程间资源共享&线程加锁
在程序运行过程中,如果存在多线程,那么各个线程读写资源就会存在先后、同时读写资源的操作,因为是在不同线程,CPU调度过程中我们无法保证哪个线程会先读写资源,哪个线程后读写资源。因此为了防止数据读写混乱和错误的发生,我们要将线程在读写数据时加锁,这样就能保证操作同一个数据对象的线程只有一个,当这个线程执行完成之后解锁,其他的线程才能操作此数据对象。NSLock / NSConditionLock / NSRecursiveLock / @synchronized都可以实现线程上锁的操作。

@synchronized
直接上例子:相信12306卖火车票的例子大家了解
首先:开启两个线程同时售票
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];
    //互斥锁 – 保证锁内的代码在同一时间内只有一个线程在执行
    @synchronized (self){
    //1.判断是否有票
    if (self.tickets > 0) {
    //2.如果有就卖一张
    self.tickets --;
    NSLog(@“还剩%d张票 %@”,self.tickets,[NSThread currentThread]);
    }else{
    //3.没有票了提示
    NSLog(@“卖完了 %@”,[NSThread currentThread]);
    break;
    }
    }
    }

}
NSLock
-(BOOL)tryLock;//尝试加锁,成功返回YES ;失败返回NO ,但不会阻塞线程的运行
-(BOOL)lockBeforeDate:(NSDate *)limit;//在指定的时间以前得到锁。YES:在指定时间之前获得了锁;NO:在指定时间之前没有获得锁。
该线程将被阻塞,直到获得了锁,或者指定时间过期。

  • (void)setName:(NSString*)newName//为锁指定一个Name
  • (NSString*)name//返回锁指定的name
    @property (nullable, copy) NSString *name;线程锁名称
    举个例子:

NSLock* myLock=[[NSLock alloc]init];
NSString *str=@“hello”;
[NSThread detachNewThreadWithBlock:^{
[myLock lock];
NSLog(@"%@",str);
str=@“world”;
[myLock unlock];
}];
[NSThread detachNewThreadWithBlock:^{
[myLock lock];
NSLog(@"%@",str);
str=@“变化了”;
[myLock unlock];
}];
输出结果不加锁之前,两个线程输出一样 hello;加锁之后,输出分辨为hello 与world。

NSConditionLock
使用此锁,在线程没有获得锁的情况下,阻塞,即暂停运行,典型用于生产者/消费者模型。

  • (instancetype)initWithCondition:(NSInteger)condition;//初始化条件锁

  • (void)lockWhenCondition:(NSInteger)condition;//加锁 (条件是:锁空闲,即没被占用;条件成立)

  • (BOOL)tryLock; //尝试加锁,成功返回TRUE,失败返回FALSE

  • (BOOL)tryLockWhenCondition:(NSInteger)condition;//在指定条件成立的情况下尝试加锁,成功返回TRUE,失败返回FALSE

  • (void)unlockWithCondition:(NSInteger)condition;//在指定的条件成立时,解锁

  • (BOOL)lockBeforeDate:(NSDate *)limit;//在指定时间前加锁,成功返回TRUE,失败返回FALSE,

  • (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;//条件成立的情况下,在指定时间前加锁,成功返回TRUE,失败返回FALSE,
    @property (readonly) NSInteger condition;//条件锁的条件
    @property (nullable, copy) NSString *name;//条件锁的名称
    举个例子:

    NSConditionLock* myCondition=[[NSConditionLock alloc]init];
    [NSThread detachNewThreadWithBlock:^{
    for(int i=0;i<5;i++)
    {
    [myCondition lock];
    NSLog(@“当前解锁条件:%d”,i);
    sleep(2);
    [myCondition unlockWithCondition:i];
    BOOL isLocked=[myCondition tryLockWhenCondition:2];
    if(isLocked)
    {
    NSLog(@“加锁成功!!!!!”);
    [myCondition unlock];
    }
    }
    }];
    输出结果,在条件2 解锁之后,等待条件2 的锁加锁成功。

NSRecursiveLock
此锁可以在同一线程中多次被使用,但要保证加锁与解锁使用平衡,多用于递归函数,防止死锁。

  • (BOOL)tryLock;//尝试加锁,成功返回TRUE,失败返回FALSE
  • (BOOL)lockBeforeDate:(NSDate *)limit;//在指定时间前尝试加锁,成功返回TRUE,失败返回FALSE
    @property (nullable, copy) NSString *name;//线程锁名称
    使用示例:

-(void)initRecycle:(int)value
{
[myRecursive lock];
if(value>0)
{
NSLog(@“当前的value值:%d”,value);
sleep(2);
[self initRecycle:value-1];
}
[myRecursive unlock];
}
输出结果: 从你传入的数值一直到1,不会出现死锁

5、线程安全之原子属性 atomic
原子属性(线程安全)与非原子属性,平时我们@property声明对象属性时会用到nonatomic,是什么意思呢?
苹果系统在我们声明对象属性时默认是atomic,也就是说在读写这个属性的时候,保证同一时间内只有一个线程能够执行。当声明时用的是atomic,通常会生成 _成员变量 如果同时重写了getter&setter _成员变量 就不自动生成。实际上原子属性内部有一个锁,叫做“自旋锁”。
首先我们比较一下“自旋锁” & “互斥锁”的异同,然后回答上面的问题

共同点
都能够保证线程安全
不同点
互斥锁:如果其他线程正在执行锁定的代码,此线程就会进入休眠状态,等待锁打开;然后被唤醒
自旋锁:如果线程被锁在外面,哥么就会用死循环的方式一直等待锁打开!
无论什么锁,都很消耗性能,效率不高,所以在我们平时开发过程中,会使用nonatomic

@property (strong, nonatomic) NSObject *myNonatomic;
@property (strong, atomic) NSObject *myAtomic;
根据上面描述,我们得出结论,当我们重写了myAtomic的setter和getter方法

  • (void)setMyAtomic:(NSObject *)myAtomic{
    _myAtomic = myAtomic;
    }
  • (NSObject *)myAtomic{
    return _myAtomic;
    }
    那么我们就必须声明一个_myAtomic静态变量

@synthesize myAtomic = _myAtomic;
否则系统在编译的时候找不到 _myAtomic

6、子线程上的Runloop
在介绍子线程上的Runloop之前先来一个有意思的小插曲,我们来介绍一下Runloop,甚至模拟一个Runloop
Runloop 运行循环
-在目前iOS开发中,几乎用不到,在以前iOS黑暗时代,程序员会用到
目的:
保证程序不退出
监听事件
没有事件让程序进入休眠
区分模式:
NSDefaultRunLoopMode - 时钟、网络事件
NSRunLoopCommonModes - 用户交互
模拟Runloop

void click(int type){
printf(“正在运行第%d”,type);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
while (YES) {
printf(“请输入选项 0 表示退出”);
int result = -1;
scanf("%d",&result);
if (result == 0) {
printf(“程序结束\n”);
break;
}else{
click(result);
}
}
}
return 0;
}
在iOS中,开辟的子线程上的Runloop是默认不开启的,并且子线程中的Runloop开启之后是手动无法关闭的。那么当我们给子线程中重复添加不同任务时并且Runloop没有开启的情况下,子线程无法监听事件(确切说是子线程的Runloop),我们后来添加的任务就无法执行。
但是我们如果让子线程Runloop一直工作又浪费资源,下面介绍一个OC中常用到的可以控制子线程Runloop的例子:
首先,Runloop就是一个死循环,那么我们就创建一个死循环,然后声明一个可以判断是否应该退出Runloop循环的属性
@property (assign, nonatomic, getter=isFinished) BOOL finished;
创建子线程并添加任务

NSThread *t = [[NSThread alloc]initWithTarget:self selector:@selector(demo) object:nil];
[t start];
self.finished = NO;
[self performSelector:@selector(otherMethod) onThread:t withObject:nil waitUntilDone:NO];

在第一个任务中加入死循环

  • (void)demo{
    NSLog(@"%@",[NSThread currentThread]);
    //在OC中使用比较多的,退出循环的方式
    while (!self.isFinished) {
    [[NSRunLoop currentRunLoop]runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:.1]];
    }
    NSLog(@“能来吗?”);
    }

在最后添加的任务结束后结束死循环

  • (void)otherMethod{
    for (int i = 0; i < 10; i ++) {
    NSLog(@"%s %@",FUNCTION,[NSThread currentThread]);

    }
    //让上面方法中的死循环结束
    self.finished = YES;
    }

这篇关于iOS进阶之多线程--NSThread详解的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/904479

相关文章

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory

OpenHarmony鸿蒙开发( Beta5.0)无感配网详解

1、简介 无感配网是指在设备联网过程中无需输入热点相关账号信息,即可快速实现设备配网,是一种兼顾高效性、可靠性和安全性的配网方式。 2、配网原理 2.1 通信原理 手机和智能设备之间的信息传递,利用特有的NAN协议实现。利用手机和智能设备之间的WiFi 感知订阅、发布能力,实现了数字管家应用和设备之间的发现。在完成设备间的认证和响应后,即可发送相关配网数据。同时还支持与常规Sof

安卓链接正常显示,ios#符被转义%23导致链接访问404

原因分析: url中含有特殊字符 中文未编码 都有可能导致URL转换失败,所以需要对url编码处理  如下: guard let allowUrl = webUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {return} 后面发现当url中有#号时,会被误伤转义为%23,导致链接无法访问

6.1.数据结构-c/c++堆详解下篇(堆排序,TopK问题)

上篇:6.1.数据结构-c/c++模拟实现堆上篇(向下,上调整算法,建堆,增删数据)-CSDN博客 本章重点 1.使用堆来完成堆排序 2.使用堆解决TopK问题 目录 一.堆排序 1.1 思路 1.2 代码 1.3 简单测试 二.TopK问题 2.1 思路(求最小): 2.2 C语言代码(手写堆) 2.3 C++代码(使用优先级队列 priority_queue)

[MySQL表的增删改查-进阶]

🌈个人主页:努力学编程’ ⛅个人推荐: c语言从初阶到进阶 JavaEE详解 数据结构 ⚡学好数据结构,刷题刻不容缓:点击一起刷题 🌙心灵鸡汤:总有人要赢,为什么不能是我呢 💻💻💻数据库约束 🔭🔭🔭约束类型 not null: 指示某列不能存储 NULL 值unique: 保证某列的每行必须有唯一的值default: 规定没有给列赋值时的默认值.primary key:

K8S(Kubernetes)开源的容器编排平台安装步骤详解

K8S(Kubernetes)是一个开源的容器编排平台,用于自动化部署、扩展和管理容器化应用程序。以下是K8S容器编排平台的安装步骤、使用方式及特点的概述: 安装步骤: 安装Docker:K8S需要基于Docker来运行容器化应用程序。首先要在所有节点上安装Docker引擎。 安装Kubernetes Master:在集群中选择一台主机作为Master节点,安装K8S的控制平面组件,如AP

【Linux 从基础到进阶】Ansible自动化运维工具使用

Ansible自动化运维工具使用 Ansible 是一款开源的自动化运维工具,采用无代理架构(agentless),基于 SSH 连接进行管理,具有简单易用、灵活强大、可扩展性高等特点。它广泛用于服务器管理、应用部署、配置管理等任务。本文将介绍 Ansible 的安装、基本使用方法及一些实际运维场景中的应用,旨在帮助运维人员快速上手并熟练运用 Ansible。 1. Ansible的核心概念

Flutter 进阶:绘制加载动画

绘制加载动画:由小圆组成的大圆 1. 定义 LoadingScreen 类2. 实现 _LoadingScreenState 类3. 定义 LoadingPainter 类4. 总结 实现加载动画 我们需要定义两个类:LoadingScreen 和 LoadingPainter。LoadingScreen 负责控制动画的状态,而 LoadingPainter 则负责绘制动画。