本文主要讲述我对 iOS 开发的一些理解,希望能通过 app 从启动到退出,将一些的知识整合起来,形成一条知识链,目前涉及到的知识点有 runloop、runtime、文件存储、界面布局、离线推送、内存管理、响应链、多线程。但大部分较为浅显,我尽量写自己的理解,专业性的代码尽量贴上链接,如有不当欢迎指正。
### 1、点击图标,开始上班,开启主线程,开始跑 runloop,
一个 app,启动之后为什么能一直存活并响应用户的操作,就是因为有一个主线程一直存活,并且主线程开启了一个 runloop。关于 runloop 可以粗略的理解为一个 do-while 的死循环,它一遍遍的执行着自己的任务,直到程序进程被杀死才停止。它承担了一个类似于小管家的角色,负责处理定时器的事件、监听用户的操作和处理系统的消息。
更深入一点的理解是,其实 runloop 存在着内核态和用户态两种状态,当没有事件需要处理的时候,就切换为内核态,开始休眠,当接收到事件,就切换为用户态,结束休眠。这样可以避免一般的 while 循环导致的 cpu 忙等待,形成一种“闲”等待,减小 cpu 负担。
runloop 的具体流程如下,需要注意的是:
1、Timers 代表定时器的事件
2、Source0 代表用户触摸事件、和PerformSelectors
3、Source1 代表锁屏/摇晃等系统事件
![runloop](https://upload-images.jianshu.io/upload_images/3288430-7ca2952ad7059140.png?imageMogr2/auto-orient/strip|imageView2/2/w/1240)
附上YYKit大神的博客地址:[https://blog.ibireme.com/2015/05/18/runloop/](https://blog.ibireme.com/2015/05/18/runloop/)
### 2、主线程开始绘制 UI,开启子线程去本地查询存储的信息,拿到数据后回到主线程刷新界面
这时候,一边开始绘制着欢迎页或者启动页的界面,一边去查询本地有没有存储着登录信息。注意,只能在主线程去绘制 UI,所以查询登录信息是在子线程,这样的好处的是,不阻塞主线程,减少用户的等待时间。
关于多线程的技术,可以理解为一心多用,就像我们可以一边走路一边听歌,我们的大脑在处理走路相关的操作时,也能处理听歌相关的操作。
iOS 里,每一个 app 都有一个自己的小家,我们称为沙盒机制。所有的小家初始样式都是一致的,但内部可以自行装修,增加各种目录结构。
![沙盒目录](https://upload-images.jianshu.io/upload_images/3288430-5fec4ff437cd3fe7.png?imageMogr2/auto-orient/strip|imageView2/2/w/1240)
需要注意的是,每个小家都是单独的,不能走访邻居家做客,即不能访问或修改其他 app 的数据,但可以通过分享的方式,在系统的公证下进行数据交流。部分系统级的资源是公用的,比如相册、摄像头、通讯录等,是类似阳光、空气一样的公用资源。
关于沙盒机制的具体加密原理,大家可以去看下这篇文章[这篇文章](https://blog.csdn.net/youshaoduo/article/details/66478551),嗯,我没看懂。
iOS 里操作本地数据一般有几种方式,按照数据类型可以进行分类,常用的一些零碎信息用偏好设置,大批量的重复数据比如通讯录数据使用数据库,iOS 的数据库是 sqlite,文件类数据直接使用文件写入/读取就可以。
界面绘制的话,是以左上角为坐标原点向右向下延伸的,一个正常的UI控件绘制,一般需要四个元素才能确定坐标,即 X 坐标(横向),Y 坐标(纵向),宽度,高度。也可以使用控件彼此依赖的约束,使用参照原则,原理就是根据设置参照条件获取坐标,比如,控件 A 的宽度等于屏幕宽,控件B的宽度等于控件 A 的宽度,那么就可以计算出控件 B 的宽度为屏幕宽。这种方案总的来说,在性能上是比不上直接设置坐标值,但在屏幕适配上占据很多优势,而且在自动计算控件高度上很轻松,减少了人工计算的步骤,也是目前主流的方案。
![坐标](https://upload-images.jianshu.io/upload_images/3288430-25347d04c8889436.png?imageMogr2/auto-orient/strip|imageView2/2/w/1240)
### 3、接收到用户输入、点击、滑动等操作,开始处理,
用户的这些操作,是 runloop 捕捉到的,然后根据响应链,一步步的寻找到响应的控件,执行对应的方法。所谓响应链,就是从屏幕开始,倒序遍历所有子控件,如果点击的坐标在该控件的范围内,就返回当前控件,并且继续遍历该控件的子控件,直到找到最小的那个子控件。
这个控件,如果有对应的方法设置,比如点击方法、手势等,那就根据方法,去寻找方法的执行者,寻找的规则是这样,先在当前类的方法列表里寻找,没有就去父类的,再没有就去父类的父类,一直到基类,如果基类也没有,就进入消息转发的流程。
这个消息转发流程的意思就是,系统对开发者说,我一层层的检查完了,都没有响应这个方法的对象,最后给你个机会,要么搞个方法来接替原来的方法,要么就给我个对象去响应这个方法。最后如果走完消息转发流程,还是没法处理,那就崩溃吧。专业的消息转发解析请看[这里](https://www.jianshu.com/p/9263720cbd91)。再深层次的到汇编的解析请看萧玉大神的[这篇文章](http://yulingtianxia.com/blog/2016/06/15/Objective-C-Message-Sending-and-Forwarding/)。
### 4、开始网络请求,等待返回的数据,拿到数据后返回主线程刷新界面,
网络请求一般有 get 和 post 两种请求方式,一般 get 用于不穿参数的拉取数据,如果需要传参数给后台一般使用 post。原因是 get 传参数需要拼接在请求的链接后面,数据的安全得不到保障,而且参数的长度是有限制的,无法传递大量的信息。
关于 http 和 https 大家可以去看[这篇文章](https://juejin.im/post/5dd50eba6fb9a05a6313ebba)。
### 5、重复3、4,间歇性休息放松,自动清理掉不使用的对象,释放内存
iOS 中每个 OC对象,都会有一个 retainCount(引用计数)属性,当对象创建的时候,引用计数都是 1,当其他对象持有这个对象的时候,引用计数加 1,不再持有的时候减 1,对象不再使用的时候再减 1,如果引用计数变为 0,那么这个对象就会立即会被系统回收掉。
后来系统提供了自动释放池的相关接口,原理就是在把对象加入到一个池子里,每次 runloop 将要结束的时候,系统会让这个池子里的所有对象引用计数减 1,如果减 1 后的引用计数为 0,那么就立即回收该对象。注意这里并不存在引用计数变为负数的情况,如果池子里的某个对象在收到系统发送过来的消息之前引用计数已经变为 0,那么该对象立刻就被销毁了,当系统消息到来的时候,该对象已经不存在,即内存地址为空,而向一个空对象做任何操作都是没有反应也没有影响的。
runloop 内部有一个自动释放池,当 runloop 开启时,就会自动创建一个自动释放池,当 runloop 在休息之前会释放掉自动释放池的东西,然后重新创建一个新的空的自动释放池,当 runloop 被唤醒重新开始跑圈时,Timer、Source 等新的事件就会放到新的自动释放池中,当 runloop 退出的时候也会被释放。
说道内存相关,自然就有内存泄漏的相关处理,我之前总结了大部分内存泄漏的情况,具体请看[iOS开发系列之内存泄漏分析(上)](https://www.jianshu.com/p/1b06751130c8)和 [iOS开发系列之内存泄漏分析(下)](https://www.jianshu.com/p/d0008f28053f)。
### 6、午休,进入后台,作为一个进程存在挂起,runloop 休眠,
当用户按下 home 键后,一般 10s 左右 app 就进入了挂起状态,挂起状态的 app 是没有任何活动的。特殊的比如播放音乐、使用定位相关服务、使用 voip(音视频点对点)服务,是可以向系统申请让 app 一直在后台运行的。所以有的公司是采用播放一个无声的音乐来让 app 保持活跃状态,需要注意的是,这种方法会导致上架被拒。
### 7、接收到推送,提醒用户点开 app,休息结束,继续工作,runloop 模式转变,
iOS 的离线推送原理是,由后台对接苹果服务器,app 每次启动后上传自己的设备 token 给后台,当发送需要推送的消息时,由后台找到对应的设备 token,用苹果规定的格式,将消息和 token 一起发给苹果负责推送的服务器 APNs,然后由苹果服务器找到该设备,在系统的层面展示这条推送消息。包括后台 JAVA 代码的全套流程专业解析请看[这里](https://www.cnblogs.com/taintain1984/p/3716642.html)。
另外还有一种推送机制叫做 pushkit,pushkit 区别与普通 APNs 的地方是,它不会弹出通知,而是直接唤醒你的 app,进入回调,也就是说,可以在用户没点击 app 启动的情况下,就运行我们自己写的代码,这样的可操作性就大多了,但由于这种推送会在用户不知情的情况就启动 app,苹果的审核也较为严格。
### 8、
杀掉进程,主线程停止,下班休息。
其实还想加更多的东西进去,不过目前也没好的思路,欢迎评论。
欢迎大家来[我的小窝](https://zmfflying.github.io/)做客啊,里面记录下了我进步的点点滴滴,一切逆境只是前进的理由,与君共勉。