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

2、类方法

  1. 创建子线程并开始,注意以下两个类方法创建后就可执行,不需手动开启
/**
block方式
*/
+ (void)detachNewThreadWithBlock:(void (^)(void))block;
/**
SEL方式
*/
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;
  1. +(void)currentThread;获取当前线程
[NSThread currentThread]
  1. +(BOOL)isMultiThreaded; 当前代码运行所在线程是否是子线程
BOOL isMulti = [NSThread isMultiThreaded];
  1. +(void)sleepUntilDate:(NSDate *)date; 当前代码所在线程睡到指定时间
   [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];
  1. +(void)sleepForTimeInterval:(NSTimeInterval)ti; 当前线程睡多长时间
    [NSThread sleepForTimeInterval:1.0];
  1. +(void)exit; 退出当前线程
[NSThread exit];
  1. +(double)threadPriority; 设置当前线程优先级
double dPriority=[NSThread threadPriority];
  1. +(BOOL)setThreadPriority:(double)p; 给当前线程设定优先级,调度优先级的取值范围是0.0 ~ 1.0,默认0.5,值越大,优先级越高。
BOOL isSetting=[NSThread setThreadPriority:(0.0~1.0)];
  1. +(NSArray *)callStackReturnAddresses;线程的调用都会有函数的调用函数的调用就会有栈返回地址的记录,在这里返回的是函 数调用返回的虚拟地址,说白了就是在该线程中函数调用的虚拟地址的数组
NSArray *addressArray=[NSThread callStackReturnAddresses];
  1. +(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都可以实现线程上锁的操作。

  1. @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;
}
}
} }
  1. 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。

  1. 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 的锁加锁成功。

  1. 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

  1. 在介绍子线程上的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;
}
  1. 在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详解的更多相关文章

  1. iOS多线程之NSThread详解

    在iOS中每个进程启动后都会建立一个主线程(UI线程),这个线程是其他线程的父线程.由于iOS中除了主线程,其他子线程是独立于Cocoa Touch的,所以只有主线程可以更新UI界面.iOS多线程的使 ...

  2. iOS 视图控制器转场详解

    iOS 视图控制器转场详解 前言的前言 唐巧前辈在微信公众号「iOSDevTips」以及其博客上推送了我的文章后,我的 Github 各项指标有了大幅度的增长,多谢唐巧前辈的推荐.有些人问我相关的问题 ...

  3. iOS 证书与签名 解惑详解

    iOS 证书与签名 解惑详解 分类: iPhone2012-06-06 19:57 9426人阅读 评论(1) 收藏 举报 iosxcodecryptographyappleiphone测试   目录 ...

  4. 《iOS 7 应用开发实战详解》

    <iOS 7 应用开发实战详解> 基本信息 作者: 朱元波    管蕾 出版社:人民邮电出版社 ISBN:9787115343697 上架时间:2014-4-25 出版日期:2014 年5 ...

  5. iOS 开发之照片框架详解(2)

    一. 概况 本文接着 iOS 开发之照片框架详解,侧重介绍在前文中简单介绍过的 PhotoKit 及其与 ALAssetLibrary 的差异,以及如何基于 PhotoKit 与 AlAssetLib ...

  6. .NET多线程同步方法详解

    .NET多线程同步方法详解(一):自由锁(InterLocked) .NET多线程同步方法详解(二):互斥锁(lock) NET多线程同步方法详解(三):读写锁(ReadWriteLock) .NET ...

  7. iOS中MVC等设计模式详解

    iOS中MVC等设计模式详解 在iOS编程,利用设计模式可以大大提高你的开发效率,虽然在编写代码之初你需要花费较大时间把各种业务逻辑封装起来.(事实证明这是值得的!) 模型-视图-控制器(MVC)设计 ...

  8. iOS 6分享列表——UIActivityViewController详解

    iOS 6分享列表——UIActivityViewController详解 2013-06-03 01:42:33     发表评论 在iOS 6之后提供了一个分享列表视图,它通过UIActivity ...

  9. IOS数据库操作SQLite3使用详解(转)

    iPhone中支持通过sqlite3来访问iPhone本地的数据库.具体使用方法如下1:添加开发包libsqlite3.0.dylib首先是设置项目文件,在项目中添加iPhone版的sqlite3的数 ...

随机推荐

  1. [web 前端] Npm package.json与package-lock.json文件的作用

    本文链接:https://blog.csdn.net/u013992330/article/details/81110018 最新版nodejs中,多了一个package-lock.json文件,刚开 ...

  2. 测量MySQL的表达式和函数的速度

    测量MySQL的表达式和函数的速度,可以调用benchmark()函数.语法格式是benchmark(loop_count,expr).运行的返回值是0,但是mysql会打印一行显示语句大概要执行多长 ...

  3. ISO/IEC 9899:2011 前言

    前言 1.ISO(国际标准组织)与IEC(国际电工技术委员会)为全世界标准形成了专门的系统.作为ISO或IEC成员的国家机构,通过由各自组织所建立的技术委员会来加入国际标准的开发,以处理特定领域的技术 ...

  4. 【docker】 yaml.scanner.ScannerError: mapping values are not allowed here in "./docker-compose.yml", line 60, column 35

    在启动docker-compose 时候 报错了 命令: docker-compose up -d && docker-compose logs -f 错误代码: 解决 出现这个错误的 ...

  5. python中的一些算法

    两个基础知识点:递归和时间复杂度 递归 递归函数的特点:自己调用自己,有结束条件,看下面例子: def fun1(x): """无结束条件,报错""& ...

  6. react前端模版Material-UI.类似于antd/bootstrap

    Material-UI Material-UI是一个实现了Google's Material Design设计规范的react组件库,开箱即用,使用它可以快速搭建出赏心悦目的应用界面. 文档 各种模版 ...

  7. app内嵌h5页面在ios手机端滑动卡顿的解决方法

    1.带滚动条的dom需加样式 -webkit-overflow-scrolling: touch;2.去掉 width:100%; height:100%

  8. SOC中的DMIPS_GFLOPS_GMACS的含义

    l  DMIPS全称叫Dhrystone MIPS 这项测试是用来计算同一秒内系统的处理能力,它的单位以百万来计算,也就是(MIPS) 上面的意思也就是,这个处理器测整数计算能力为(200*100万) ...

  9. JavaScript有用的代码片段和trick

    浮点数取整 const x = 123.4545; x >> 0; ~~x; x | 0; Math.floor(x); 注意:前三种方法只适用于32个位整数,对于负数的处理上和Math. ...

  10. [转帖]《吊打面试官》系列-Redis基础

    <吊打面试官>系列-Redis基础 https://www.cnblogs.com/aobing/archive/2019/11/07/11811194.html   你知道的越多,你不知 ...