- author :巴哥莫 https://www.jianshu.com/u/a64ad02ab496
- 调试环境 :Mac mojave 10.14.1 + Xcode10 + Swift4.0
- 关键字 :进程、线程 、主线程 、子线程 、队列 、、主队列 、global队列 、main函数、Runloop
- 文集 :IOS- Swift-启动篇
- 章节 :启动
- 编号 :1.0.0
- 写作时间 :2018-12-05
- 本文地址 : https://www.jianshu.com/p/75490b41cde3
目录
前言
笔者本着一个从业多年OC开发的码农,最近兴致好,想学习一下Swift并写点东西当作笔记。原本以为这是个很简单的事情,不就是把OC代码翻译一遍吗?有什么难的,写的时候才发现,把话讲明白似乎没那么容易。
内容概述
- 进程和线程概念复习
- C&OC main启动
- 主线程&子线程
- 主队列
进程和线程概念复习
巴哥去请教了公司C语言的同事,同时也查了一些资料,先引入几个概念的东西 线程进程简单描述
进程
是具有一定独立功能的程序,相对操作系统来说,操作系统分配资源给进程,所以进程作为系统资源分配和调度的基本单位,进程是可以独 立运行的一段程序。
线程
线程进程的一个实体,是CPU调度和分派的基本单位,他是比进程更小的能独立运行的基本单位。相对于进程,线程拥有系统资源比较少,而且线程的生命周期是进程这个程序来控制的。在运行时,只是暂用一些计数器、寄存器和栈 。同时一个进程至少要有一个主线程。所以真正执行任务的是线程。
关于进程和线程,网上的说法五花八门,巴哥复制了一段,供大家参考,下面这段话是我的一个同事告诉我的,他让我深刻理解,我也贴出来供大家参考
进程是拥有资源的最小单位。线程是执行的最小单位。 --- 某C语言同事
main 启动
C 语言代码 main
int main(){
while (1) {
printf("pid is %d \n",getpid());
}
return 0;
}
result:
pid is 78743
pid is 78743
pid is 78743
……
-
在进入main的时候进程和主线程就已经存在了
应用程序进程启动的时候主线程被系统创建了,应用程序是需要不断的和用户进行交互的,即主线程是需要一直存在。main函数在执行到return 的时候进程就会被销毁,线程也会被释放。很显然,应用程序的main函数是不会主动返回的。
- 主线程常驻
应用程序是需要一个长久存在的线程,这个线程通常被我们称作为主线程,主线程如何常驻?(上一小节中通过while循环可以让主线程长期存活,但是这种方式不可取)应用程序都是响应式的机制,例如, 点击,触摸,外部进程抢占资源 …… ,应用程序需要快速的对这些事件进行相应。
oc main函数代码
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
swift没有暴露main方法 他是使用了@UIApplicationMain ,两者内部实现是一样的。(OC的比较直观,直接用OC代码做例子)
UIApplicationMain 函数做了什么,我们从他的函数申明来分析一下?UIKIT_EXTERN int UIApplicationMain(int argc, char * _Nullable argv[_Nonnull], NSString * _Nullable principalClassName, NSString * _Nullable delegateClassName);
四个参数
@param argc C main函数参数 不再复述
@param argv C main函数参数 不再复述
@param principalClassName 一个字符串类型的参数principalClassName,直译为主要类,必须为UIApplication或者其子类,代表着当前app自身。并且如果此参数为nil的话,则默认为@"UIApplication"
@param delegateClassName 代理类。在UIApplication中有个delegate的变量,delegate遵守UIApplicationDelegate协议负责程序的生命周期。UIApplication 接收到所有的系统事件和生命周期事件时,都会把事件传递给UIApplicationDelegate进行处理综上所述:UIApplicationMain做了如下3件事
- 创建了UIApplication或其子类的对象
- 根据传入第四个参数创建了代理对象并设置UIApplication的代理为刚刚创建出来的代理对象
- 应用程序需要main线程常驻,并且还要是响应式 的,所以UIApplication在创建的时候应该还做了一件事,启动了Runloop。(Runloop是怎么做到不堵塞主线程并且快速响应,下一章节将会作介绍)
主线程VS子线程
按照某C语言同事的说法(进程是拥有资源的最小单位。线程是执行的最小单位)进程应该是资源空间以及资源空间下线程的总和。传统意义上的主线程应该叫第一个线程,只不过其他线程的建立是依赖于第一个线程,既然线程是执行的最小单位,CPU在分配时间片段给线程的时候应该是雨露均沾的,而不是分主次的。虽然第一个线程在创建时先于其他的线程,但是他们一旦创建了,CPU在调度的时候应该平等。
思考:??对于整个操作系统而言,线程之间是否的平等的
主线程
主线程通常又叫UI线程。为什么和UI相关的东西都要放在主线程中执行?为什么在子线程中去做UI的操作会经常出现Bug?
@IBAction func doDownLoadAction(_ sender: Any) {
DispatchQueue.global().async {
[unowned self] in
self.imageView.image = UIImage(named: "image1.png");
}
}
result: 虽然程序没有崩溃,但是在控制台上还是报了警告信息,并且这个图片加载的很慢或根本就加载不出来。(子线程在给UI赋值的时候其实值已经传成功,只是没有机制去触发UI的更新)
Main Thread Checker: UI API called on a background thread: -[UIImageView setImage:]
从OCmain的启动函数中可以看出,子线程和主线程宏观上的区别就是主线中添加了Runloop ,Runloop使得主线程一致存在,不会退出。假如我们在子线程中也添加一个Runloop,情况会是什么样的呢?
@IBOutlet weak var imageView: UIImageView!
var subThread:Thread!;
override func viewDidLoad() {
super.viewDidLoad();
subThread = Thread.init(target: self, selector: #selector(makeSubThreadAlive), object: nil)
subThread.name = "subThread";
subThread.start();
}
@IBAction func doSubThreadTask(_ sender: Any) {
self .perform(#selector(refreashImage), on: subThread, with: nil, waitUntilDone: false)
}
@objc func refreashImage(){
self.imageView.image = UIImage(named: "image1.png");
//Thread.sleep(forTimeInterval: 5);
}
@objc func makeSubThreadAlive(){
print("runloop start");
let machPort = NSMachPort();
RunLoop.current.add(machPort, forMode: RunLoop.Mode.default);
RunLoop.current.run();
print("runloop end")
}
result:虽然依然会有警告,但是图片刷新正常了,很显然是Runloop触发了图片控件的更新
Main Thread Checker: UI API called on a background thread: -[UIImageView setImage:]
思考:??是不是表示如果开启一个常驻的子线程,就可以通过该子线程来更新UI?
当然还是在主线程中去更新UI比较好。巴哥通过这个例子告诉大家,其实主线程没有那么神秘,它和其他的线程之间是平等的,只不过UIApplication在初始化的时候在主线程中启动一个Runloop(Runloop 巴哥计划也开个章节写一写,因为这个很重要)
主队列
- 队列概念
队列像栈一样,是一种线性表,它的特性是先进先出,插入在一端,删除在另一端。就像排队一样,刚来的人入队(push)要排在队尾(rear),每次出队(pop)的都是队首(front)的人- 队列特点
队列中的数据元素遵循“先进先出”(First In First Out)的原则,简称FIFO结构。
在队尾添加元素,在队头删除元素。
主队列的获取
DispatchQueue.main
OperationQueue.main;
主队列的获取是通过类变量获取的,这说明主队列是事先分配好的
Question:队列就是队列,为什么要强行提个概念叫主队列呢?
队列和线程
主队列运行在主线程中吗 ?iOS主线程和主队列的区别
备注:线程就是线程,队列就是队列,这两者概念上应该是清晰的(没有可比性,叫做联系或则关系比较准确),只不过队列是运行在线程上的
DispatchQueue.main.async {
print("2222 \(Thread.main)");
}
result: 主队列运行在主线程中,它只是被默认取了个名字叫 main 真实的名字叫做 com.apple.main-thread
2222 <NSThread: 0x282678b00>{number = 1, name = main}
func doSomeThingInCommonQueue(){
let queue = DispatchQueue.init(label: "aaa");
queue.sync {
print("queue run in \(Thread.current)");
self.imageView.image = UIImage(named: "image1.png");
}
}
result: 启动一个队列使用同步方式,它运行的线程是主线程,并且界面刷新正常。
queue run in <NSThread: 0x28233edc0>{number = 1, name = main}
队列是平等的,自定义的队列在主线程中运行也能起到主队列的作用,主队列只不过是一种约定俗成的概念,默认将系统帮我们建立的这个取名为main的队列叫做主队列
巴哥浅见iOS主线程和主队列的区别
这篇文章中的例子很好,巴哥谈谈对这几个例子的理解。
import Foundation
print("Hello, World!")
debugPrint("current thread out: \(Thread.current)") //主线程在这里呢 看我的number编号
let key = DispatchSpecificKey<String>()
let queueGlobal = DispatchQueue.global();
let globalKey = DispatchSpecificKey<String>();
DispatchQueue.main.setSpecific(key: key, value: "main")
DispatchQueue.global().setSpecific(key: globalKey, value: "global");
func log() {
debugPrint("main thread: \(Thread.isMainThread)")
debugPrint("current thread in: \(Thread.current)")
let valueglobal = DispatchQueue.global().getSpecific(key: key);
print("从globalqueue中取queue key 为 main 是否能取到 \(String(describing: valueglobal))");
let valuemain = DispatchQueue.main.getSpecific(key: key);
print("从main queue中取queue key 为 main \(String(describing: valuemain))");
let value = DispatchQueue.getSpecific(key: key)
print("\(String(describing: value))");
debugPrint("main queue: \(value != nil)")
let globalValue = DispatchQueue.getSpecific(key: globalKey);
debugPrint("global queue: \(globalValue != nil)")
}
DispatchQueue.global().sync(execute: log)
RunLoop.current.run()// result2 会屏蔽它
result1: 启动RunLoop (getSpecific这个函数的解释巴哥会在GCD章节中介绍)
Hello, World!
"current thread out: <NSThread: 0x100f0bbc0>{number = 1, name = main}"
"main thread: true"
"current thread in: <NSThread: 0x100f0bbc0>{number = 1, name = main}"
从globalqueue中取queue key 为 main 是否能取到 nil
从main queue中取queue key 为 main Optional("main")
nil
"main queue: false"
"global queue: true"
result2: 不启动RunLoop,Program ended with exit code: 0 进程退出了
Hello, World!
"current thread out: <NSThread: 0x100f0bbc0>{number = 1, name = main}"
"main thread: true"
"current thread in: <NSThread: 0x100f0bbc0>{number = 1, name = main}"
从globalqueue中取queue key 为 main 是否能取到 nil
从main queue中取queue key 为 main Optional("main")
nil
"main queue: false"
"global queue: true"
Program ended with exit code: 0
- 上文中巴哥提到,不要把主线程看的很特殊,应用程序中主线程只不过是在线程 number = 1的线程上启动了RunLoop ,让这个number = 1的线程常驻,RunLoop启动后会让这个number = 1的线程有响应机制。
- sync 不会启动新的线程,所以结果中当前线程是在number = 1的线程上执行
- DispatchQueue.getSpecific(key: key) 这个类方法不是表示从全局获取名字叫做key的队列
如果global queue使用异步执行
import Foundation
print("Hello, World!")
debugPrint("current thread out: \(Thread.current)") //主线程在这里呢 看我的number编号
let key = DispatchSpecificKey<String>()
let queueGlobal = DispatchQueue.global();
let globalKey = DispatchSpecificKey<String>();
DispatchQueue.main.setSpecific(key: key, value: "main")
DispatchQueue.global().setSpecific(key: globalKey, value: "global");
func log() {
debugPrint("main thread: \(Thread.isMainThread)")
debugPrint("current thread in: \(Thread.current)")
let valueglobal = DispatchQueue.global().getSpecific(key: key);
print("从globalqueue中取queue key 为 main 是否能取到 \(String(describing: valueglobal))");
let valuemain = DispatchQueue.main.getSpecific(key: key);
print("从main queue中取queue key 为 main \(String(describing: valuemain))");
let value = DispatchQueue.getSpecific(key: key)
print("\(String(describing: value))");
debugPrint("main queue: \(value != nil)")
let globalValue = DispatchQueue.getSpecific(key: globalKey);
debugPrint("global queue: \(globalValue != nil)")
}
DispatchQueue.global().async(execute: log)//切换成异步
RunLoop.current.run()
result
Hello, World!
"current thread out: <NSThread: 0x100f0dd20>{number = 1, name = main}"
"main thread: false"
"current thread in: <NSThread: 0x100f92dd0>{number = 2, name = (null)}"
从globalqueue中取queue key 为 main 是否能取到 nil
从main queue中取queue key 为 main Optional("main")
nil
"main queue: false"
"global queue: true
- DispatchQueue.main & DispatchQueue.global() 是通过类属性取到的,这两个queue系统早就帮我们分配好了
- 使用DispatchQueue.getSpecific方法去取队列的value的时候,是看你有没有把这个队列分发出去,上面的例子中分发的是DispatchQueue.global(),所以你去取key为main 的队列的value时候取不到
上个例子中把最有一个RunLoop.current.run()屏蔽
result
Hello, World!
"current thread out: <NSThread: 0x10180b7f0>{number = 1, name = main}"
Program ended with exit code: 0
线程都来不及切换进程就已经退出了
再看例子2 使用dispatchMain()
import Foundation
print("Hello, World!")
debugPrint("current thread out: \(Thread.current)") //主线程在这里呢 看我的number编号
let key = DispatchSpecificKey<String>()
let queueGlobal = DispatchQueue.global();
let globalKey = DispatchSpecificKey<String>();
DispatchQueue.main.setSpecific(key: key, value: "main")
DispatchQueue.global().setSpecific(key: globalKey, value: "global");
func log() {
debugPrint("main thread: \(Thread.isMainThread)")
debugPrint("current thread in: \(Thread.current)")
let valueglobal = DispatchQueue.global().getSpecific(key: key);
print("从globalqueue中取queue key 为 main 是否能取到 \(String(describing: valueglobal))");
let valuemain = DispatchQueue.main.getSpecific(key: key);
print("从main queue中取queue key 为 main \(String(describing: valuemain))");
let value = DispatchQueue.getSpecific(key: key)
print("\(String(describing: value))");
debugPrint("main queue: \(value != nil)")
let globalValue = DispatchQueue.getSpecific(key: globalKey);
debugPrint("global queue: \(globalValue != nil)")
}
DispatchQueue.global().async(execute: log)
dispatchMain();
result: DispatchQueue.global()的global方法被执行了,dispatchMain() 只有无法停止这个进程,只能强制退出,dispatchMain() 没有返回值,使得进程永远存在
Hello, World!
"current thread out: <NSThread: 0x102104100>{number = 1, name = main}"
"main thread: false"
"current thread in: <NSThread: 0x1022134f0>{number = 2, name = (null)}"
从globalqueue中取queue key 为 main 是否能取到 nil
从main queue中取queue key 为 main Optional("main")
nil
"main queue: false"
"global queue: true"
- dispatchMain() 官方解释是 Executes blocks submitted to the main queue. 字面翻译就是 "把main queue 的代码块都执行了"
思考:dispatchMain() 为什么把 DispatchQueue.global() 也执行了呢?
巴哥的猜想是 DispatchQueue.global().async async的代码块一定是在主队列中
总结 iOS主线程和主队列的区别
- 主队列到底是不是在主线程中执行,巴哥的解释是 一定会在主线程执行,关键是你有没有把它分发出去,只要你分发就一定是在主线程中执行。DispatchQueue.main.async 系统默认会把队列派发到主线程中,这篇文章的例子中派发不是main queue 而是 global queue ,这两者是有区别的
小结
- 进程是拥有资源的最小单位。线程是执行的最小单位。
- 线程之间是平等的,主线程只不过是 number = 1的线程,应用程序的主线程,也只不过是在number = 1的线程上启动了Runloop,让number = 1的线程常驻,使得进程不会被销毁
- 主线程是系统帮我们创建的,而子线程是需要依赖主线程来创建
- 主队列一定是在主线程中执行的
- 队列之间也平等的,系统默认会帮助我们分配两个队列dispatchMain()和DispatchQueue.global()
- 线程和队列没有可比性,两者是完全不同的两个概念