当前位置: 首页 > 编程日记 > 正文

iOS底层原理 - 常驻线程

iOS底层原理 - 常驻线程

在 AFN 2.0 时代,会经常看到 AFN 创建一个常驻线程的方式:

0️⃣ AFN 2.0 时代的常驻线程

+ (NSThread *)networkRequestThread {static NSThread *_networkRequestThread = nil;static dispatch_once_t oncePredicate;dispatch_once(&oncePredicate, ^{_networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];[_networkRequestThread start];});return _networkRequestThread;
}+ (void)networkRequestThreadEntryPoint:(id)__unused object {@autoreleasepool {[[NSThread currentThread] setName:@"AFNetworking"];NSRunLoop *runLoop = [NSRunLoop currentRunLoop];[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];[runLoop run];}
}
复制代码

我们知道 RunLoop 是App运行的基础,并且每个线程都有其对应的唯一的 RunLoop 保证其正常运行,而且我们还知道 RunLoop 必须在特定模式下才能保持活跃状态,更多关于 RunLoop 底层的问题可参阅 iOS - Runloop 详解

1️⃣ 新时代常驻线程初试

在项目中我们也会有类似创建一个常驻线程进行某些耗时操作的需求,若每次均创建一个新的异步线程效率很低,作为优化我们需要一个常驻的异步线程。 如下代码可满足我们创建一个常驻线程的需求:

1. 声明常驻线程属性并对其强引用
```objc
@property (nonatomic, weak) NSThread *p_thread;
```
复制代码
2. 实例化常驻线程,且获取当前 RunLoop 设置一个 source 使其保活
```objc
+ (void)p_creatThreadMethod {void (^creatThreadBlock)(void) = ^ {NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];[currentRunLoop addPort:[NSPort new] forMode:NSDefaultRunLoopMode];[currentRunLoop run];};if (@available(iOS 10.0, *)) {self.p_thread = [[NSThread alloc] initWithBlock:creatThreadBlock];} else {self.p_thread = [[NSThread alloc] initWithTarget:self selector:@selector(class_creatThreadMethod:) object:creatThreadBlock];}
}+ (void)class_creatThreadMethod:(void (^)(void))block {block();
}
```
复制代码
3. 使用:在需要常驻线程执行某操作时:
```objc
- (void)threakTaskMethod:(void (^)(void))task {[self performSelector:@selector(taskMethod:) onThread:self.p_thread withObject:task waitUntilDone:NO];}
```
复制代码
弊端:此时我们创建的常驻线程无法跟随当前对象的释放而释放:因为

Apple 对 [[NSRunLoop currentRunLoop] run]; 的解释:

If no input sources or timers are attached to the run loop, this method exits immediately; otherwise, it runs the receiver in the NSDefaultRunLoopMode by repeatedly invoking runMode:beforeDate:. In other words, this method effectively begins an infinite loop that processes data from the run loop’s input sources and timers. Manually removing all known input sources and timers from the run loop is not a guarantee that the run loop will exit. macOS can install and remove additional input sources as needed to process requests targeted at the receiver’s thread. Those sources could therefore prevent the run loop from exiting. If you want the run loop to terminate, you shouldn't use this method. Instead, use one of the other run methods and also check other arbitrary conditions of your own, in a loop. A simple example would be:

大概意思就是: 如果 RunLoop 没有定时器或输入源,这个方法会立即结束。否则他会反复调用 runMode:beforeDate: 方法,换句话说就是这个方法实际上是开启了一个无限循环用以处理 runloop 的输入源和定时器。 手动移除所有输入源和定时器并不能保证 RunLoop 会退出。MacOS 可以安装和移除额外的输入源以处理接收者线程的请求。所以这种方式可组织 RunLoop 退出。 如果你想终结 RunLoop ,你不能使用这种方式。相反,使用其他的运行方法在 RunLoop 中检测他们的状态,一个简单的?:

这时 Apple 为我们提供了一个控制 RunLoop 声明周期的例子:

BOOL shouldKeepRunning = YES; // global
NSRunLoop *theRL = [NSRunLoop currentRunLoop];
while (shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);复制代码

所以我们可以对以上代码进行改进!

2️⃣ 控制常驻线程的生命周期

1. 声明常驻线程属性和保活状态变量
@property (nonatomic, strong) XWThread *p_thread;
@property (nonatomic, assign, getter=isShouldKeepRunning) BOOL shouldKeepRunning;
复制代码
2. 在管理类中创建常驻线程
- (NSThread *)thread {NSThread *thread = nil;__weak typeof(self) weakSelf = self;void (^creatThreadBlock)(void) = ^ {NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];[currentRunLoop addPort:[NSPort new] forMode:NSDefaultRunLoopMode];while (weakSelf && weakSelf.isShouldKeepRunning) {// runloop 只有在外部标识 shouldKeepRunning 为 YES 时才继续 run[currentRunLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];}};self.shouldKeepRunning = YES;if (@available(iOS 10.0, *)) {thread = [[NSThread alloc] initWithBlock:creatThreadBlock];} else {thread = [[NSThread alloc] initWithTarget:weakSelf selector:@selector(creatThreadMethod:) object:creatThreadBlock];}[thread start];return thread;
}- (void)creatThreadMethod:(void (^)(void))creatThreadBlock {creatThreadBlock();
}
复制代码
3. 在使用常驻线程执行某些操作时
/**在默认常驻线程中执行操作 (线程需随当前对象创建或销毁)@param task 操作*/
- (void)executeTask:(XWLivingThreadTask)task {if (!task || !self.p_thread) return;[self performSelector:@selector(threakTaskMethod:) onThread:self.p_thread withObject:task waitUntilDone:NO];
}- (void)threakTaskMethod:(void (^)(void))task {task ? task() : nil;
}
复制代码
4. 在对象销毁时手动销毁常驻线程
- (void)dealloc {[self performSelector:@selector(clearThreadMethod) onThread:self.p_thread withObject:nil waitUntilDone:YES];
}- (void)clearThreadMethod {self.shouldKeepRunning = NO; // runloop 结束标记CFRunLoopStop(CFRunLoopGetCurrent());
}
复制代码

至此,我们就封装了一个可控制其生命周期的常驻线程。美滋滋。

其中 RunLoop 保活部分代码也可使用底层 CoreFoundation 的代码实现:

    void (^creatThreadBlock)(void) = ^ {CFRunLoopSourceContext content = {0};CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &content);CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);CFRelease(source);CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, false);};
复制代码

3️⃣ 开源

你若问我有没有演示 Demo, 答案是有的,封装了一个小工具类用以快速创建全局常驻线程和局部常驻线程的方法。 这是代码 ↓

XWLivingThread

相关文章:

A monad tutorial for Clojure programmers (part 3)

Before moving on to the more advanced aspects of monads, let’s recapitulate what defines a monad (see part 1 and part 2 for explanations): A data structure that represents the result of a computation, or the computation itself. We haven’t seen an example…

Flex精华摘要--使用AS脚本

在MXML文件中实现ActionScript逻辑的几种方法&#xff1a;最简单的方法&#xff0c;在一个MXML文件中通过组件的事件直接书写简单的逻辑控制&#xff0c;但是并不推荐。 <?xml version"1.0" encoding"utf-8"?> <mx:Application xmlns:mx"h…

(C++)自定义链表并写入

确定链表节点的组成&#xff0c;一般由数据和指针构成 struct node{int data;//数据域node* next;//指针域 }; 使用new运算符为节点分配内存空间 node* p new node; 编写创建列表函数&#xff0c;参数为链表的长度(从用户输入读入)&#xff0c;返回值为创建的列表的头指针…

Unicode转义(\uXXXX)的编码和解码

在涉及Web前端开发时, 有时会遇到\uXXXX格式表示的字符, 其中XXXX是16进制数字的字符串表示形式, 在js中这个叫Unicode转义字符, 和\n \r同属于转义字符. 在其他语言中也有类似的, 可能还有其它变形的格式. 多数时候遇到需要解码的情况多点, 所以会先介绍解码decode, 后介绍…

BZOJ 2004 [Hnoi2010]Bus 公交线路

题目链接 https://www.lydsy.com/JudgeOnline/problem.php?id2004 题解 状压dp&#xff0c;记f[i][S]f[i][S]f[i][S]表示[1,i−p][1,i-p][1,i−p]的车都被安排好了&#xff0c;而[i−p1,i][i-p1,i][i−p1,i]的车中&#xff0c;SSS中有111的位置都安排有车停&#xff0c;并且恰…

【转载】C语言编译全过程

今天在blog.chinaunix.net/u3博客看到一篇关于语言编译过程的文章&#xff0c;觉得精简&#xff0c;清晰所以摘录下来我的blog。作为一个程序员了解编译过程对程序的编写也很有帮助。下面是博文的内容&#xff1a;编译的概念&#xff1a;编译程序读取源程序&#xff08;字符流&…

5层模型中数据从源主机到目的主机之旅

报文是用户发送的数据 传输层可能对报文进行拆分&#xff0c;加上段头 网络层会加上网络层的头&#xff0c;构成的协议数据单元叫做数据报 链路层会加头加尾构造帧 路由器的链路层会去掉帧头帧尾&#xff0c;还原到网络层数据报 再次封装成链路层的数据帧 目的主机的链路层再…

JavaScript模式读书笔记 第5章 对象创建模式

1&#xff0c;命名空间模式 namespace <script>var myApp {};//通过全局变量来实现命名空间maApp.Parent function (){};myApp.Child function(){};</script>通用命名空间函数<script>//不安全代码var myApp {};//安全代码if(typeof myApp "undef…

174. Dungeon Game

一、题目 1、审题 2、分析 只能向右、向下移动的王子&#xff0c;从左上角要到右下角救公主&#xff0c;每经过一个方格&#xff0c;可能获得血瓶加血量&#xff0c;或者碰到怪物减血量&#xff0c;当王子血量 < 1 时就挂了&#xff0c;为了能成功救得公主&#xff0c;求王子…

DotNetNuke安装与下载

【下载专区】 DotNetNuke (DNN) 5.1 稳定版正式发布 http://www.dnnmix.com/dotnetnuke-dnn-51-released/ DotNetNuke (DNN) 资源共享 http://www.dnnmix.com/resources/ DotNetNuke官方下载 http://www.dotnetnuke.com/tabid/125/default.aspx 【安装教程】 DotNetNuke安装大…

1025 反转链表

1. 第一次做链表题&#xff0c;但是这题其实也就是套了个链表的壳子&#xff0c;虽然在结点的结构体里面有下一节点地址next这个属性&#xff0c;但是也只在最初给结点标序号时用到&#xff0c;由于没有真正对链表实施倒序&#xff0c;所以后面输出的下一结点的地址实际上只是算…

关于margin

<html><body><div style"width:200px;height:200px;background-color:red;> <div style"width:100px;height:100px;background-color:black;margin-left:300px;"></div></div></body></html> 左边是火狐显示&a…

DNS迭代式和递归式域名查询对比

背景知识&#xff1a;DNS数据库是树状的层次式的 本地域名服务器并不在这个体系当中&#xff0c;它相当于这个体系面向用户的代理。 迭代式&#xff1a;DNS server告诉用户&#xff1a;我不认识这域名&#xff0c;但我知道你可以问哪个DNS服务器 递归式&#xff1a;用户告诉D…

UIActionSheet在iOS8中被弃用造成的错误

UIActionSheet在iOS7.0中效果图如下&#xff1a; UIActionSheet在iOS8中效果图如下&#xff1a; 造成这样的原因&#xff0c;是因为此控件在iOS8中被弃用了&#xff0c;而使用了UIAlertViewController替代的原因&#xff0c;具…

SQL分页语句(转)

有关分页 SQL 的资料很多&#xff0c;有的使用存储过程&#xff0c;有的使用游标。本人不喜欢使用游标&#xff0c;我觉得它耗资、效率低&#xff1b;使用存储过程是个不错的选择&#xff0c;因为存储过程是经过预编译的&#xff0c;执行效率高&#xff0c;也更灵活。先看看单条…

一个考查作用域以及闭包的题目

var a 2;var func (function(){ var a 3; return function(){a;console.log(a); } })(); func();func(); 1.涉及的知识点&#xff1a; &#xff08;1&#xff09;JS变量的作用域 &#xff08;2&#xff09;闭包2.变量的作用域&#xff0c;通俗来说就是变量所能起到作用的范围…

弄懂“进程”(上):3个组成部分、4个基本特征、4个基本状态

目录 进程实体的三个部分 1.PCB 2.程序段 3.相关的数据段 进程的四大特征 1.动态性 2.并发性 3.独立性 4.异步性 进程的状态(3个基本挂起) 1.三个基本状态 2.挂起状态 进程实体的三个部分 1.PCB 作用是让参与并发执行的每个程序独立运行&#xff0c;或者说&…

解决Failed to execute goal org.apache.maven.plugins

1.Maven构建失败 Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin: 2.3 . 2 :compile ( default-compile) on project oecp: Compilation failure 2.解决方法 把jdk换成自己安装的jdk 换后&#xff0c;再maven install就可以了转载于:https://www.cnb…

P4722 【模板】最大流

P4722 【模板】最大流 加强版 / 预流推进 今日心血来潮&#xff0c;打算学习hlpp 然后学了一阵子。发现反向边建错了。容量并不是0.qwq 然后就荒废了一晚上。 算法流程的话。有时间补上 #include<cstdio> #include<algorithm> #include<iostream> #include&l…

与我们的书合影——在2009北京国际图书展(BIBF)

2009年9月5日&#xff0c;武汉博文编辑许莹、夏青观看了于国展旧馆&#xff08;静安庄&#xff09;举行的2009北京国际图书展&#xff08;BIBF&#xff09;“专业场”。在电子工业出版社展台&#xff0c;编辑兴奋地与我们的几本畅销书&#xff08;《把时间当作朋友》、《走出软…

弄懂“进程”(下):进程的控制、同步和通信

进程控制 是进程管理的主要功能&#xff0c;负责创建和终止进程、进程执行过程中的状态转换。 由操作系统内核通过原语实现。 1.OS内核 常驻内存的、紧靠硬件的软件层次&#xff0c;运行在系统态(又称管态、内核态)&#xff0c;以免遭到用户程序的破坏。 主要包括&#xf…

(转自Timon's wang blogs)C#实现web信息自动抓取

原文转自&#xff1a;http://www.csharp.net.cn/post/C实现web信息自动抓取.html主要为了学习一下相关的网络蜘蛛&#xff0c;为自己获取信息使用背景 随着Internet的普及&#xff0c;网络信息正以极高的速度增长&#xff0c;在这么多数据中找到自己需要的信息是一件很繁琐的事…

数据结构-栈与队列

栈的定义 栈是限定仅在表尾进行插入和删除操作的线性表 我们把允许插入和删除的一端称为栈顶 (top) &#xff0c;另一端称为栈底 (bottom) &#xff0c;不含任何数据元素的栈称为空栈。 栈又称为后进先出 (Last In Filrst Out) 的线性表&#xff0c;简称LIFO结构。 理解栈的定义…

雷观(七):靠谱的程序员,不是随便一个码农就可以做到的

在学习Web开发4年之后&#xff0c;我自己可以独立做一些基本的项目了。在加入前单位秒针&#xff0c;也做了几个Web项目。 我发现一个现象&#xff0c;很多公司大部分的Web项目&#xff0c;用到的技术很少&#xff0c;主要就是SSH等框架&#xff0c;实现一些行业的业务逻辑&…

1032 Sharing

思路很简单&#xff0c;不要想多了&#xff0c;就是找第一个出现的共同字母&#xff0c;即使后面不一样了也没关系。 经验收获如下&#xff1a; 1. 5位整数算小整数&#xff0c;用静态链表&#xff0c;本质上是哈希。 2. 读入字符型的时候千万注意空格。 AC代码 #include&…

Magento开发的特点有哪些?

Magento是一套专业开源的电子商务系统&#xff0c;也是目前主流的外贸网站购物系统&#xff0c;都是居于PHP语言开发的&#xff0c;数据库使用的是Mysql&#xff0c;且浏览界面很适合欧美用户的使用习惯。Magento开发设计得非常灵活&#xff0c;具有模块化架构体系和丰富的功能…

Linux多任务编程之五:exit()和_exit()函数(转)

来源&#xff1a;CSDN 作者&#xff1a;王文松 转自&#xff1a;Linux公社 ------------------------------------------------------------------------------------------------------------------------------------------------ wait()和waitpid() 函数说明 wait()函数用…

LogMiner日志分析工具的使用

1.安装logminer&#xff1a; 要安装LogMiner工具&#xff0c;必须首先要运行下面这样两个脚本&#xff0c; $ORACLE_HOME/rdbms/admin/dbmslm.sql $ORACLE_HOME/rdbms/admin/dbmslmd.sql. 这两个脚本必须均以SYS用户身份运行。 *************使用字典文件…

1052 Linked List Sorting

1. 开始测试点4不通过&#xff0c;得分24/25&#xff0c;是忽略了所有节点都不在链表上的特殊情况。 2. 其实就是用静态链表&#xff0c;把结点根据值的大小&#xff0c;升序排列&#xff0c;所以一开始把每个结点的key赋值为超出最大值的maxn&#xff0c;是为了方便输出。 3…

C#尝试读取或写入受保护的内存。这通常指示其他内存已损坏。

用VS2012调试时发现在调用数据集时提示“尝试读取或写入受保护的内存。这通常指示其他内存已损坏。” 用管理员身份运行CMD&#xff0c;输入netsh winsock reset并回车 转载于:https://www.cnblogs.com/CandiceW/p/4204552.html