├── README.md ├── docs ├── 00、目录.md ├── 01-01、计算机网络 L1.md ├── 01-02、HTTP和HTTPS协议 L2.md ├── 01-03、TCP 和 UDP 协议 L3.md ├── 01-04、设计模式 L4.md ├── 01-05、数据结构和算法(空).md ├── 01-06、编译链接启动 L5.md ├── 01-07、数据加密 L6.md ├── 02-01、KVC与KVO L7.md ├── 02-02、分类与关联对象 L8.md ├── 02-03、block L9.md ├── 02-04、多线程 L10.md ├── 02-05、内存管理 L11.md ├── 02-06、Runtime L12.md ├── 02-07、Runloop L13.md ├── 03-01、UIKit L14.md ├── 03-02、事件处理 L15.md ├── 03-03、动画 L16.md ├── 03-04、网络编程(空).md ├── 03-05、OC与WebView交互(空) L17.md ├── 04-01、性能优化 L18.md ├── 04-02、组件化 L19.md ├── 04-03、音视频技术(空) L20.md ├── 04-04、线上卡顿检测 L21.md ├── 04-05、无埋点技术(空) L22.md └── 05-01、源码解读(空) L23.md └── res ├── 001.jpg ├── 002.jpg ├── 003.png ├── 004.png ├── 005.png ├── 006.png ├── 007.png ├── 008.png ├── 009.png ├── 010.png ├── 011.png ├── 012.png ├── 013.png ├── 014.png ├── 015.png ├── 016.png ├── 017.png └── 018.jpg /README.md: -------------------------------------------------------------------------------- 1 | # iOS面试总结 2 | ## 关于笔记 3 | ### 前言 4 | 该笔记本来是发在公众号【iOS成长之路】的读者群里,初衷是希望大家帮我勘误一下,后来机缘巧合之下被群主放在公众号周报内容中。当时发的有道云笔记连接,不太方便后期的维护,现在把他放在github上,也方便大家给我提反馈。 5 | 6 | iOS 成长之路的读者群,目前是我加的群里少有的还在讨论技术的 iOS 群,大家有兴趣的话可以通过群主的公众号加入。 7 | 8 | ### 为何整理笔记 9 | 开始是准备整理一份面试题的,但是后来发现面试题太杂太多了,整理起来有些摸不着头脑,所以后来就按照 iOS 的知识框架来整理的。 10 | 11 | ### 整理自己的笔记 12 | 我建议大家都来自己整理一份笔记出来。 13 | 14 | 整理笔记的过程,其实是对自己掌握知识点的一个查漏补缺。学习的正确方式应该是,先搭建起框架,然后再往里面填充知识点,这样才能由点到线、再由线到面。我从年初开始想着复习iOS知识,但是前期只是零碎的复习一些知识点,效果一直不太好,而在12月整理这个笔记的过程中,我感觉自己的知识好像串起来了,收获很大,现在不管是什么知识点我大概都知道该放在哪一块。 15 | 16 | 其次,整理笔记的过程中,其实是可以加深自己对知识点的理解的。《如何学习》这本书里提到,人们对知识的记忆能力是超乎大家想象的,很多时候你之所以觉得没有记住,是因为你没有掌握提取知识点的方法,而在总结的过程中,我们用自己的语言组织信息,其实就可以加强你提取记忆的熟练程度。 17 | 18 | ### 笔记的质量 19 | 因为本来是准备整理给自己面试用的,所以质量上肯定是有一定把关的。但是,受限于我个人的能力,可能会对知识点的理解存在一定的偏差。希望大家遇到错误的地方,可以帮我指出,然后共同的把这份总结维护好。 20 | 21 | ## 目录 22 | ### 基础知识 23 | - [01、计算机基础](https://github.com/pengwj/iOSInterview/blob/master/docs/01-01%E3%80%81%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%20L1.md) 24 | - [02、HTTP和HTTPS协议](https://github.com/pengwj/iOSInterview/blob/master/docs/01-02%E3%80%81HTTP%E5%92%8CHTTPS%E5%8D%8F%E8%AE%AE%20L2.md) 25 | - [03、TCP/UDP](https://github.com/pengwj/iOSInterview/blob/master/docs/01-03%E3%80%81TCP%20%E5%92%8C%20UDP%20%E5%8D%8F%E8%AE%AE%20L3.md) 26 | - [04、设计模式](https://github.com/pengwj/iOSInterview/blob/master/docs/01-04%E3%80%81%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%20L4.md) 27 | - [05、数据结构和算法(空)](https://github.com/pengwj/iOSInterview/blob/master/docs/01-05%E3%80%81%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%92%8C%E7%AE%97%E6%B3%95%EF%BC%88%E7%A9%BA%EF%BC%89.md) 28 | - [06、编译连接启动](https://github.com/pengwj/iOSInterview/blob/master/docs/01-06%E3%80%81%E7%BC%96%E8%AF%91%E9%93%BE%E6%8E%A5%E5%90%AF%E5%8A%A8%20L5.md) 29 | - [07、数据加密](https://github.com/pengwj/iOSInterview/blob/master/docs/01-07%E3%80%81%E6%95%B0%E6%8D%AE%E5%8A%A0%E5%AF%86%20L6.md) 30 | 31 | ### iOS知识 32 | - [01、KVC与KVO](https://github.com/pengwj/iOSInterview/blob/master/docs/02-01%E3%80%81KVC%E4%B8%8EKVO%20L7.md) 33 | - [02、分类与关联对象](https://github.com/pengwj/iOSInterview/blob/master/docs/02-02%E3%80%81%E5%88%86%E7%B1%BB%E4%B8%8E%E5%85%B3%E8%81%94%E5%AF%B9%E8%B1%A1%20L8.md) 34 | - [03、block](https://github.com/pengwj/iOSInterview/blob/master/docs/02-03%E3%80%81block%20L9.md) 35 | - [04、多线程](https://github.com/pengwj/iOSInterview/blob/master/docs/02-04%E3%80%81%E5%A4%9A%E7%BA%BF%E7%A8%8B%20L10.md) 36 | - [05、内存管理](https://github.com/pengwj/iOSInterview/blob/master/docs/02-05%E3%80%81%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86%20L11.md) 37 | - [06、Runtime](https://github.com/pengwj/iOSInterview/blob/master/docs/02-06%E3%80%81Runtime%20L12.md) 38 | - [07、Runloop](https://github.com/pengwj/iOSInterview/blob/master/docs/02-07%E3%80%81Runloop%20L13.md) 39 | 40 | ### Cocoa 41 | - [01、UIKit](https://github.com/pengwj/iOSInterview/blob/master/docs/03-01%E3%80%81UIKit%20L14.md) 42 | - [02、事件处理](https://github.com/pengwj/iOSInterview/blob/master/docs/03-02%E3%80%81%E4%BA%8B%E4%BB%B6%E5%A4%84%E7%90%86%20L15.md) 43 | - [03、动画](https://github.com/pengwj/iOSInterview/blob/master/docs/03-03%E3%80%81%E5%8A%A8%E7%94%BB%20L16.md) 44 | - [04、网络编程(空)]() 45 | - [05、OC与WebView交互(空)]() 46 | 47 | ### iOS进阶 48 | - [01、性能优化](https://github.com/pengwj/iOSInterview/blob/master/docs/04-01%E3%80%81%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96%20L18.md) 49 | - [02、组件化](https://github.com/pengwj/iOSInterview/blob/master/docs/04-02%E3%80%81%E7%BB%84%E4%BB%B6%E5%8C%96%20L19.md) 50 | - [03、音视频技术(空)]() 51 | - [04、线上卡顿监控](https://github.com/pengwj/iOSInterview/blob/master/docs/04-04%E3%80%81%E7%BA%BF%E4%B8%8A%E5%8D%A1%E9%A1%BF%E6%A3%80%E6%B5%8B%20%20L21.md) 52 | - [05、无埋点技术(空)]() 53 | 54 | ### 源码 55 | - [01、源码解读(空)]() 56 | 57 | ### 资料 58 | - https://hit-alibaba.github.io/interview/ 59 | - https://zsisme.gitbooks.io/ios-/content/chapter6/catiledLayer.html 60 | - http://www.cyc2018.xyz 61 | - https://iosgua.com/interval/docs/ch09/tc01/9-1-1/ 62 | - [iOS底层探索汇总](https://blog.csdn.net/LiqunZhang/article/details/109119082) -------------------------------------------------------------------------------- /docs/00、目录.md: -------------------------------------------------------------------------------- 1 | ### 基础知识 2 | - 01、计算机基础 3 | - 02、HTTP和HTTPS协议 4 | - 03、TCP/UDP 5 | - 04、设计模式 6 | - 05、数据结构和算法(空) 7 | - 06、编译连接启动(空) 8 | - 07、数据加密(空) 9 | 10 | ### iOS知识 11 | - 01、KVC与KVO 12 | - 02、分类与关联对象 13 | - 03、block 14 | - 04、多线程 15 | - 05、内存管理 16 | - 06、Runtime 17 | - 07、Runloop 18 | 19 | ### Cocoa 20 | - 01、UIKit 21 | - 02、事件处理 22 | - 03、动画 23 | - 04、网络编程(空) 24 | - 05、OC与WebView交互(空) 25 | 26 | ### iOS进阶 27 | - 01、性能优化 28 | - 02、组件化(空) 29 | - 03、音视频技术(空) 30 | - 04、线上卡顿监控(空) 31 | - 05、无埋点技术(空) 32 | 33 | ### 源码 34 | - 01、源码解读(空) 35 | 36 | ### 资料 37 | - https://hit-alibaba.github.io/interview/ 38 | - https://zsisme.gitbooks.io/ios-/content/chapter6/catiledLayer.html 39 | - http://www.cyc2018.xyz 40 | - https://iosgua.com/interval/docs/ch09/tc01/9-1-1/ 41 | - [iOS底层探索汇总](https://blog.csdn.net/LiqunZhang/article/details/109119082) -------------------------------------------------------------------------------- /docs/01-01、计算机网络 L1.md: -------------------------------------------------------------------------------- 1 | [40 张图带你搞懂 TCP 和 UDP](https://mp.weixin.qq.com/s/wxzJrSLGDZ6tAt3ys4Vp8g) 2 | 3 | [CS-Notes](http://www.cyc2018.xyz/#%E7%B3%BB%E7%BB%9F%E8%AE%BE%E8%AE%A1) 4 | 5 | [TOC] 6 | 7 | # 网络七层协议(OSI七层模型) 8 | - 应用层 9 | - 10 | 11 | ## 协议详解 12 | - 应用层 13 | 1. 用户接口、应用程序 14 | 2. Application典型设备:网关 15 | 3. 典型协议、标准和应用:TELNET、FTP、HTTP 16 | - 表示层 17 | 1. 数据表示、压缩和加密 18 | 2. 典型设备:网关 19 | 3. 典型协议、标准和应用:ASCLL、PICT、TIFF、JPEG|MPEG 20 | 4. 表示层相当于一个东西的表示,表示的一些协议,比如图片、声音和视频MPEG 21 | - 会话层 22 | 1. 会话的建立和结束 23 | 2. 典型设备:网关 24 | 3. 典型协议、标准和应用:RPC、SQL、NFS、X WINDOWS、ASP 25 | - 传输层 26 | 1. 主要功能:端到端控制Transport 27 | 2. 典型设备:网关 28 | 3. 典型协议、标准和应用:TCP、UDP、SPX 29 | - 网络层 30 | 1. 主要功能:路由、寻址Network 31 | 2. 典型设备:路由器 32 | 3. 典型协议、标准和应用:IP、IPX、APPLETALK、ICMP 33 | - 数据链路层 34 | 1. 主要功能:保证无差错的疏忽链路的data link 35 | 2. 典型设备:交换机、网桥、网卡 36 | 3. 典型协议、标准和应用:802.2、802.3ATM、HDLC、FRAME RELAY 37 | - 物理层 38 | 1. 主要功能:传输比特流Physical 39 | 2. 典型设备:集线器、中继器 40 | 3. 典型协议、标准和应用:V.35、EIA/TIA-232 41 | 42 | # TCP/IP四层模型 43 | - 应用层 44 | - 运输层 45 | - 网络层 46 | - 网络接口层 47 | 48 | # 原理体系结构(五层模型) 49 | - 应用层 50 | - 运输层 51 | - 网络层 52 | - 数据链路层 53 | - 物理层 54 | 55 | # 常见的应用层协议 56 | ## DNS 57 | 域名系统DNS是因特网使用的命名系统,用来把便于人们使用的机器名字转换为IP地址。 58 | ## FTP 59 | FTP是文件传输协议。FTP提供交互式的访问,允许客户指明文件类型与格式,并允许文件具有存取权限。FTP基于TCP。 60 | ## TELNET 61 | telnet是一个简单的远程终端协议,它也是因特网的正式标准。又称为终端仿真协议。 62 | ## HTTP 63 | 超文本传送协议,是面向事务的应用层协议。http使用面向连接的TCP作为运输层协议,保证了数据的可靠传输。 64 | ## SMTP 65 | 简单邮件传送协议。 66 | ## POP3 67 | 邮件读取协议,POP3(Post Office Protocol 3)协议通常被用来接收电子邮件。 68 | ## SNMP 69 | 简单网络管理协议 70 | ## MQTT 71 | 消息队列遥测传输协议 72 | 73 | # Cookie和Session 74 | ## cookie 75 | - 1、用户和服务器的交互 76 | cookie主要是用来记录用户状态,区分用户,状态保存在客户端。cookie功能需要浏览器的支持。如果浏览器不支持cookie(如大部分手机中的浏览器)或者把cookie禁用了,cookie功能就会失效。 77 | ![image](https://qn.nobady.cn/iOS/cookie.png) 78 | 1. 首次访问amazon时,客户端发送一个HTTP请求到服务器端。服务器端发送一个HTTP响应到客户端,其中包含Set-Cookie头部 79 | 2. 客户端发送一个HTTP请求到服务器端,其中包含Cookie头部。服务器端发送一个HTTP响应到客户端 80 | 3. 隔段时间再去访问时,客户端会直接发包含Cookie头部的HTTP请求。服务器端发送一个HTTP响应到客户端。 81 | 82 | - 2、cookie的修改和删除 83 | - 在修改cookie的时候,只需要新cookie覆盖旧cookie即可,在覆盖的时候,由于cookie具有不可跨域名性,注意name、path、domain需与原cookie一致 84 | - 删除cookie也一样,设置cookie的过期时间expires为过去的一个时间点,或者maxAge=0(有效期)即可 85 | 86 | - 3、cookie的安全 87 | - 事实上,cookie的使用存在争议,因为它被认为是对用户隐私的一种侵害,而且cookie并不安全 88 | - HTTP协议不仅是无状态的,而且是不安全的。使用HTTP协议的数据不经过任何加密就直接在网络上传播,有被截获的可能。使用HTTP协议传输很机密的内容是一种隐患。 89 | 1. 如果不希望Cookie在HTTP等非安全协议中传输,可以设置Cookie的secure属性为true。浏览器只会在HTTPS和SSL等安全协议中传输此类Cookie。 90 | 2. 此外,secure属性并不能对Cookie内容加密,因而不能保证绝对的安全性。如果需要高安全性,需要在程序中对Cookie内容加密、解密,以防泄密。 91 | 3. 也可以设置cookie为HttpOnly,如果在cookie中设置了HttpOnly属性,那么通过js脚本将无法读取到cookie信息,这样能有效的防止XSS(跨站脚本攻击)攻击 92 | 93 | ## Session 94 | - Session是服务器端使用的一种记录客户端状态的机制,使用上比Cookie简单一些,相应的也增加了服务器的存储压力。 95 | - Session是另一种记录客户状态的机制,不同的是Cookie保存在客户端浏览器中,而Session保存在服务器上。 客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。这就是Session。客户端浏览器再次访问时只需要从该Session中查找该客户的状态就可以了。 96 | ![image](https://qn.nobady.cn/iOS/session.png) 97 | - 如图: 98 | - 当程序需要为某个客户端的请求创建一个session时,服务器首先检查这个客户端的请求里是否已包含了一个session标识(称为SessionId) 99 | - 如果已包含则说明以前已经为此客户端创建过session,服务器就按照SessionId把这个session检索出来,使用(检索不到,会新建一个) 100 | - 如果客户端请求不包含SessionId,则为此客户端创建一个session并且生成一个与此session相关联的SessionId,SessionId的值应该是一个既不会重复,又不容易被找到规律以仿造的字符串,这个SessionId将被在本次响应中返回给客户端保存。 101 | - 保存这个SessionId的方式可以采用cookie,这样在交互过程中浏览器可以自动的按照规则把这个标识发送给服务器。但cookie可以被人为的禁止,则必须有其他机制以便在cookie被禁止时仍然能够把SessionId传递回服务器。 102 | 103 | ## Cookie 和Session 的区别: 104 | - 1、cookie数据存放在客户的浏览器上,session数据放在服务器上。 105 | - 2、cookie相比session不是很安全,别人可以分析存放在本地的cookie并进行cookie欺骗,考虑到安全应当使用session。 106 | - 3、session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,考虑到减轻服务器性能方面,应当使用cookie。 107 | - 4、单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。而session存储在服务端,可以无限量存储 108 | - 5、所以:将登录信息等重要信息存放为session;其他信息如果需要保留,可以放在cookie中 109 | 110 | # DNS是什么 111 | 因特网上的主机,可以使用多种方式标识,比如主机名或IP地址。一种标识方法就是用它的主机名(hostname),比如·www.baidu.com、www.google.com、gaia.cs.umass.edu等。这方式方便人们记忆和接受,但是这种长度不一、没有规律的字符串路由器并不方便处理。还有一种方式,就是直接使用定长的、有着清晰层次结构的IP地址,路由器比较热衷于这种方式。为了折衷这两种方式,我们需要一种能进行主机名到IP地址转换的目录服务。这就是域名系统(Domain Name System,DNS)的主要任务。 112 | 113 | - DNS是: 114 | - 1、一个由分层的DNS服务器实现的分布式数据库 115 | - 2、一个使得主机能够查询分布式数据库的应用层协议 116 | 117 | - DNS服务器通常是运行BIND软件的UNIX机器,DNS协议运行在UDP上,使用53号端口 118 | - DNS通常是由其他应用层协议所使用的,包括HTTP、SMTP等。其作用则是:将用户提供的主机名解析为IP地址 119 | - DNS的一种简单设计就是在因特网上只使用一个DNS服务器,该服务器包含所有的映射。很明显这种设计是有很大的问题的: 120 | - 单点故障:如果该DNS服务器崩溃,全世界的网络随之瘫痪 121 | - 通信容量:单个DNS服务器必须处理所有DNS查询 122 | - 远距离的集中式数据库:单个DNS服务器必须面对所有用户,距离过远会有严重的时延。 123 | - 维护:该数据库过于庞大,还需要对新添加的主机频繁更新。 124 | - 所以,DNS被设计成了一个分布式、层次数据库 125 | 126 | # DNS解析过程 127 | 以www.163.com为例: 128 | - 客户端打开浏览器,输入一个域名。比如输入www.163.com,这时,客户端会发出一个DNS请求到本地DNS服务器。本地DNS服务器一般都是你的网络接入服务器商提供,比如中国电信,中国移动。 129 | - 查询www.163.com的DNS请求到达本地DNS服务器之后,本地DNS服务器会首先查询它的缓存记录,如果缓存中有此条记录,就可以直接返回结果。如果没有,本地DNS服务器还要向DNS根服务器进行查询。 130 | - 根DNS服务器没有记录具体的域名和IP地址的对应关系,而是告诉本地DNS服务器,你可以到域服务器上去继续查询,并给出域服务器的地址。 131 | - 本地DNS服务器继续向域服务器发出请求,在这个例子中,请求的对象是.com域服务器。.com域服务器收到请求之后,也不会直接返回域名和IP地址的对应关系,而是告诉本地DNS服务器,你的域名的解析服务器的地址。 132 | - 最后,本地DNS服务器向域名的解析服务器发出请求,这时就能收到一个域名和IP地址对应关系,本地DNS服务器不仅要把IP地址返回给用户电脑,还要把这个对应关系保存在缓存中,以备下次别的用户查询时,可以直接返回结果,加快网络访问。 133 | 134 | # DNS和HTTPDNS 135 | https://www.cnblogs.com/jimmyhe/p/11279762.html 136 | 137 | # 资料 138 | [http协议各版本差异](https://blog.51cto.com/13932385/2393194?source=dra) -------------------------------------------------------------------------------- /docs/01-02、HTTP和HTTPS协议 L2.md: -------------------------------------------------------------------------------- 1 | - 转载自 https://hit-alibaba.github.io/interview/basic/network/HTTP.html 2 | 3 | [TOC] 4 | 5 | # HTTP 的特性 6 | - HTTP 协议构建于 TCP/IP 协议之上,是一个应用层协议,默认端口号是 80 7 | - HTTP 是无连接无状态的 8 | 9 | ## HTTP版本 10 | ### HTTP/0.9 11 | - HTTP协议的最初版本,功能简陋,仅支持请求方式GET,并且仅能请求访问HTML格式的资源。 12 | 13 | ### HTTP/1.0 14 | - 请求行必须在尾部添加协议版本字段(http/1.0);必须包含头消息 15 | - 增加了请求方式POST和HEAD 16 | - 根据Content-Type可以支持多种数据格式 17 | - 开始支持cache,当客户端在规定时间内访问统一网站,直接访问cache即可 18 | 19 | ### HTTP/1.1 20 | - 引入了持久连接(persistent connection),即TCP连接默认不关闭,可以被多个请求复用,不用声明Connection: keep-alive 21 | - 加入了管道机制,在同一个TCP连接里,允许多个请求同时发送,增加了并发性,进一步改善了HTTP协议的效率 22 | - 新增了请求方式PUT、PATCH、OPTIONS、DELETE等 23 | - 支持文件断点续传,RANGE:bytes 24 | 25 | ### HTTP/2.0 26 | - 增加双工模式,即不仅客户端能够同时发送多个请求,服务端也能同时处理多个请求,解决了队头堵塞的问题 27 | - 增加服务器推送的功能,即不经请求服务端主动向客户端发送数据 28 | 29 | # HTTP 报文 30 | ## 请求报文 31 | HTTP 协议是以 ASCII 码传输,建立在 TCP/IP 协议之上的应用层规范。规范把 HTTP 请求分为三个部分:状态行、请求头、消息主体。类似于下面这样: 32 | ``` 33 | 34 | 35 | 36 | 37 | ``` 38 | 39 | HTTP 定义了与服务器交互的不同方法,最基本的方法有4种,分别是GET,POST,PUT,DELETE。URL全称是资源描述符,我们可以这样认为:一个URL地址,它用于描述一个网络上的资源,而 HTTP 中的GET,POST,PUT,DELETE就对应着对这个资源的查,增,改,删4个操作。 40 | 41 | 1. GET 用于信息获取,而且应该是安全的 和 幂等的。 42 | 43 | 所谓安全的意味着该操作用于获取信息而非修改信息。换句话说,GET 请求一般不应产生副作用。就是说,它仅仅是获取资源信息,就像数据库查询一样,不会修改,增加数据,不会影响资源的状态。 44 | 45 | 幂等的意味着对同一 URL 的多个请求应该返回同样的结果。 46 | 47 | GET 请求报文示例: 48 | ``` 49 | GET /books/?sex=man&name=Professional HTTP/1.1 50 | Host: www.example.com 51 | User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6) 52 | Gecko/20050225 Firefox/1.0.1 53 | Connection: Keep-Alive 54 | ``` 55 | 2. POST 表示可能修改变服务器上的资源的请求。 56 | ``` 57 | POST / HTTP/1.1 58 | Host: www.example.com 59 | User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6) 60 | Gecko/20050225 Firefox/1.0.1 61 | Content-Type: application/x-www-form-urlencoded 62 | Content-Length: 40 63 | Connection: Keep-Alive 64 | 65 | sex=man&name=Professional 66 | ``` 67 | 3. 注意: 68 | - GET 可提交的数据量受到URL长度的限制,HTTP 协议规范没有对 URL 长度进行限制。这个限制是特定的浏览器及服务器对它的限制 69 | - 理论上讲,POST 是没有大小限制的,HTTP 协议规范也没有进行大小限制,出于安全考虑,服务器软件在实现时会做一定限制 70 | - 参考上面的报文示例,可以发现 GET 和 POST 数据内容是一模一样的,只是位置不同,一个在 URL 里,一个在 HTTP 包的包体里 71 | 72 | ## POST 提交数据的方式 73 | HTTP 协议中规定 POST 提交的数据必须在 body 部分中,但是协议中没有规定数据使用哪种编码方式或者数据格式。实际上,开发者完全可以自己决定消息主体的格式,只要最后发送的 HTTP 请求满足上面的格式就可以。 74 | 75 | 但是,数据发送出去,还要服务端解析成功才有意义。一般服务端语言如 PHP、Python 等,以及它们的 framework,都内置了自动解析常见数据格式的功能。服务端通常是根据请求头(headers)中的 Content-Type 字段来获知请求中的消息主体是用何种方式编码,再对主体进行解析。所以说到 POST 提交数据方案,包含了 Content-Type 和消息主体编码方式两部分。下面就正式开始介绍它们: 76 | 77 | - application/x-www-form-urlencoded 78 | 79 | 这是最常见的 POST 数据提交方式。浏览器的原生
表单,如果不设置 enctype 属性,那么最终就会以 application/x-www-form-urlencoded 方式提交数据。上个小节当中的例子便是使用了这种提交方式。可以看到 body 当中的内容和 GET 请求是完全相同的。 80 | 81 | - multipart/form-data 82 | 83 | 这又是一个常见的 POST 数据提交的方式。我们使用表单上传文件时,必须让 表单的 enctype 等于 multipart/form-data。直接来看一个请求示例: 84 | 85 | ``` 86 | POST http://www.example.com HTTP/1.1 87 | Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryrGKCBY7qhFd3TrwA 88 | 89 | ------WebKitFormBoundaryrGKCBY7qhFd3TrwA 90 | Content-Disposition: form-data; name="text" 91 | 92 | title 93 | ------WebKitFormBoundaryrGKCBY7qhFd3TrwA 94 | Content-Disposition: form-data; name="file"; filename="chrome.png" 95 | Content-Type: image/png 96 | 97 | PNG ... content of chrome.png ... 98 | ------WebKitFormBoundaryrGKCBY7qhFd3TrwA-- 99 | ``` 100 | 101 | 这个例子稍微复杂点。首先生成了一个 boundary 用于分割不同的字段,为了避免与正文内容重复,boundary 很长很复杂。然后 Content-Type 里指明了数据是以 multipart/form-data 来编码,本次请求的 boundary 是什么内容。消息主体里按照字段个数又分为多个结构类似的部分,每部分都是以 --boundary 开始,紧接着是内容描述信息,然后是回车,最后是字段具体内容(文本或二进制)。如果传输的是文件,还要包含文件名和文件类型信息。消息主体最后以 --boundary-- 标示结束。关于 multipart/form-data 的详细定义,请前往 RFC1867 查看(或者相对友好一点的 MDN 文档)。 102 | 103 | 这种方式一般用来上传文件,各大服务端语言对它也有着良好的支持。 104 | 105 | 上面提到的这两种 POST 数据的方式,都是浏览器原生支持的,而且现阶段标准中原生 表单也只支持这两种方式(通过 元素的 enctype 属性指定,默认为 application/x-www-form-urlencoded。其实 enctype 还支持 text/plain,不过用得非常少)。 106 | 107 | 随着越来越多的 Web 站点,尤其是 WebApp,全部使用 Ajax 进行数据交互之后,我们完全可以定义新的数据提交方式,例如 application/json,text/xml,乃至 application/x-protobuf 这种二进制格式,只要服务器可以根据 Content-Type 和 Content-Encoding 正确地解析出请求,都是没有问题的。 108 | 109 | ## 响应报文 110 | HTTP 响应与 HTTP 请求相似,HTTP响应也由3个部分构成,分别是: 111 | 112 | - 状态行 113 | - 响应头(Response Header) 114 | - 响应正文 115 | 116 | 状态行由协议版本、数字形式的状态代码、及相应的状态描述,各元素之间以空格分隔。 117 | 118 | 常见的状态码有如下几种: 119 | - 200 OK 客户端请求成功 120 | - 301 Moved Permanently 请求永久重定向 121 | - 302 Moved Temporarily 请求临时重定向 122 | - 304 Not Modified 文件未修改,可以直接使用缓存的文件。 123 | - 400 Bad Request 由于客户端请求有语法错误,不能被服务器所理解。 124 | - 401 Unauthorized 请求未经授权。这个状态代码必须和WWW-Authenticate报头域一起使用 125 | - 403 Forbidden 服务器收到请求,但是拒绝提供服务。服务器通常会在响应正文中给出不提供服务的原因 126 | - 404 Not Found 请求的资源不存在,例如,输入了错误的URL 127 | - 500 Internal Server Error 服务器发生不可预期的错误,导致无法完成客户端的请求。 128 | - 503 Service Unavailable 服务器当前不能够处理客户端的请求,在一段时间之后,服务器可能会恢复正常。 129 | 130 | 下面是一个HTTP响应的例子: 131 | ``` 132 | HTTP/1.1 200 OK 133 | 134 | Server:Apache Tomcat/5.0.12 135 | Date:Mon,6Oct2003 13:23:42 GMT 136 | Content-Length:112 137 | 138 | ... 139 | ``` 140 | 141 | ## 条件 GET 142 | HTTP 条件 GET 是 HTTP 协议为了减少不必要的带宽浪费,提出的一种方案。详见 [RFC2616](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html)。 143 | 144 | 1. HTTP 条件 GET 使用的时机? 145 | 146 | 客户端之前已经访问过某网站,并打算再次访问该网站。 147 | 148 | 2. HTTP 条件 GET 使用的方法? 149 | 150 | 客户端向服务器发送一个包询问是否在上一次访问网站的时间后是否更改了页面,如果服务器没有更新,显然不需要把整个网页传给客户端,客户端只要使用本地缓存即可,如果服务器对照客户端给出的时间已经更新了客户端请求的网页,则发送这个更新了的网页给用户。 151 | 152 | 下面是一个具体的发送接受报文示例: 153 | 154 | 客户端发送请求: 155 | ``` 156 | GET / HTTP/1.1 157 | Host: www.sina.com.cn:80 158 | If-Modified-Since:Thu, 4 Feb 2010 20:39:13 GMT 159 | Connection: Close 160 | ``` 161 | 162 | 第一次请求时,服务器端返回请求数据,之后的请求,服务器根据请求中的 If-Modified-Since 字段判断响应文件没有更新,如果没有更新,服务器返回一个 304 Not Modified响应,告诉浏览器请求的资源在浏览器上没有更新,可以使用已缓存的上次获取的文件。 163 | 164 | ``` 165 | HTTP/1.0 304 Not Modified 166 | Date: Thu, 04 Feb 2010 12:38:41 GMT 167 | Content-Type: text/html 168 | Expires: Thu, 04 Feb 2010 12:39:41 GMT 169 | Last-Modified: Thu, 04 Feb 2010 12:29:04 GMT 170 | Age: 28 171 | X-Cache: HIT from sy32-21.sina.com.cn 172 | Connection: close 173 | ``` 174 | 如果服务器端资源已经更新的话,就返回正常的响应。 175 | 176 | 177 | ## 持久连接 178 | 我们知道 HTTP 协议采用“请求-应答”模式,当使用普通模式,即非 Keep-Alive 模式时,每个请求/应答客户和服务器都要新建一个连接,完成之后立即断开连接(HTTP 协议为无连接的协议);当使用 Keep-Alive 模式(又称持久连接、连接重用)时,Keep-Alive 功能使客户端到服务器端的连接持续有效,当出现对服务器的后继请求时,Keep-Alive 功能避免了建立或者重新建立连接。 179 | 180 | 在 HTTP 1.0 版本中,并没有官方的标准来规定 Keep-Alive 如何工作,因此实际上它是被附加到 HTTP 1.0协议上,如果客户端浏览器支持 Keep-Alive ,那么就在HTTP请求头中添加一个字段 Connection: Keep-Alive,当服务器收到附带有 Connection: Keep-Alive 的请求时,它也会在响应头中添加一个同样的字段来使用 Keep-Alive 。这样一来,客户端和服务器之间的HTTP连接就会被保持,不会断开(超过 Keep-Alive 规定的时间,意外断电等情况除外),当客户端发送另外一个请求时,就使用这条已经建立的连接。 181 | 182 | 在 HTTP 1.1 版本中,默认情况下所有连接都被保持,如果加入 "Connection: close" 才关闭。目前大部分浏览器都使用 HTTP 1.1 协议,也就是说默认都会发起 Keep-Alive 的连接请求了,所以是否能完成一个完整的 Keep-Alive 连接就看服务器设置情况。 183 | 184 | 由于 HTTP 1.0 没有官方的 Keep-Alive 规范,并且也已经基本被淘汰,以下讨论均是针对 HTTP 1.1 标准中的 Keep-Alive 展开的。 185 | 186 | 注意: 187 | 188 | - HTTP Keep-Alive 简单说就是保持当前的TCP连接,避免了重新建立连接。 189 | 190 | - HTTP 长连接不可能一直保持,例如 Keep-Alive: timeout=5, max=100,表示这个TCP通道可以保持5秒,max=100,表示这个长连接最多接收100次请求就断开。 191 | 192 | - HTTP 是一个无状态协议,这意味着每个请求都是独立的,Keep-Alive 没能改变这个结果。另外,Keep-Alive也不能保证客户端和服务器之间的连接一定是活跃的,在 HTTP1.1 版本中也如此。唯一能保证的就是当连接被关闭时你能得到一个通知,所以不应该让程序依赖于 Keep-Alive 的保持连接特性,否则会有意想不到的后果。 193 | 194 | - 使用长连接之后,客户端、服务端怎么知道本次传输结束呢?两部分:1. 判断传输数据是否达到了Content-Length 指示的大小;2. 动态生成的文件没有 Content-Length ,它是分块传输(chunked),这时候就要根据 chunked 编码来判断,chunked 编码的数据在最后有一个空 chunked 块,表明本次传输数据结束,详见[这里](https://www.cnblogs.com/skynet/archive/2010/12/11/1903347.html)。什么是 chunked 分块传输呢?下面我们就来介绍一下。 195 | 196 | ## Transfer-Encoding 197 | Transfer-Encoding 是一个用来标示 HTTP 报文传输格式的头部值。尽管这个取值理论上可以有很多,但是当前的 HTTP 规范里实际上只定义了一种传输取值——chunked。 198 | 199 | 如果一个HTTP消息(请求消息或应答消息)的Transfer-Encoding消息头的值为chunked,那么,消息体由数量未定的块组成,并以最后一个大小为0的块为结束。 200 | 201 | 每一个非空的块都以该块包含数据的字节数(字节数以十六进制表示)开始,跟随一个CRLF (回车及换行),然后是数据本身,最后块CRLF结束。在一些实现中,块大小和CRLF之间填充有白空格(0x20)。 202 | 203 | 最后一块是单行,由块大小(0),一些可选的填充白空格,以及CRLF。最后一块不再包含任何数据,但是可以发送可选的尾部,包括消息头字段。消息最后以CRLF结尾。 204 | 205 | 一个示例响应如下: 206 | 207 | ``` 208 | HTTP/1.1 200 OK 209 | Content-Type: text/plain 210 | Transfer-Encoding: chunked 211 | 212 | 25 213 | This is the data in the first chunk 214 | 215 | 1A 216 | and this is the second one 217 | 0 218 | ``` 219 | 220 | ### 注意事项: 221 | - chunked 和 multipart 两个名词在意义上有类似的地方,不过在 HTTP 协议当中这两个概念则不是一个类别的。multipart 是一种 Content-Type,标示 HTTP 报文内容的类型,而 chunked 是一种传输格式,标示报头将以何种方式进行传输。 222 | - chunked 传输不能事先知道内容的长度,只能靠最后的空 chunk 块来判断,因此对于下载请求来说,是没有办法实现进度的。在浏览器和下载工具中,偶尔我们也会看到有些文件是看不到下载进度的,即采用 chunked 方式进行下载。 223 | - chunked 的优势在于,服务器端可以边生成内容边发送,无需事先生成全部的内容。HTTP/2 不支持 Transfer-Encoding: chunked,因为 HTTP/2 有自己的 streaming 传输方式(Source:[MDN - Transfer-Encoding](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Transfer-Encoding))。 224 | 225 | ## HTTP Pipelining(HTTP 管线化) 226 | 默认情况下 HTTP 协议中每个传输层连接只能承载一个 HTTP 请求和响应,浏览器会在收到上一个请求的响应之后,再发送下一个请求。在使用持久连接的情况下,某个连接上消息的传递类似于请求1 -> 响应1 -> 请求2 -> 响应2 -> 请求3 -> 响应3。 227 | 228 | HTTP Pipelining(管线化)是将多个 HTTP 请求整批提交的技术,在传送过程中不需等待服务端的回应。使用 HTTP Pipelining 技术之后,某个连接上的消息变成了类似这样请求1 -> 请求2 -> 请求3 -> 响应1 -> 响应2 -> 响应3。 229 | 230 | 注意下面几点: 231 | - 管线化机制通过持久连接(persistent connection)完成,仅 HTTP/1.1 支持此技术(HTTP/1.0不支持) 232 | - 只有 GET 和 HEAD 请求可以进行管线化,而 POST 则有所限制 233 | - 初次创建连接时不应启动管线机制,因为对方(服务器)不一定支持 HTTP/1.1 版本的协议 234 | - 管线化不会影响响应到来的顺序,如上面的例子所示,响应返回的顺序并未改变 235 | - HTTP /1.1 要求服务器端支持管线化,但并不要求服务器端也对响应进行管线化处理,只是要求对于管线化的请求不失败即可 236 | - 由于上面提到的服务器端问题,开启管线化很可能并不会带来大幅度的性能提升,而且很多服务器端和代理程序对管线化的支持并不好,因此现代浏览器如 Chrome 和 Firefox 默认并未开启管线化支持 237 | 238 | 更多关于 HTTP Pipelining 的知识可以参考[这里](https://developer.mozilla.org/en-US/docs/Web/HTTP/Connection_management_in_HTTP_1.x)。 239 | 240 | ## 会话跟踪 241 | 1. 什么是会话? 242 | 243 | 客户端打开与服务器的连接发出请求到服务器响应客户端请求的全过程称之为会话。 244 | 245 | 2. 什么是会话跟踪? 246 | 247 | 会话跟踪指的是对同一个用户对服务器的连续的请求和接受响应的监视。 248 | 249 | 3. 为什么需要会话跟踪? 250 | 251 | 浏览器与服务器之间的通信是通过HTTP协议进行通信的,而HTTP协议是”无状态”的协议,它不能保存客户的信息,即一次响应完成之后连接就断开了,下一次的请求需要重新连接,这样就需要判断是否是同一个用户,所以才有会话跟踪技术来实现这种要求。 252 | 253 | 4. 会话跟踪常用的方法? 254 | 1. URL 重写 255 | 256 | URL(统一资源定位符)是Web上特定页面的地址,URL重写的技术就是在URL结尾添加一个附加数据以标识该会话,把会话ID通过URL的信息传递过去,以便在服务器端进行识别不同的用户。 257 | 258 | 2. 隐藏表单域 259 | 260 | 将会话ID添加到HTML表单元素中提交到服务器,此表单元素并不在客户端显示 261 | 262 | 3. Cookie 263 | 264 | Cookie 是Web 服务器发送给客户端的一小段信息,客户端请求时可以读取该信息发送到服务器端,进而进行用户的识别。对于客户端的每次请求,服务器都会将 Cookie 发送到客户端,在客户端可以进行保存,以便下次使用。 265 | 266 | 客户端可以采用两种方式来保存这个 Cookie 对象,一种方式是保存在客户端内存中,称为临时 Cookie,浏览器关闭后这个 Cookie 对象将消失。另外一种方式是保存在客户机的磁盘上,称为永久 Cookie。以后客户端只要访问该网站,就会将这个 Cookie 再次发送到服务器上,前提是这个 Cookie 在有效期内,这样就实现了对客户的跟踪。 267 | 268 | Cookie 是可以被客户端禁用的。 269 | 270 | 4. Session: 271 | 272 | 每一个用户都有一个不同的 session,各个用户之间是不能共享的,是每个用户所独享的,在 session 中可以存放信息。 273 | 274 | 在服务器端会创建一个 session 对象,产生一个 sessionID 来标识这个 session 对象,然后将这个 sessionID 放入到 Cookie 中发送到客户端,下一次访问时,sessionID 会发送到服务器,在服务器端进行识别不同的用户。 275 | 276 | Session 的实现依赖于 Cookie,如果 Cookie 被禁用,那么 session 也将失效。 277 | 278 | ## 跨站攻击 279 | ### CSRF(Cross-site request forgery,跨站请求伪造) 280 | 281 | CSRF(XSRF) 顾名思义,是伪造请求,冒充用户在站内的正常操作。 282 | 283 | 例如,一论坛网站的发贴是通过 GET 请求访问,点击发贴之后 JS 把发贴内容拼接成目标 URL 并访问: 284 | ``` 285 | http://example.com/bbs/create_post.php?title=标题&content=内容 286 | ``` 287 | 288 | 那么,我们只需要在论坛中发一帖,包含一链接: 289 | ``` 290 | http://example.com/bbs/create_post.php?title=我是脑残&content=哈哈 291 | ``` 292 | 只要有用户点击了这个链接,那么他们的帐户就会在不知情的情况下发布了这一帖子。可能这只是个恶作剧,但是既然发贴的请求可以伪造,那么删帖、转帐、改密码、发邮件全都可以伪造。 293 | 294 | ### 如何防范 CSRF 攻击? 295 | - 关键操作只接受 POST 请求 296 | - 验证码 297 | 298 | CSRF 攻击的过程,往往是在用户不知情的情况下构造网络请求。所以如果使用验证码,那么每次操作都需要用户进行互动,从而简单有效的防御了CSRF攻击。 299 | 300 | 但是如果你在一个网站作出任何举动都要输入验证码会严重影响用户体验,所以验证码一般只出现在特殊操作里面,或者在注册时候使用。 301 | - 检测 Referer 302 | 303 | 常见的互联网页面与页面之间是存在联系的,比如你在 www.baidu.com 应该是找不到通往www.google.com 的链接的,再比如你在论坛留言,那么不管你留言后重定向到哪里去了,之前的那个网址一定会包含留言的输入框,这个之前的网址就会保留在新页面头文件的 Referer 中 304 | 305 | 通过检查 Referer 的值,我们就可以判断这个请求是合法的还是非法的,但是问题出在服务器不是任何时候都能接受到 Referer 的值,所以 Referer Check 一般用于监控 CSRF 攻击的发生,而不用来抵御攻击。 306 | 307 | - Token 308 | 309 | 目前主流的做法是使用 Token 抵御 CSRF 攻击。下面通过分析 CSRF 攻击来理解为什么 Token 能够有效 310 | 311 | CSRF 攻击要成功的条件在于攻击者能够预测所有的参数从而构造出合法的请求。所以根据不可预测性原则,我们可以对参数进行加密从而防止 CSRF 攻击。 312 | 313 | 另一个更通用的做法是保持原有参数不变,另外添加一个参数 Token,其值是随机的。这样攻击者因为不知道 Token 而无法构造出合法的请求进行攻击。 314 | 315 | Token 使用原则 316 | 317 | - Token 要足够随机————只有这样才算不可预测 318 | - Token 是一次性的,即每次请求成功后要更新Token————这样可以增加攻击难度,增加预测难度 319 | - Token 要注意保密性————敏感操作使用 post,防止 Token 出现在 URL 中 320 | 321 | 注意:过滤用户输入的内容不能阻挡 csrf,我们需要做的是过滤请求的来源。 322 | 323 | ### XSS(Cross Site Scripting,跨站脚本攻击) 324 | 325 | XSS 全称“跨站脚本”,是注入攻击的一种。其特点是不对服务器端造成任何伤害,而是通过一些正常的站内交互途径,例如发布评论,提交含有 JavaScript 的内容文本。这时服务器端如果没有过滤或转义掉这些脚本,作为内容发布到了页面上,其他用户访问这个页面的时候就会运行这些脚本。 326 | 327 | 运行预期之外的脚本带来的后果有很多中,可能只是简单的恶作剧——一个关不掉的窗口: 328 | 329 | ``` 330 | while (true) { 331 | alert("你关不掉我~"); 332 | } 333 | ``` 334 | 335 | 也可以是盗号或者其他未授权的操作。 336 | 337 | XSS 是实现 CSRF 的诸多途径中的一条,但绝对不是唯一的一条。一般习惯上把通过 XSS 来实现的 CSRF 称为 XSRF。 338 | 339 | ### 如何防御 XSS 攻击? 340 | 理论上,所有可输入的地方没有对输入数据进行处理的话,都会存在 XSS 漏洞,漏洞的危害取决于攻击代码的威力,攻击代码也不局限于 script。防御 XSS 攻击最简单直接的方法,就是过滤用户的输入。 341 | 342 | 如果不需要用户输入 HTML,可以直接对用户的输入进行 HTML escape 。下面一小段脚本: 343 | 344 | ``` 345 | 346 | ``` 347 | 348 | 经过 escape 之后就成了: 349 | 350 | ``` 351 | <script>window.location.href="http://www.baidu.com"</script> 352 | ``` 353 | 354 | 它现在会像普通文本一样显示出来,变得无毒无害,不能执行了。 355 | 356 | 当我们需要用户输入 HTML 的时候,需要对用户输入的内容做更加小心细致的处理。仅仅粗暴地去掉 script 标签是没有用的,任何一个合法 HTML 标签都可以添加 onclick 一类的事件属性来执行 JavaScript。更好的方法可能是,将用户的输入使用 HTML 解析库进行解析,获取其中的数据。然后根据用户原有的标签属性,重新构建 HTML 元素树。构建的过程中,所有的标签、属性都只从白名单中拿取。 357 | 358 | 359 | # HTTP和HTTPS的区别?HTTPS为什么更安全? 360 | - 区别 361 | 1. HTTPS需要向机构申请CA证书,极少免费 362 | 2. HTTP属于明文传输,HTTPS基于SSL/TLS进行加密传输 363 | 3. HTTP端口号为80,HTTPS端口号为443 364 | 4. HTTPS是加密传输,有身份验证的环节,更加安全 365 | - 安全 366 | - SSL(安全套接层)、TLS(传输层安全),在传输层上,对网络连接进行加密处理,保障数据的完整性,更加的安全。 367 | 368 | # HTTPS的连接建立流程 369 | HTTPS为了兼顾安全与效率,同时使用了对称加密和非对称加密。在传输的过程中会涉及到三个密钥: 370 | - 服务器端的公钥和私钥,用来进行非对称加密 371 | - 客户端生成的随机密钥,用来进行对称加密 372 | ![image](https://qn.nobady.cn/iOS/https.png) 373 | 374 | 如上图,HTTPS连接过程大致可以分为八步: 375 | - 1、客户端访问HTTPS连接 376 | - 客户端会把安全协议版本号、客户端支持的加密算法列表、随机数C发给服务器。 377 | - 2、服务器发送证书给客户端 378 | - 服务端接收密钥算法配件后,会和自己支持的加密算法列表进行对比,如果不符合,就断开连接。否则,服务器会在算法列表中,选择一种对称算法(如AES)、一种公钥算法(如RSA)和一种MAC算法发给客户端。 379 | - 服务端有一个密钥对,即公钥和私钥,是用来进行非对称加密的。服务器保存着私钥,不能将其泄露,公钥可以发给任何人。 380 | - 在发送加密算法的同时还会把数字证书和随机数S发送给客户端。 381 | - 3、客户端验证server证书 382 | - 会对server公钥进行检查,验证其合法性,如果发现公钥有问题,那么HTTPS传输就无法继续。 383 | - 4、客户端组装会话秘钥 384 | - 如果公钥合格,那么客户端会用服务器公钥来生产一个前主密钥(Pre-Master Secret,PMS),并通过该前主密钥和随机数C、S来组装成会话秘钥。 385 | - 5、客户端将前主密钥加密发送给服务端 386 | - 是通过服务端的公钥来对前主密钥进行非对称加密,发送给服务端 387 | - 6、服务端通过私钥解密得到前主密钥 388 | - 服务端接收到加密信息后,用私钥解密得到主密钥 389 | - 7、服务端组装会话秘钥 390 | - 服务器通过前主密钥和随机数C、S来组装会话秘钥 391 | - 至此,服务端和客户端都已经知道了用于此次会话的主密钥 392 | - 8、数据传输 393 | - 客户端收到服务器发送来的密文,用客户端秘钥对其进行对称解密,得到服务器发送的数据 394 | - 同理,服务端收到客户端发送来的密文,用服务端密钥对其进行对称解密,得到客户端发送的数据。 395 | 396 | 397 | -------------------------------------------------------------------------------- /docs/01-03、TCP 和 UDP 协议 L3.md: -------------------------------------------------------------------------------- 1 | https://hit-alibaba.github.io/interview/basic/network/TCP.html 2 | 3 | https://hit-alibaba.github.io/interview/basic/network/UDP.html 4 | 5 | [TOC] 6 | 7 | # TCP和UDP的区别 8 | - TCP:面向连接、传输可靠(保证数据正确性,保证数据顺序)、用于传输大量数据(流模式)、速度慢,建立连接需要开销较多(时间,系统资源) 9 | - UDP:面向非连接、传输不可靠、用于传输少量数据(数据包模式)、速度快 10 | 11 | 12 | 13 | TCP | UDP 14 | ---|--- 15 | 面向连接 | 无连接 16 | 每条TCP连接只能一对一通信 | 支持一对一、一对多、多对一和多对多通信 17 | 面向字节流 | 面向报文 18 | 可靠传输(使用校验和,确认和重传机制) | 传输不可靠 19 | 使用流量控制和拥塞控制 | 不使用流量控制和拥塞控制 20 | 首部开销大(20-60字节) | 首部开销小(8字节) 21 | 速度慢 | 速度快 22 | 23 | 24 | [UDP和TCP的对比](https://www.bilibili.com/video/BV1Rt411W7zv) 25 | 26 | # TCP 27 | ## TCP的特性 28 | - TCP 提供一种面向连接的、可靠的字节流服务 29 | - 在一个 TCP 连接中,仅有两方进行彼此通信。广播和多播不能用于 TCP 30 | - TCP 使用校验和,确认和重传机制来保证可靠传输 31 | - TCP 给数据分节进行排序,并使用累积确认保证数据的顺序不变和非重复 32 | - TCP 使用滑动窗口机制来实现流量控制,通过动态改变窗口的大小进行拥塞控制 33 | 34 | 注意:TCP 并不能保证数据一定会被对方接收到,因为这是不可能的。TCP 能够做到的是,如果有可能,就把数据递送到接收方,否则就(通过放弃重传并且中断连接这一手段)通知用户。因此准确说 TCP 也不是 100% 可靠的协议,它所能提供的是数据的可靠递送或故障的可靠通知。 35 | 36 | ## 三次握手 和 四次挥手 37 | ### 三次握手(简易版) 38 | - 1、由客户端向服务端发送SYN同步报文 39 | - 2、当服务端收到SYN同步报文之后,会返回给客户端SYN同步报文和ACK确认报文 40 | - 3、客户端会向服务端发送ACK确认报文,此时客户端和服务端的连接正式建立 41 | 42 | ### 建立连接(简易版) 43 | - 1、这个时候客户端就可以通过HTTP请求报文,向服务端发送请求 44 | - 2、服务端接收到客户端的请求后,向客户端回复HTTP响应报文 45 | 46 | ### 三次握手(详细版) 47 | 所谓三次握手(Three-way Handshake),是指建立一个 TCP 连接时,需要客户端和服务器总共发送3个包。 48 | 49 | 三次握手的目的是连接服务器指定端口,建立 TCP 连接,并同步连接双方的序列号和确认号,交换 TCP 窗口大小信息。在 socket 编程中,客户端执行 connect() 时。将触发三次握手。 50 | 51 | - 第一次握手(SYN=1, seq=x): 52 | 53 | 客户端发送一个 TCP 的 SYN 标志位置1的包,指明客户端打算连接的服务器的端口,以及初始序号 X,保存在包头的序列号(Sequence Number)字段里。 54 | 55 | 发送完毕后,客户端进入 SYN_SEND 状态。 56 | 57 | - 第二次握手(SYN=1, ACK=1, seq=y, ACK num=x+1): 58 | 59 | 服务器发回确认包(ACK)应答。即 SYN 标志位和 ACK 标志位均为1。服务器端选择自己 ISN 序列号,放到 Seq 域里,同时将确认序号(Acknowledgement Number)设置为客户的 ISN 加1,即X+1。 发送完毕后,服务器端进入 SYN_RCVD 状态。 60 | 61 | - 第三次握手(ACK=1,ACK num=y+1) 62 | 63 | 客户端再次发送确认包(ACK),SYN 标志位为0,ACK 标志位为1,并且把服务器发来 ACK 的序号字段+1,放在确定字段中发送给对方,并且在数据段放写ISN的+1 64 | 65 | 发送完毕后,客户端进入 ESTABLISHED 状态,当服务器端接收到这个包时,也进入 ESTABLISHED 状态,TCP 握手结束。 66 | 67 | 三次握手的过程的示意图如下: 68 | 69 | 70 | ![image](https://note.youdao.com/yws/api/personal/file/WEBc02f1430e6d1c4afa5e472ae701813b0?method=download&shareKey=d7d11a18090641929158565f6897b3b9) 71 | 72 | 73 | ### 四次挥手(详细版) 74 | TCP 的连接的拆除需要发送四个包,因此称为四次挥手(Four-way handshake),也叫做改进的三次握手。客户端或服务器均可主动发起挥手动作,在 socket 编程中,任何一方执行 close() 操作即可产生挥手操作。 75 | - 第一次挥手(FIN=1,seq=x) 76 | 77 | 假设客户端想要关闭连接,客户端发送一个 FIN 标志位置为1的包,表示自己已经没有数据可以发送了,但是仍然可以接受数据。 78 | 79 | 发送完毕后,客户端进入 FIN_WAIT_1 状态。 80 | 81 | - 第二次挥手(ACK=1,ACKnum=x+1) 82 | 83 | 服务器端确认客户端的 FIN 包,发送一个确认包,表明自己接受到了客户端关闭连接的请求,但还没有准备好关闭连接。 84 | 85 | 发送完毕后,服务器端进入 CLOSE_WAIT 状态,客户端接收到这个确认包之后,进入 FIN_WAIT_2 状态,等待服务器端关闭连接。 86 | 87 | - 第三次挥手(FIN=1,seq=y) 88 | 89 | 服务器端准备好关闭连接时,向客户端发送结束连接请求,FIN 置为1。 90 | 91 | 发送完毕后,服务器端进入 LAST_ACK 状态,等待来自客户端的最后一个ACK。 92 | 93 | - 第四次挥手(ACK=1,ACKnum=y+1) 94 | 95 | 客户端接收到来自服务器端的关闭请求,发送一个确认包,并进入 TIME_WAIT状态,等待可能出现的要求重传的 ACK 包。 96 | 97 | 服务器端接收到这个确认包之后,关闭连接,进入 CLOSED 状态。 98 | 99 | 客户端等待了某个固定时间(两个最大段生命周期,2MSL,2 Maximum Segment Lifetime)之后,没有收到服务器端的 ACK ,认为服务器端已经正常关闭连接,于是自己也关闭连接,进入 CLOSED 状态。 100 | 101 | 102 | 四次挥手的示意图如下: 103 | 104 | 105 | ![image](https://note.youdao.com/yws/api/personal/file/WEB5fc05efbb7a55cadbc766ef3ebeefbd4?method=download&shareKey=309d36cc443b51e45b40108256df0f8a) 106 | 107 | ### 为什么需要四次挥手 108 | - tcp是全双工通信,服务端和客服端都能发送和接收数据。 109 | - tcp在断开连接时,需要服务端和客服端都确定对方将不再发送数据。 110 | 111 | ### SYN攻击 112 | - 什么是 SYN 攻击(SYN Flood)? 113 | 114 | 在三次握手过程中,服务器发送 SYN-ACK 之后,收到客户端的 ACK 之前的 TCP 连接称为半连接(half-open connect)。此时服务器处于 SYN_RCVD 状态。当收到 ACK 后,服务器才能转入 ESTABLISHED 状态. 115 | 116 | SYN 攻击指的是,攻击客户端在短时间内伪造大量不存在的IP地址,向服务器不断地发送SYN包,服务器回复确认包,并等待客户的确认。由于源地址是不存在的,服务器需要不断的重发直至超时,这些伪造的SYN包将长时间占用未连接队列,正常的SYN请求被丢弃,导致目标系统运行缓慢,严重者会引起网络堵塞甚至系统瘫痪。 117 | 118 | SYN 攻击是一种典型的 DoS/DDoS 攻击。 119 | 120 | - 如何检测 SYN 攻击? 121 | 122 | 检测 SYN 攻击非常的方便,当你在服务器上看到大量的半连接状态时,特别是源IP地址是随机的,基本上可以断定这是一次SYN攻击。在 Linux/Unix 上可以使用系统自带的 netstats 命令来检测 SYN 攻击。 123 | 124 | - 如何防御 SYN 攻击? 125 | 126 | SYN攻击不能完全被阻止,除非将TCP协议重新设计。我们所做的是尽可能的减轻SYN攻击的危害,常见的防御 SYN 攻击的方法有如下几种: 127 | 128 | - 缩短超时(SYN Timeout)时间 129 | - 增加最大半连接数 130 | - 过滤网关防护 131 | - SYN cookies技术 132 | 133 | ### TCP KeepAlive 134 | TCP 的连接,实际上是一种纯软件层面的概念,在物理层面并没有“连接”这种概念。TCP 通信双方建立交互的连接,但是并不是一直存在数据交互,有些连接会在数据交互完毕后,主动释放连接,而有些不会。在长时间无数据交互的时间段内,交互双方都有可能出现掉电、死机、异常重启等各种意外,当这些意外发生之后,这些 TCP 连接并未来得及正常释放,在软件层面上,连接的另一方并不知道对端的情况,它会一直维护这个连接,长时间的积累会导致非常多的半打开连接,造成端系统资源的消耗和浪费,为了解决这个问题,在传输层可以利用 TCP 的 KeepAlive 机制实现来实现。主流的操作系统基本都在内核里支持了这个特性。 135 | 136 | 137 | TCP KeepAlive 的基本原理是,隔一段时间给连接对端发送一个探测包,如果收到对方回应的 ACK,则认为连接还是存活的,在超过一定重试次数之后还是没有收到对方的回应,则丢弃该 TCP 连接。 138 | 139 | [TCP-Keepalive-HOWTO](http://www.tldp.org/HOWTO/html_single/TCP-Keepalive-HOWTO/) 有对 TCP KeepAlive 特性的详细介绍,有兴趣的同学可以参考。这里主要说一下,TCP KeepAlive 的局限。首先 TCP KeepAlive 监测的方式是发送一个 probe 包,会给网络带来额外的流量,另外 TCP KeepAlive 只能在内核层级监测连接的存活与否,而连接的存活不一定代表服务的可用。例如当一个服务器 CPU 进程服务器占用达到 100%,已经卡死不能响应请求了,此时 TCP KeepAlive 依然会认为连接是存活的。因此 TCP KeepAlive 对于应用层程序的价值是相对较小的。需要做连接保活的应用层程序,例如 QQ,往往会在应用层实现自己的心跳功能。 140 | 141 | ## TCP拥塞控制 142 | ![image](https://iosgua.com/interval/docs/ch01/tc01/01/tcp_congestion.png) 143 | - 慢启动、当 CongWin低于Threshold, 发送端在慢启动阶段,窗口指数增长。 144 | - 拥塞避免、当 CongWin高于Threshold, 发送端在拥塞避免阶段,窗口线性增长。 145 | - 快重传、当接收到三个重复的ACK,Threshold设置为CongWin/2,CongWin设置为Threshold。 146 | - 快恢复、当超时发生的时候,Threshold设置为CongWin/2,CongWin设置为1MSS,重新跑1-3流程 147 | 148 | [TCP的拥塞控制](https://www.bilibili.com/video/BV1L4411a7RN) 149 | 150 | ## 流量控制 151 | - 让发送方的发送速率不要太快,要让接收方来得及接收 152 | - 利用滑动窗口机制可以很方便的在TCP连接实现对发送方的流量控制 153 | 154 | [TCP的流量控制](https://www.bilibili.com/video/BV1EJ411n7y7) 155 | 156 | # UDP 157 | UDP 是一个简单的传输层协议。和 TCP 相比,UDP 有下面几个显著特性: 158 | - UDP 缺乏可靠性。UDP 本身不提供确认,序列号,超时重传等机制。UDP 数据报可能在网络中被复制,被重新排序。即 UDP 不保证数据报会到达其最终目的地,也不保证各个数据报的先后顺序,也不保证每个数据报只到达一次 159 | - UDP 数据报是有长度的。每个 UDP 数据报都有长度,如果一个数据报正确地到达目的地,那么该数据报的长度将随数据一起传递给接收方。而 TCP 是一个字节流协议,没有任何(协议上的)记录边界。 160 | - UDP 是无连接的。UDP 客户和服务器之前不必存在长期的关系。UDP 发送数据报之前也不需要经过握手创建连接的过程。 161 | - UDP 支持多播和广播。 -------------------------------------------------------------------------------- /docs/01-04、设计模式 L4.md: -------------------------------------------------------------------------------- 1 | - [图说设计模式](https://design-patterns.readthedocs.io/zh_CN/latest/) 2 | - [Trip-to-iOS-Design-Patterns](https://github.com/skyming/Trip-to-iOS-Design-Patterns) 3 | - [iOS应用架构谈 view层的组织和调用方案](https://casatwy.com/iosying-yong-jia-gou-tan-viewceng-de-zu-zhi-he-diao-yong-fang-an.html) 4 | - [iOS应用架构谈 view层的组织和调用方案](https://casatwy.com/iosying-yong-jia-gou-tan-viewceng-de-zu-zhi-he-diao-yong-fang-an.html) 5 | - [iOS应用架构谈 网络层设计方案](https://casatwy.com/iosying-yong-jia-gou-tan-wang-luo-ceng-she-ji-fang-an.html) 6 | 7 | [TOC] 8 | 9 | # 设计模式六大原则 10 | - 单一职责原则:即一个类应该只负责一项职责 11 | - 开闭原则:对扩展开放,对修改关闭 12 | - 接口隔离原则:客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上 13 | - 依赖倒转原则:高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象 14 | - 里氏替换原则:所有引用基类的地方必须能透明的使用其子类的对象 15 | - 最少知道原则:一个对象应该对其他对象保持最少的了解 16 | 17 | # 常见的设计模式 18 | ## 1.单例模式 19 | ## 2.工厂模式 20 | ## 3.观察者模式 21 | ## 4.适配器模式 22 | ## 5.MVC 模式 23 | ## 6.代理模式 24 | 25 | [iOS 中的 21 种设计模式](https://www.jianshu.com/p/6b302c7fe987) 26 | 27 | # MVC 28 | - Model是数据管理者,View为数据展示者,Controller为数据加工者,Model和View又都是由Controller来根据业务需求调配,所以Controller还负担了一个数据流调配的功能。 29 | 30 | ## MVC的划分 31 | ### M应该做的事: 32 | 1. 给ViewController提供数据 33 | 2. 给ViewController存储数据提供接口 34 | 3. 提供经过抽象的业务基本组建,供Controller调度 35 | 36 | ### C应该做的事: 37 | 1. 管理View Container的生命周期(self.view) 38 | 2. 负责生成所有的View实例,并放入View Container 39 | 3. 监听来自View与业务有关的事件,通过与Model的合作,来完成对应事件的业务 40 | 41 | ### V应该做的事: 42 | 1. 相应与业务无关的事件,实现动画效果,点击反馈等 43 | 2. 界面元素表达 44 | 45 | # MVCS 46 | - 基于MVC衍生出来的一套架构。从概念上来说,它拆分的部分是Model部分,拆出来一个Store。这个Store专门负责数据存取。但从实际操作的角度上讲,它拆开的是Controller。 47 | - Model为瘦Model,只用于表达数据,然后存储、数据处理都交给外面的来做。 48 | - MVCS是将数据的存储和处理部分抽离出来,交给另一个对象去做,这个对象就是Store。 49 | 50 | ## 胖Model和瘦Model 51 | ### 胖Model 52 | - 胖Model包含了部分弱业务逻辑。胖Model要达到的目的是,Controller从胖Model这里拿到数据之后,不用额外做操作或者只要做非常少的操作,就能够将数据直接应用在View上。(比如对时间数据在不同label上的显示处理逻辑) 53 | ### 瘦Model 54 | - 瘦Model只负责业务数据的表达,所有业务无论强弱一律扔到Controller。瘦Model要达到的目的是,尽一切可能去编写细粒度Model,然后配套各种helper类(工具类)或方法来对弱业务做抽象,强业务依旧交给Controller 55 | 56 | # MVVM 57 | MVVM本质上是为Controller减负,把数据加工的任务从Controller中解放出来。 58 | 59 | ## ViewModel的作用 60 | 把RawData变成直接能被View使用的对象的一种Model 61 | 62 | # 如何选择MVC、MVVM架构 63 | - 第一心法:保留最重要的任务,拆分其它不重要的任务 64 | - 第二心法:拆分后的模块要尽可能提高可复用性,尽量做到DRY 65 | - 第三心法:要尽可能提高拆分模块后的抽象度 -------------------------------------------------------------------------------- /docs/01-05、数据结构和算法(空).md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pengwj/iOSInterview/e0918812d041cb9bd5fc0f5175b67bd60306899d/docs/01-05、数据结构和算法(空).md -------------------------------------------------------------------------------- /docs/01-06、编译链接启动 L5.md: -------------------------------------------------------------------------------- 1 | # 编译&链接过程 2 | 这个过程中隐藏了预处理、编译、汇编和链接4个过程。 3 | 4 | ![image](https://note.youdao.com/yws/api/personal/file/88C5E8CFE3A64A369FECCD87297414D3?method=download&shareKey=9884a15dc8dc7604c7f7c6baf735c274) 5 | 6 | ## 预处理 7 | 处理macro 宏, import 头文件及其他的预编译指令(均以"#"开头的文件),产生.i文件。规则如下: 8 | - "#define"删除并展开对应宏定义。 9 | - 处理所有的条件预编译指令。如#if/#ifdef/#else/#endif。 10 | - "#include/#import"包含的文件递归插入到此处。 11 | - 删除所有的注释"//或/**/"。 12 | - 添加行号和文件名标识。如“# 1 "main.m"”,编译调试会用到。 13 | 14 | ## 编译 15 | 编译就是把上面得到的.i文件进行:词法分析、语法分析、静态分析、优化生成相应的汇编代码,得到.s文件。 16 | ### 词法分析 17 | 将源代码的字符序列分割成一个个token(关键字、标识符、字面量、特殊符号),比如把标识符放到符号表(静态链接那篇,重点讲符号表)。 18 | 19 | ### 语法分析 20 | 生成抽象语法树 AST,此时运算符号的优先级确定了;有些符号具有多重含义也确定了,比如“*”是乘号还是对指针取内容;表达式不合法、括号不匹配等,都会报错。 21 | 22 | ### 静态分析 23 | 分析类型声明和匹配问题。比如整型和字符串相加,肯定会报错。 24 | 25 | ### 中间语言生成 26 | CodeGen根据AST自顶向下遍历逐步翻译成 LLVM IR,并且在编译期就可以确定的表达式进行优化,比如代码里t1=2+6,可以优化t1=8。(假如开启了bitcode) 27 | 28 | ### 目标代码生成与优化 29 | 根据中间语言生成依赖具体机器的汇编语言。并优化汇编语言。这个过程中,假如有变量且定义在同一个编译单元里,那给这个变量分配空间,确定变量的地址。假如变量或者函数不定义在这个编译单元,得链接时候,才能确定地址。 30 | 31 | ## 汇编 32 | 汇编就是把上面得到的.s文件里的汇编指令一一翻译成机器指令。得到.o文件,也就是目标文件,后面会重点讲的Mach-O文件。 33 | 34 | ## 链接 35 | 链接就是把目标文件(一个或多个)和需要的库(静态库/动态库)链接成可执行文件。 36 | 37 | # iOS的编译器 38 | iOS现在采用了LLVM作为编译器。 39 | - LLVM采用三相设计,前端Clang负责解析,验证和诊断输入代码中的错误,然后将解析的代码转换为LLVM IR,后端LLVM编译把IR通过一系列改进代码的分析和优化过程提供,然后被发送到代码生成器以生成本机机器代码。 40 | 41 | # iOS程序-详细编译过程 42 | ## 43 | - 1.写入辅助文件:将项目的文件结构对应表、将要执行的脚本、项目依赖库的文件结构对应表写成文件,方便后面使用;并且创建一个 .app 包,后面编译后的文件都会被放入包中; 44 | - 2.运行预设脚本:Cocoapods 会预设一些脚本,当然你也可以自己预设一些脚本来运行。这些脚本都在 Build Phases中可以看到; 45 | - 3.编译文件:针对每一个文件进行编译,生成可执行文件 Mach-O,这过程 LLVM 的完整流程,前端、优化器、后端; 46 | - 4.链接文件:将项目中的多个可执行文件合并成一个文件; 47 | - 5.拷贝资源文件:将项目中的资源文件拷贝到目标包; 48 | - 6.编译 storyboard 文件:storyboard 文件也是会被编译的; 49 | - 7.链接 storyboard 文件:将编译后的 storyboard 文件链接成一个文件; 50 | - 8.编译 Asset 文件:我们的图片如果使用 Assets.xcassets 来管理图片,那么这些图片将会被编译成机器码,除了 icon 和 launchImage; 51 | - 9.运行 Cocoapods 脚本:将在编译项目之前已经编译好的依赖库和相关资源拷贝到包中。 52 | - 10.生成 .app 包 53 | - 11.将 Swift 标准库拷贝到包中 54 | - 12.对包进行签名 55 | - 13.完成打包 56 | 57 | ## 戴铭总结 58 | - 编译信息写入辅助文件,创建文件架构 .app 文件 59 | - 处理文件打包信息 60 | - 执行 CocoaPod 编译前脚本,checkPods Manifest.lock 61 | - 编译.m文件,使用 CompileC 和 clang 命令 62 | - 链接需要的 Framework 63 | - 编译 xib 64 | - 拷贝 xib ,资源文件 65 | - 编译 ImageAssets 66 | - 处理 info.plist 67 | - 执行 CocoaPod 脚本 68 | - 拷贝标准库 69 | - 创建 .app 文件和签名 70 | 71 | # Mach-O 72 | ## 什么是Mach-O文件? 73 | Mach-O 文件是 iOS 和 OS 操作系统系统支持的可执行文件格式。 74 | - 他是源代码编译得到的文件,包含机器指令、数据,还有链接时候需要的一些信息,比如符号表、调试信息、字符串等。 75 | - 他由Header(头部信息)、Load Commands(加载命令)、Data(数据段)三部分组成。 76 | 77 | ## 胖二进制格式 78 | 通用二进制格式就是多种架(比如arm64和x86)构的Mach-O文件“打包”在一起,所以通用二进制格式,更多被叫做胖二进制格式。 79 | 80 | # 资料 81 | [深入剖析 iOS 编译 Clang / LLVM](https://ming1016.github.io/2017/03/01/deeply-analyse-llvm/#%E8%B5%84%E6%96%99%E7%BD%91%E5%9D%80) 82 | 83 | [链接器:符号是怎么绑定到地址上的?](https://time.geekbang.org/column/article/86840) 84 | 85 | [iOS程序员的自我修养](https://juejin.cn/post/6844903912143585288) 86 | 87 | [iOS编译链接原理](https://www.jianshu.com/p/b62b891c5d18) 88 | 89 | [iOS程序员的自我修养-编译、链接过程(一) 90 | ](https://juejin.cn/post/6844903912147795982) 91 | 92 | [iOS开发你不知道的事-编译&链接](https://juejin.cn/post/6844903841842872327) 93 | 94 | [iOS链接原理解析与应用实践](https://mp.weixin.qq.com/s/_3WXnDolNICs2euoJph44A) 95 | 96 | [乐少-点击 Run 之后发生了什么?](https://www.jianshu.com/p/d5cf01424e92) 97 | 98 | [iOS App从点击到启动](https://www.jianshu.com/p/231b1cebf477) -------------------------------------------------------------------------------- /docs/01-07、数据加密 L6.md: -------------------------------------------------------------------------------- 1 | # 对称加密 2 | ## 定义 3 | 密码学中的一类加密算法。这类算法在加密和解密时使用相同的密钥,或是使用两个可以简单地相互推算的密钥。 4 | 5 | ## 常见算法 6 | 常见的对称加密算法有DES、3DES、AES、Blowfish、IDEA、RC5、RC6 7 | 8 | # 非对称加密 9 | ## 定义 10 | 密码学的一种算法,它需要两个密钥,一个是公开密钥,另一个是私有密钥;公钥用作加密,私钥则用作解密。使用私钥加密的信息,只能由该私钥对应的公钥才能解密,使用公钥加密的信息,只能由该公钥对应的私钥才能解密。 11 | 12 | ## 常见算法 13 | RSA、ElGamal、Rabin(RSA的特例)、DSA、ECDSA。使用最广泛的是RSA算法(由发明者Rivest、Shmir和Adleman姓氏首字母缩写而来)。 14 | 15 | # 非对称加密与对称加密优缺点 16 | 17 | 对称加密 | 非对称加密 18 | ---|--- 19 | 加解密的效率要高得多、加密速度快 | 加密和解密花费时间长、速度慢 20 | 不是非常安全,密钥管理负担很重 | 安全性更高,公钥是公开的,密钥是自己保存的 21 | 22 | # 数字签名 23 | 数字签名就是用发送方的私钥对原始数据进行签名,只有用发送方公钥才能通过签名验证。 24 | 25 | ## 常见算法 26 | MD5withRSA/SHA1withRSA/SHA256withRSA/SHA1withDSA/SHA256withDSA/SHA512withDSA/ECDSA 27 | 28 | # 哈希算法 29 | ## 定义 30 | 哈希算法(Hash)又称摘要算法(Digest),它的作用是:对任意一组输入数据进行计算,得到一个固定长度的输出摘要。 31 | 32 | ## 常见算法 33 | MD5、SHA-1、RipeMD-160、SHA-256、SHA-512 34 | 35 | ## 误区 36 | MD5算法理论上无法解密(暴力碰撞为非正常解密) 37 | 38 | # 资料 39 | [bang-iOS App 签名的原理](http://blog.cnbang.net/tech/3386/) 40 | 41 | [wiki-对称密钥加密](https://zh.wikipedia.org/wiki/%E5%B0%8D%E7%A8%B1%E5%AF%86%E9%91%B0%E5%8A%A0%E5%AF%86) 42 | 43 | [wiki-非对称式密码学](https://zh.wikipedia.org/wiki/%E5%85%AC%E5%BC%80%E5%AF%86%E9%92%A5%E5%8A%A0%E5%AF%86) 44 | 45 | [白话解释 对称加密算法 VS 非对称加密算法](https://segmentfault.com/a/1190000004461428) 46 | 47 | [廖雪峰-非对称加密算法](https://www.liaoxuefeng.com/wiki/1252599548343744/1304227873816610) 48 | 49 | [廖雪峰-对称加密算法](https://www.liaoxuefeng.com/wiki/1252599548343744/1304227762667553) 50 | 51 | [廖雪峰-哈希算法](https://www.liaoxuefeng.com/wiki/1252599548343744/1304227729113121) -------------------------------------------------------------------------------- /docs/02-01、KVC与KVO L7.md: -------------------------------------------------------------------------------- 1 | [TOC] 2 | 3 | # KVO (240-249) 4 | ## KVO的实现原理 5 | 使用KVO监听A对象属性时,runtime会动态的创建该对象的子类(NSKVONotifying_A),然后重写setter方法,在setter方法中调用```_NSSetXXXValueAndNotify```,最终调用willchangeValue、didchangeValue方法。 6 | 7 | + 利用runtime动态生成一个子类,并且让实例对象的isa指向这个全新的子类 8 | + 当修改实例对象的属性时,会调用Foundation的_NSSetXXXValueAndValueNotify函数 (_NSSetIntValueAndNotify) 9 | + _NSSetXXXValueAndValueNotify函数的内部实现如下: 10 | + willChangeValueForKey: 11 | + 父类原来的setter 12 | + didChangeValueForKey: 该方法内部会触发监听器(Oberser)的监听方法(observeValueForKeyPath:ofObject:change:context:) 13 | 14 | ## 如何手动触发通知 15 | 手动调用willChangeValueForKey:和didChangeValueForKey: 16 | 17 | ## 直接修改成员变量(_key方式)会触发KVO吗? 18 | 不会触发KVO 19 | 20 | # KVC (250-252) 21 | ## 通过KVC修改属性会触发KVO么? 22 | 会触发KVO,就算没有实现setter方法(开发者、系统均没有实现,只有成员变量时),也会触发(推测KVC内部有主动调用didChangeValueForKey方法) 23 | 24 | ## KVC的赋值和取值过程是怎样的?原理是什么? 25 | + 赋值过程 26 | + 按照setKey:、_setKey:顺序查找方法 27 | + 如果没找到方法,并且accessInstanceVariablesDirectly返回YES,就开始查询成员变量 28 | + 按照_key、_isKey、key、isKey的顺序查找成员变量 29 | 30 | + 取值过程 31 | + 按照getKey、key、isKey、_key的顺序查找 32 | + 如果accessInstanceVariablesDirectly返回YES,就直接查询成员变量,否则直接崩溃 33 | + 按照_key、_isKey、key、isKey的顺序查找成员变量 -------------------------------------------------------------------------------- /docs/02-02、分类与关联对象 L8.md: -------------------------------------------------------------------------------- 1 | [TOC] 2 | 3 | # Category (253-265) 4 | ## Category的数据结构 5 | ``` 6 | struct category_t { 7 | const char *name; 8 | classref_t cls; 9 | struct method_list_t *instanceMethods; 10 | struct method_list_t *classMethods; 11 | struct protocol_list_t *protocols; 12 | struct property_list_t *instanceProperties; 13 | struct property_list_t *_classProperties; 14 | 15 | method_list_t *methodsForMeta(bool isMeta) { 16 | if (isMeta) return classMethods; 17 | else return instanceMethods; 18 | } 19 | 20 | property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi); 21 | } 22 | 23 | ``` 24 | 25 | ## Category的使用场合是什么? 26 | + 给系统类添加方法 27 | + 使用分类为VC解压 28 | 29 | ## Category的实现原理 30 | + Category编译后的底层结构是结构体category_t,里面存储着分类的对象方法、类方法、属性、协议信息 31 | + 在程序启动时,runtime会将category的数据,合并到类信息中(类对象、元类对象中) 32 | 33 | ## Category和Class Extension的区别是什么? 34 | + Class Extension在编译的时候,数据就包含在类信息中 35 | + Category是在运行时,才会将数据合并到类信息中 36 | + Category不需要源码就可以添加信息,Class Extension必须要源码才可以 37 | 38 | ## Category中有load方法吗?load方法是什么时候调用的?load 方法能继承吗? 39 | + 有load方法 40 | + load方法在runtime加载类、分类的时候调用 41 | + load方法可以继承,但是一般情况下不会主动去调用load方法,都是让系统自当调用 42 | 43 | ## load、initialize方法的区别什么? 44 | + +initialize是通过objc_msgSend进行调用的,+load方法是直接通过方法地址调用的 45 | + initialize方法是第一次收到消息时调用的,load方法是启动时调用的 46 | + initialize方法的分类实现会覆盖本身的方法实现,子类会继承父类的方法实现 47 | 48 | ## load方法在category中的调用的顺序? 49 | 每个类、分类的+load,在程序运行过程中只调用一次 50 | + 先调用类的+load方法 51 | + 按照编译先后顺序调用(先编译,先调用) 52 | + 调用子类的+load之前会先调用父类的+load 53 | 54 | + 再调用分类的+load方法 55 | + 按照编译先后顺序调用(先编译,先调用) 56 | 57 | ## initialize方法在category中的调用的顺序? 58 | +initialize方法会在类第一次接收到消息时调用 59 | + 先调用父类的+initialize,再调用子类的+initialize 60 | + 先初始化父类,再初始化子类,每个类只会初始化一次 61 | 62 | ## Category能否添加成员变量?如果可以,如何给Category添加成员变量? 63 | + 不能直接给Category添加成员变量,但是可以使用关联对象间接给Category实现效果 64 | 65 | # 关联对象 (266-273) 66 | ## 实现关联对象技术的核心对象 67 | + AssociationsManager 68 | + AssociationsHashMap 69 | + ObjectAssociationMap 70 | + ObjcAssociation 71 | 72 | runtime全局管理着一个AssociationsManager,里面包含着一个AssociationsHashMap,该hashMap的key是对象的指针,value是ObjectAssociationMap。每一个ObjectAssociationMap的key是该对象的属性,值里面包含着属性的值以及存储策略。 73 | 74 | ## 关联对象的释放时机 75 | - ARC下会主动释放,不过释放的时间会较晚,会在对象释放的最后的object_dispose()方法中释放,不放心的话可以在dealloc中setAssociatedObject为nil,以置空关联关系。 -------------------------------------------------------------------------------- /docs/02-03、block L9.md: -------------------------------------------------------------------------------- 1 | [TOC] 2 | 3 | # block 4 | ## block的原理是怎样的?本质是什么? 5 | 封装了函数调用以及调用环境的OC对象 6 | + block本质上也是一个OC对象,内部含有isa指针 7 | + block是一个封装了函数调用以及函数调用环境的OC对象 8 | + block的底层结构是一个结构体 9 | 10 | ``` 11 | isa 12 | flags 13 | reserved 14 | invoke 15 | descriptor 16 | variables 17 | ``` 18 | 19 | ``` 20 | struct __main_block_impl_0 { 21 | struct __block_impl impl; 22 | struct __main_block_desc_0 *Desc; 23 | } 24 | 25 | struct __block_impl { 26 | void *isa; 27 | int Flags; 28 | int Reserved; 29 | void *FuncPtr; 30 | } 31 | 32 | struct __main_block_desc_0 { 33 | size_t reserved; 34 | size_t Block_size; 35 | } 36 | 37 | ``` 38 | 39 | ## Block的写法 40 | ``` 41 | typedef BOOL (^myBlock)(NSInteger, CGFloat); 42 | 43 | @property (nonatomic, copy) myBlock block; 44 | 45 | self.block = ^(NSInteger a, CGFloat b) { 46 | 47 | if ((CGFloat)a > b) { 48 | return YES; 49 | } else { 50 | return NO; 51 | } 52 | }; 53 | ``` 54 | 55 | ## block的类型有哪些? 56 | 可以通过class方法查看具体类型,最终都是继承自NSBlock->NSObject 57 | + __NSGlobalBlock__ (_NSConcreteGlobalBlock) 存放在数据区(全局) 58 | + 没有访问auto变量(可以访问全局变量、静态变量,只要没访问auto变量就好) 59 | + __NSStackBlock__ (_NSConcreteStackBlock) 存放在栈区 60 | + 访问了auto变量(自动局部变量) 61 | + __NSMallocBlock__ (_NSConcreteMallocBlock) 存放在堆区 62 | + __NSStackBlock__调用了copy函数 63 | 64 | __NSGlobalBlock__调用copy函数后,还是__NSGlobalBlock__类型 65 | 66 | ## block的继承关系? 67 | __ NSGlobalBlock__ -> __ NSGlobalBlock -> NSBlock -> NSObject 68 | 69 | ## block在arc环境下什么情况会自动copy? 70 | 在arc环境下,编译器会根据情况自动将栈上的block复制到堆上,比如以下情况: 71 | + block作为函数返回值时 72 | + 将block赋值给__strong指针时 73 | + block作为Cocoa API中方法名含有usingBlock的方法参数时 74 | + block作为GCD API的方法参数时 75 | 76 | ## block的属性修饰词为什么是copy?使用block有哪些使用注意? 77 | + block一旦没有进行copy操作,就不会在堆上 78 | + 使用注意:循环引用问题 79 | 80 | ## block调用copy时实质做了什么? 81 | + 将block复制到了堆上 82 | + 将block中持有的auto变量也复制到了堆上,如果是对象就捕获其指针 83 | + block->person(Block_byref)->forwarding->person对象 84 | 85 | ## __block的作用是什么?有什么使用注意点? 86 | __block的本质是,编译器会将__block变量包装成一个对象,然后将指针捕获到block结构体中,这样就可以在block内部持有/修改外部的auto变量。 87 | + __block可以用于解决block内部无法修饰auto变量值的问题 88 | + __block不能用来修饰全局变量、静态变量 89 | 90 | ## __block的__forwarding指针 91 | + 这样设计的主要原因是,当auto变量被多个block持有时,栈上的block和堆上的block可以访问同一个内存区域。(当block非全局,且被__weak修饰时为stackblock) 92 | 93 | ## block在修改NSMutableArray,需不需要添加__block? 94 | 不需要,往NSMutableArray中添加数据时没有修改该变量的值 95 | 96 | ## block会导致循环引用,解决方案是什么? 97 | + arc情况下 98 | + 用__weak、__unsafe_unretained解决 99 | + 用__block解决(我觉得这个解决方式很蹩脚,面试时不要说) 100 | + mrc情况下 101 | + 用__unsafe_unretained、__block解决(mrc情况下没有__weak、__block主要是借助runtime的一个特性) -------------------------------------------------------------------------------- /docs/02-04、多线程 L10.md: -------------------------------------------------------------------------------- 1 | [TOC] 2 | 3 | # 多线程 4 | ## 基本概念 5 | ### 什么是多线程? 6 | + 多条线程同时执行任务 7 | 8 | ### 串行、并行、同步、异步 9 | - 串行:执行多个任务时,各个任务按顺序执行,完成一个之后才能进行下一个 10 | - 并行:执行多个任务时,多个任务可以同时执行。 11 | - 同步: 12 | - 在当前任务执行结束之前,会一直等待,直到当前任务完成之后再继续执行 13 | - 不具备开启新线程的能力 14 | - 异步: 15 | - 在当前任务执行结束之前,它不会做任何等待,可以继续执行任务。 16 | - 具备开启新线程的能力 17 | 18 | ## 常见方案 19 | ### iOS的多线程方案有哪几种?你更倾向于哪一种? 20 | + pthread、NSThread、GCD、NSOperation 21 | + 平时使用GCD较多,NSOperation用的较少 22 | 23 | ### GCD跟NSOperation的区别,以及各自的优势? 24 | + GCD更接近底层,而NSOperationQueue则更高级抽象,所以GCD在追求性能的底层操作来说,是速度最快的。 25 | + 从异步操作之间的事务性,顺序行,依赖关系。GCD需要自己写更多的代码来实现,而NSOperationQueue已经内建了这些支持 26 | + 如果异步操作的过程需要更多的被交互和UI呈现出来,NSOperationQueue会是一个更好的选择。底层代码中,任务之间不太互相依赖,而需要更高的并发能力,GCD则更有优势 27 | 28 | ### NSOperation添加依赖 29 | ``` 30 | // 添加依赖 31 | [op2 addDependency:op1]; // 让op2 依赖于 op1,则先执行op1,在执行op2 32 | ``` 33 | 34 | ### 你在项目中使用过GCD吗? 35 | + 使用GCD做延时操作(dispatch_after)、线程切换操作( dispatch_async)较多 36 | + 将下载任务异步的放在队列中,等任务执行完成后,在里面切换线程 37 | + dispatch_group完成多任务文件上传,然后同步文件地址到服务器 38 | 39 | ### 多任务上传,然后汇总数据上传服务器的(或者下载多张图片,然后合并成一张图片) 40 | + 队列组实现,dispatch_group_async、dispatch_group_notify(dispatch_group_enter) 41 | + 队列优先级,GCD、NSOperation 42 | + NSOperation设置依赖 43 | + 异步串行队列 44 | 45 | [dispatch_group_enter](https://www.jianshu.com/p/ed985874da3e) 46 | 47 | ### GCD 的队列类型 48 | + 串行队列 49 | + 并行队列 50 | 51 | ## 线程安全 52 | ### 信号量(待补充) 53 | - 待补充 54 | 55 | ### 多线程的安全隐患? 56 | + 资源共享 57 | + 一块资源可能被多个线程共享,多个线程可能会访问同一块资源 58 | + 多个线程访问同一个对象、变量、文件 59 | + 当多个线程访问同一块资源时,就可能引发数据错乱和数据安全问题 60 | 61 | ### 线程安全的处理手段有哪些? 62 | + 使用线程同步技术(即加锁) 63 | + OSSpinLock 64 | + os_unfair_lock 65 | + pthread_mutex 66 | + dispatch_semaphore 67 | + dispatch_queue(DISPATCH_QUEUEE_SERIAL) 68 | + NSLock 69 | + NSRecursiveLock 70 | + NSCondition 71 | + NSConditionLock 72 | + @synchronized 73 | + 使用串行队列 74 | + dispatch_queue,在子线程中同步执行串行队列 75 | 76 | ## 线程锁 77 | ### 锁的分类 78 | + 自旋锁,等待锁的线程会处于忙等(busy-wait)状态,一直占用着CPU资源 79 | + OSSpinLock 80 | + 互斥锁,等待锁的线程会进入休眠状态 81 | + pthread_mutex (PTHREAD_MUTEX_NORMAL类型) 82 | + NSLock 83 | + 递归锁,可以多次加锁,然后再依次解锁 84 | + pthread_mutex (PTHREAD_MUTEX_RECURSIVE类型) 85 | + NSRecursiveLock 86 | + @synchronized 87 | + 条件锁,可以设置加锁条件 88 | + NSCondition 89 | + NSConditionLock 90 | + 信号量 91 | + dispatch_semaphore 92 | + os_unfair_lock 93 | + 网传是自旋锁,李明杰说是互斥锁,但是没有文献支持 94 | 95 | ### 锁的性能 96 | + 性能从高到低 97 | + os_unfair_lock 98 | + OSSpinLock 99 | + dispatch_semaphore 100 | + pthread_mutex 101 | + dispatch_queue(DISPATCH_QUEUE_SERIAL) 102 | + NSLock 103 | + NSCondition 104 | + pthread_mutex(recursive) 105 | + NSRecursiveLock 106 | + NSConditionLock 107 | + @synchronized 108 | + 这个数据来自李明杰,没有验证过程,仅供参考,部分数据可以看[这里](https://blog.ibireme.com/2016/01/16/spinlock_is_unsafe_in_ios/) 109 | 110 | ### 什么情况使用自旋锁比较划算? 111 | + 预计线程等待锁的时间很短 112 | + 加锁的代码(临界区)经常被调用,但竞争情况很少发生 113 | + CPU资源不紧张 114 | + 多核处理器 115 | 116 | ### 什么情况使用互斥锁比较划算? 117 | + 预计线程等待锁的时间较长 118 | + 单核处理器 119 | + 临界区有IO操作 120 | + 临界区代码复杂或者循环量大 121 | + 临界区竞争非常激烈 122 | 123 | ### 什么是“死锁” 124 | + 多个进程因循环等待资源而造成无法执行的现象 125 | + 死锁会造成进程无法执行,同时会造成系统资源的极大浪费(资源无法释放) 126 | + A、B两个线程,分别对M、N两个资源加锁,但是又都在相互等待对方所持有的另外一个资源 127 | 128 | + https://blog.csdn.net/u010828718/article/details/50463083 129 | 130 | ### “死锁”的四个必要条件 131 | + 互斥使用:一个资源每次只能被一个进程使用,即在一段时间内某 资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。 132 | + 请求与保持:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源 已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。 133 | + 不可剥夺:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能 由获得该资源的进程自己来释放(只能是主动释放)。 134 | + 循环等待: 若干进程间形成首尾相接循环等待资源的关系 135 | 136 | + https://www.cnblogs.com/bopo/p/9228834.html 137 | 138 | 139 | ### 什么是“优先级反转” 140 | + A任务优先级较低,先对相应的资源C加锁 141 | + B任务优先级较高,在获取资源C时发现被加锁了,进入忙等状态 142 | + cpu发现B的优先级高,就加大算力给B,导致A的任务无法执行完毕,双方效率降低 143 | 144 | ### “优先级反转”算不算“死锁” 145 | + 需要依赖等待的类型,如果是忙等,那就是死锁,如果是阻塞,那就不是(群友说的,还有待验证) 146 | 147 | # 参考文章 148 | [iOS 多线程:『NSOperation、NSOperationQueue』详尽总结](https://www.jianshu.com/p/4b1d77054b35) 149 | 150 | [iOS 多线程:『GCD』详尽总结](https://www.jianshu.com/p/2d57c72016c6) -------------------------------------------------------------------------------- /docs/02-05、内存管理 L11.md: -------------------------------------------------------------------------------- 1 | https://hit-alibaba.github.io/interview/iOS/ObjC-Basic/MM.html 2 | 3 | [TOC] 4 | 5 | # Objective-C 中的内存分配 6 | - 在 Objective-C 中,对象通常是使用 alloc 方法在堆上创建的。 [NSObject alloc] 方法会在堆上分配一块内存,按照NSObject的内部结构填充这块儿内存区域。 7 | - 一旦对象创建完成,就不可能再移动它了。因为很可能有很多指针都指向这个对象,这些指针并没有被追踪。因此没有办法在移动对象的位置之后更新全部的这些指针。 8 | 9 | # MRC 与 ARC 10 | Objective-C中提供了两种内存管理机制:MRC(MannulReference Counting)和 ARC(Automatic Reference Counting),分别提供对内存的手动和自动管理,来满足不同的需求。现在苹果推荐使用 ARC 来进行内存管理。 11 | 12 | ## MRC 13 | ### 对象操作的四个类别 14 | 15 | 对象操作 | OC中对应的方法 | 对应的 retainCount 变化 16 | ---|--- | --- 17 | 生成并持有对象 | alloc/new/copy/mutableCopy等 | +1 18 | 持有对象 | retain | +1 19 | 释放对象 | release | -1 20 | 废弃对象 | dealloc | 0 21 | 22 | ### 四个法则 23 | - 自己生成的对象,自己持有。 24 | - 非自己生成的对象,自己也能持有。 25 | - 不在需要自己持有对象的时候,释放。 26 | - 非自己持有的对象无需释放。 27 | 28 | 如下是四个黄金法则对应的代码示例: 29 | ``` 30 | /* 31 | * 自己生成并持有该对象 32 | */ 33 | id obj0 = [[NSObeject alloc] init]; 34 | id obj1 = [NSObeject new]; 35 | ``` 36 | ``` 37 | /* 38 | * 持有非自己生成的对象 39 | */ 40 | id obj = [NSArray array]; // 非自己生成的对象,且该对象存在,但自己不持有 41 | [obj retain]; // 自己持有对象 42 | ``` 43 | ``` 44 | /* 45 | * 不在需要自己持有的对象的时候,释放 46 | */ 47 | id obj = [[NSObeject alloc] init]; // 此时持有对象 48 | [obj release]; // 释放对象 49 | /* 50 | * 指向对象的指针仍就被保留在obj这个变量中 51 | * 但对象已经释放,不可访问 52 | */ 53 | ``` 54 | ``` 55 | /* 56 | * 非自己持有的对象无法释放 57 | */ 58 | id obj = [NSArray array]; // 非自己生成的对象,且该对象存在,但自己不持有 59 | [obj release]; // ~~~此时将运行时crash 或编译器报error~~~ 非 ARC 下,调用该方法会导致编译器报 issues。此操作的行为是未定义的,可能会导致运行时 crash 或者其它未知行为 60 | ``` 61 | 其中 ```非自己生成的对象,且该对象存在,但自己不持有``` 这个特性是使用autorelease来实现的,示例代码如下: 62 | ``` 63 | - (id) getAObjNotRetain { 64 | id obj = [[NSObject alloc] init]; // 自己持有对象 65 | [obj autorelease]; // 取得的对象存在,但自己不持有该对象 66 | return obj; 67 | } 68 | ``` 69 | ``` autorelease ``` 使得对象在超出生命周期后能正确的被释放(通过调用release方法)。在调用```release``` 后,对象会被立即释放,而调用 ```autorelease``` 后,对象不会被立即释放,而是注册到 ```autoreleasepool``` 中,经过一段时间后 ```pool```结束,此时调用release方法,对象被释放。 70 | 71 | 在MRC的内存管理模式下,与对变量的管理相关的方法有:retain, release 和 autorelease。retain 和 release 方法操作的是引用记数,当引用记数为零时,便自动释放内存。并且可以用 NSAutoreleasePool 对象,对加入自动释放池(autorelease 调用)的变量进行管理,当 drain 时回收内存。 72 | 73 | ## ARC 74 | ARC 是苹果引入的一种自动内存管理机制,会根据引用计数自动监视对象的生存周期,实现方式是在编译时期自动在已有代码中插入合适的内存管理代码以及在 Runtime 做一些优化。 75 | 76 | ### 变量标识符 77 | 在ARC中与内存管理有关的变量标识符,有下面几种: 78 | - __strong 79 | - __weak 80 | - __unsafe_unretained 81 | - __autoreleasing 82 | 83 | __strong 是默认使用的标识符。只要还有一个强指针指向某个对象,这个对象就会一直存活。 84 | 85 | __weak 声明这个引用不会保持被引用对象的存活,如果对象没有强引用了,弱引用会被置为 nil 86 | 87 | __unsafe_unretained 声明这个引用不会保持被引用对象的存活,如果对象没有强引用了,它不会被置为 nil。如果它引用的对象被回收掉了,该指针就变成了野指针。 88 | 89 | __autoreleasing 用于标示使用引用传值的参数(id *),在函数返回时会被自动释放掉。 90 | 91 | 变量标识符的用法如下: 92 | ``` 93 | Number* __strong num = [[Number alloc] init]; 94 | ``` 95 | 96 | 注意 __strong 的位置应该放到 * 和变量名中间,放到其他的位置严格意义上说是不正确的,只不过编译器不会报错。 97 | 98 | ### 属性标识符 99 | 类中的属性也可以加上标志符: 100 | ``` 101 | @property (assign/retain/strong/weak/unsafe_unretained/copy) Number* num 102 | ``` 103 | 104 | #### assign 105 | assign表明 setter 仅仅是一个简单的赋值操作,通常用于基本的数值类型,例如CGFloat和NSInteger。 106 | 107 | #### strong 108 | strong 表明属性定义一个拥有者关系。当给属性设定一个新值的时候,首先这个值进行 retain ,旧值进行 release ,然后进行赋值操作。 109 | 110 | #### weak 111 | weak 表明属性定义了一个非拥有者关系。当给属性设定一个新值的时候,这个值不会进行 retain,旧值也不会进行 release, 而是进行类似 assign 的操作。不过当属性指向的对象被销毁时,该属性会被置为nil。 112 | 113 | #### unsafe_unretained 114 | unsafe_unretained 的语义和 assign 类似,不过是用于对象类型的,表示一个非拥有(unretained)的,同时也不会在对象被销毁时置为nil的(unsafe)关系。 115 | 116 | #### copy 117 | copy 类似于 strong,不过在赋值时进行 copy 操作而不是 retain 操作。通常在需要保留某个不可变对象(NSString最常见),并且防止它被意外改变时使用。 118 | 119 | #### 错误使用属性标识符的后果 120 | 如果我们给一个原始类型设置 strong\weak\copy ,编译器会直接报错: 121 | > Property with 'retain (or strong)' attribute must be of object type 122 | 123 | 设置为 unsafe_unretained 倒是可以通过编译,只是用起来跟 assign 也没有什么区别。 124 | 125 | 反过来,我们给一个 NSObject 属性设置为 assign,编译器会报警: 126 | 127 | > Assigning retained object to unsafe property; object will be released after assignment 128 | 129 | 正如警告所说的,对象在赋值之后被立即释放,对应的属性也就成了野指针,运行时跑到属性有关操作会直接崩溃掉。和设置成 unsafe_unretained 是一样的效果(设置成 weak 不会崩溃)。 130 | 131 | #### unsafe_unretained 的用处 132 | unsafe_unretained 差不多是实际使用最少的一个标识符了,在使用中它的用处主要有下面几点: 133 | 1. 兼容性考虑。iOS4 以及之前还没有引入 weak,这种情况想表达弱引用的语义只能使用 unsafe_unretained。这种情况现在已经很少见了。 134 | 2. 性能考虑。使用 weak 对性能有一些影响,因此对性能要求高的地方可以考虑使用 unsafe_unretained 替换 weak。一个例子是 [YYModel](https://github.com/ibireme/YYModel/blob/master/YYModel/NSObject%2BYYModel.m) 的实现,为了追求更高的性能,其中大量使用 unsafe_unretained 作为变量标识符。 135 | 136 | # 引用循环 137 | 当两个对象互相持有对方的强引用,并且这两个对象的引用计数都不是0的时候,便造成了引用循环。 138 | 139 | 要想破除引用循环,可以从以下几点入手: 140 | - 注意变量作用域,使用 autorelease 让编译器来处理引用 141 | - 使用弱引用(weak) 142 | - 当实例变量完成工作后,将其置为nil 143 | 144 | # Autorelease Pool 145 | Autorelase Pool 提供了一种可以允许你向一个对象延迟发送release消息的机制。当你想放弃一个对象的所有权,同时又不希望这个对象立即被释放掉(例如在一个方法中返回一个对象时),Autorelease Pool 的作用就显现出来了。 146 | 147 | 所谓的延迟发送release消息指的是,当我们把一个对象标记为autorelease时: 148 | ``` 149 | NSString* str = [[[NSString alloc] initWithString:@"hello"] autorelease]; 150 | ``` 151 | 152 | 这个对象的 retainCount 会+1,但是并不会发生 release。当这段语句所处的 autoreleasepool 进行 drain 操作时,所有标记了 autorelease 的对象的 retainCount 会被 -1。即 release 消息的发送被延迟到 pool 释放的时候了。 153 | 154 | 在 ARC 环境下,苹果引入了 @autoreleasepool 语法,不再需要手动调用 autorelease 和 drain 等方法。 155 | 156 | ## Autorelease Pool 的用处 157 | 在 ARC 下,我们并不需要手动调用 autorelease 有关的方法,甚至可以完全不知道 autorelease 的存在,就可以正确管理好内存。因为 Cocoa Touch 的 Runloop 中,每个 runloop circle 中系统都自动加入了 Autorelease Pool 的创建和释放。 158 | 159 | 当我们需要创建和销毁大量的对象时,使用手动创建的 autoreleasepool 可以有效的避免内存峰值的出现。因为如果不手动创建的话,外层系统创建的 pool 会在整个 runloop circle 结束之后才进行 drain,手动创建的话,会在 block 结束之后就进行 drain 操作。详情请参考苹果官方文档。一个普遍被使用的例子如下: 160 | 161 | ``` 162 | for (int i = 0; i < 100000000; i++) 163 | { 164 | @autoreleasepool 165 | { 166 | NSString* string = @"ab c"; 167 | NSArray* array = [string componentsSeparatedByString:string]; 168 | } 169 | } 170 | ``` 171 | 172 | 如果不使用 autoreleasepool ,需要在循环结束之后释放 100000000 个字符串,如果 使用的话,则会在每次循环结束的时候都进行 release 操作。 173 | 174 | 175 | ## Autorelease Pool 进行 Drain 的时机 176 | 如上面所说,系统在 runloop 中创建的 autoreleaspool 会在 runloop 一个 event 结束时进行释放操作。我们手动创建的 autoreleasepool 会在 block 执行完成之后进行 drain 操作。需要注意的是: 177 | 178 | - 当 block 以异常(exception)结束时,pool 不会被 drain 179 | - Pool 的 drain 操作会把所有标记为 autorelease 的对象的引用计数减一,但是并不意味着这个对象一定会被释放掉,我们可以在 autorelease pool 中手动 retain 对象,以延长它的生命周期(在 MRC 中)。 180 | 181 | ## main.m 中 Autorelease Pool 的解释 182 | 大家都知道在 iOS 程序的 main.m 文件中有类似这样的语句: 183 | ``` 184 | int main(int argc, char * argv[]) { 185 | @autoreleasepool { 186 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 187 | } 188 | } 189 | ``` 190 | 191 | 在面试中问到有关 autorelease pool 有关的知识也多半会问一下,这里的 pool 有什么作用,能不能去掉之类。在这里我们分析一下。 192 | 193 | 根据苹果官方文档, UIApplicationMain 函数是整个 app 的入口,用来创建 application 对象(单例)和 application delegate。尽管这个函数有返回值,但是实际上却永远不会返回,当按下 Home 键时,app 只是被切换到了后台状态。 194 | 195 | 同时参考苹果关于 Lifecycle 的官方文档,UIApplication 自己会创建一个 main run loop,我们大致可以得到下面的结论: 196 | 197 | 1. main.m 中的 UIApplicationMain 永远不会返回,只有在系统 kill 掉整个 app 时,系统会把应用占用的内存全部释放出来。 198 | 2. 因为(1), UIApplicationMain 永远不会返回,这里的 autorelease pool 也就永远不会进入到释放那个阶段 199 | 3. 在 (2) 的基础上,假设有些变量真的进入了 main.m 里面这个 pool(没有被更内层的 pool 捕获),那么这些变量实际上就是被泄露的。这个 autorelease pool 等于是把这种泄露情况给隐藏起来了。 200 | 4. UIApplication 自己会创建 main run loop,在 Cocoa 的 runloop 中实际上也是自动包含 autorelease pool 的,因此 main.m 当中的 pool 可以认为是没有必要的。 201 | 202 | 在基于 AppKit 框架的 Mac OS 开发中, main.m 当中就是不存在 autorelease pool 的,也进一步验证了我们得到的结论。不过因为我们看不到更底层的代码,加上苹果的文档中不建议修改 main.m ,所以我们也没有理由就直接把它删掉(亲测,删掉之后不影响 App 运行,用 Instruments 也看不到泄露)。 203 | 204 | ## Autorelease Pool 与函数返回值 205 | 如果一个函数的返回值是指向一个对象的指针,那么这个对象肯定不能在函数返回之前进行 release,这样调用者在调用这个函数时得到的就是野指针了,在函数返回之后也不能立刻就 release,因为我们不知道调用者是不是 retain 了这个对象,如果我们直接 release 了,可能导致后面在使用这个对象时它已经成为 nil 了。 206 | 207 | 为了解决这个纠结的问题, Objective-C 中对对象指针的返回值进行了区分,一种叫做 retained return value,另一种叫做 unretained return value。前者表示调用者拥有这个返回值,后者表示调用者不拥有这个返回值,按照“谁拥有谁释放”的原则,对于前者调用者是要负责释放的,对于后者就不需要了。 208 | 209 | 按照苹果的命名 convention,以 alloc, copy, init, mutableCopy 和 new 这些方法打头的方法,返回的都是 retained return value,例如 [[NSString alloc] initWithFormat:],而其他的则是 unretained return value,例如 [NSString stringWithFormat:]。我们在编写代码时也应该遵守这个 convention。 210 | 211 | 我们分别在 MRC 和 ARC 情况下,分析一下两种返回值类型的区别。 212 | 213 | ### MRC 214 | 在 MRC 中我们需要关注这两种函数返回类型的区别,否则可能会导致内存泄露。 215 | 216 | 对于 retained return value,需要负责释放 217 | 218 | 假设我们有一个 property 定义如下: 219 | 220 | ``` 221 | @property (nonatomic, retain) NSObject *property; 222 | ``` 223 | 224 | 在对其赋值的时候,我们应该使用: 225 | ``` 226 | self.property = [[[NSObject alloc] init] autorelease]; 227 | ``` 228 | 229 | 然后在 dealloc 方法中加入: 230 | ``` 231 | [_property release]; 232 | _property = nil; 233 | ``` 234 | 235 | 这样内存的情况大体是这样的: 236 | 1. init 把 retain count 增加到 1 237 | 2. 赋值给 self.property ,把 retain count 增加到 2 238 | 3. 当 runloop circle 结束时,autorelease pool 执行 drain,把 retain count 减为 1 239 | 4. 当整个对象执行 dealloc 时, release 把 retain count 减为 0,对象被释放 240 | 241 | 可以看到没有内存泄露发生。 242 | 243 | 如果我们只是使用: 244 | 245 | ``` 246 | self.property = [[NSObject alloc] init]; 247 | ``` 248 | 这一条语句会导致 retain count 增加到 2,而我们少执行了一次 release,就会导致 retain count 不能被减为 0 。 249 | 250 | 另外,我们也可以使用临时变量: 251 | 252 | ``` 253 | NSObject * a = [[NSObject alloc] init]; 254 | self.property = a; 255 | [a release]; 256 | ``` 257 | 258 | 这种情况,因为对 a 执行了一次 release,所有不会出现上面那种 retain count 不能减为 0 的情况。 259 | 260 | 注意:现在大家基本都是 ARC 写的比较多,会忽略这一点,但是根据上面的内容,我们看到在 MRC 中直接对 self.proprety 赋值和先赋给临时变量,再赋值给 self.property,确实是有区别的!我在面试中就被问到这一点了。 261 | 262 | 我们在编写自己的代码时,也应该遵守上面的原则,同样是使用 autorelease: 263 | 264 | ``` 265 | // 注意函数名的区别 266 | + (MyCustomClass *) myCustomClass 267 | { 268 | return [[[MyCustomClass alloc] init] autorelease]; // 需要 autorelease 269 | } 270 | - (MyCustomClass *) initWithName:(NSString *) name 271 | { 272 | return [[MyCustomClass alloc] init]; // 不需要 autorelease 273 | } 274 | ``` 275 | 276 | 对于 unretained return value,不需要负责释放 277 | 278 | 当我们调用非 alloc,init 系的方法来初始化对象时(通常是工厂方法),我们不需要负责变量的释放,可以当成普通的临时变量来使用: 279 | 280 | ``` 281 | NSString *name = [NSString stringWithFormat:@"%@ %@", firstName, lastName]; 282 | self.name = name 283 | // 不需要执行 [name release] 284 | ``` 285 | 286 | ### ARC 287 | 在 ARC 中我们完全不需要考虑这两种返回值类型的区别,ARC 会自动加入必要的代码,因此我们可以放心大胆地写: 288 | 289 | ``` 290 | self.property = [[NSObject alloc] init]; 291 | self.name = [NSString stringWithFormat:@"%@ %@", firstName, lastName]; 292 | ``` 293 | 294 | 以及在自己写的函数中: 295 | 296 | ``` 297 | + (MyCustomClass *) myCustomClass 298 | { 299 | return [[MyCustomClass alloc] init]; // 不用 autorelease 300 | } 301 | ``` 302 | 303 | 这些写法都是 OK 的,也不会出现内存问题。 304 | 305 | 为了进一步理解 ARC 是如何做到这一点的,我们可以参考 Clang 的文档。 306 | 307 | 对于 retained return value, Clang 是这样做的: 308 | 309 | > When returning from such a function or method, ARC retains the value at the point of evaluation of the return statement, before leaving all local scopes. 310 | 311 | > When receiving a return result from such a function or method, ARC releases the value at the end of the full-expression it is contained within, subject to the usual optimizations for local values. 312 | 313 | 可以看到基本上 ARC 就是帮我们在代码块结束的时候进行了 release: 314 | 315 | ``` 316 | NSObject * a = [[NSObject alloc] init]; 317 | self.property = a; 318 | //[a release]; 我们不需要写这一句,因为 ARC 会帮我们把这一句加上 319 | ``` 320 | 321 | 对于 unretained return value: 322 | 323 | > When returning from such a function or method, ARC retains the value at the point of evaluation of the return statement, then leaves all local scopes, and then balances out the retain while ensuring that the value lives across the call boundary. In the worst case, this may involve an autorelease, but callers must not assume that the value is actually in the autorelease pool. 324 | 325 | > ARC performs no extra mandatory work on the caller side, although it may elect to do something to shorten the lifetime of the returned value. 326 | 327 | 这个和我们之前在 MRC 中做的不是完全一样。ARC 会把对象的生命周期延长,确保调用者能拿到并且使用这个返回值,但是并不一定会使用 autorelease,文档写的是在 worst case 的情况下才可能会使用,因此调用者不能假设返回值真的就在 autorelease pool 中。从性能的角度,这种做法也是可以理解的。如果我们能够知道一个对象的生命周期最长应该有多长,也就没有必要使用 autorelease 了,直接使用 release 就可以。如果很多对象都使用 autorelease 的话,也会导致整个 pool 在 drain 的时候性能下降。 328 | 329 | ## weak 与 autorelease 330 | 众所周知,weak 不会持有对象,当给一个 weak 赋以一个自己生成的对象(即上面提到的 retained return value)后,对象会立马被释放。 331 | 332 | 一个很常见的 warning 就是 Assigning retained object to weak variable, object will be released after assignment. 333 | 334 | 但是我们前面也提到了,可以持有非自己生成的对象,这通过 autorelease 实现。 335 | 336 | 那么如果一个 weak 被赋以一个非自己生成的对象(即上面提到的 unretained return value)呢?代码如下: 337 | 338 | ``` 339 | NSNumber __weak *number = [NSNumber numberWithInt:100]; 340 | NSLog(@"number = %@", number); 341 | ``` 342 | 343 | 这种情况下是可以正确打印值的。 344 | 345 | clang的文档 是这么说的:这种情况下,weak 并不会立即释放,而是会通过 objc_loadWeak 这个方法注册到 AutoreleasePool 中,以延长生命周期。 346 | 347 | # ARC 下是否还有必要在 dealloc 中把属性置为 nil? 348 | 为了解决这个问题,首先让我们理清楚属性是个什么存在。属性(property) 实际上就是一种语法糖,每个属性背后都有实例变量(Ivar)做支持,编译器会帮我们自动生成有关的 setter 和 getter,对于下面的 property: 349 | 350 | ``` 351 | @interface Counter : NSObject 352 | @property (nonatomic, retain) NSNumber *count; 353 | @end; 354 | ``` 355 | 生成的 getter 和 setter 类似下面这样: 356 | ``` 357 | - (NSNumber *)count { 358 | return _count; 359 | } 360 | - (void)setCount:(NSNumber *)newCount { 361 | [newCount retain]; 362 | [_count release]; 363 | // Make the new assignment. 364 | _count = newCount; 365 | } 366 | ``` 367 | 368 | Property 这部分对于 MRC 和 ARC 都是适用的。 369 | 370 | 有了这部分基础,我们再来理解一下把属性置为 nil 这个步骤。首先要明确一点,在 MRC 下,我们并不是真的把属性置为 nil,而是把 Ivar 置为 nil。 371 | 372 | ``` 373 | [_property release]; 374 | _property = nil; 375 | ``` 376 | 如果用 self.property 的话还会调用 setter,里面可能存在某些不应该在 dealloc 时运行的代码。 377 | 378 | 对于 ARC 来说,系统会自动在 dealloc 的时候把所有的 Ivar 都执行 release,因此我们也就没有必要在 dealloc 中写有关 release 的代码了。 379 | 380 | # 在 ARC 下把变量置为 nil 有什么效果?什么情况下需要把变量置为 nil? 381 | 在上面有关 property 的内容基础上,我们知道用: 382 | 383 | ``` 384 | self.property = nil 385 | ``` 386 | 387 | 实际上就是手动执行了一次 release。而对于临时变量来说: 388 | 389 | ``` 390 | NSObject *object = [[NSObject alloc] init]; 391 | object = nil; 392 | ``` 393 | 394 | 置为 nil 这一句其实没什么用(除了让 object 在下面的代码里不能再使用之外),因为上面我们讨论过 ,ARC 下的临时变量是受到 Autorelease Pool 的管理的,会自动释放。 395 | 396 | 因为 ARC 下我们不能再使用 release 函数,把变量置为 nil 就成为了一种释放变量的方法。真正需要我们把变量置为 nil 的,通常就是在使用 block 时,用于破除循环引用: 397 | 398 | ``` 399 | MyViewController * __block myController = [[MyViewController alloc] init…]; 400 | // ... 401 | myController.completionHandler = ^(NSInteger result) { 402 | [myController dismissViewControllerAnimated:YES completion:nil]; 403 | myController = nil; 404 | }; 405 | ``` 406 | 在 YTKNetwork 这个项目中,也可以看到类似的代码: 407 | 408 | ``` 409 | - (void)clearCompletionBlock { 410 | // nil out to break the retain cycle. 411 | self.successCompletionBlock = nil; 412 | self.failureCompletionBlock = nil; 413 | } 414 | ``` 415 | 416 | # ARC 在运行时期的优化 417 | 上面提到对于 unretained return value, ARC “并不一定会使用 autorelease”,下面具体解释一下。 418 | 419 | ARC 所做的事情并不仅仅局限于在编译期找到合适的位置帮你插入合适的 release 等等这样的内存管理方法,其在运行时期也做了一些优化,如下是两个优化的例子: 420 | 1. 合并对称的引用计数操作。比如将 +1/-1/+1/-1 直接置为 0. 421 | 2. 巧妙地跳过某些情况下 autorelease 机制的调用。 422 | 423 | 其中第二个优化,是 ARC 针对 autorelease 返回值提供的一套优化策略,大体的流程如下: 424 | 425 | 当方法全部基于 ARC 实现时,在方法 return 的时候,ARC 会调用 objc_autoreleaseReturnValue() 以替代 MRC 下的 autorelease。在 MRC 下需要 retain 的位置,ARC 会调用 objc_retainAutoreleasedReturnValue()。因此下面的 ARC 代码: 426 | ``` 427 | + (instancetype)createSark { 428 | return [self new]; 429 | } 430 | // caller 431 | Sark *sark = [Sark createSark]; 432 | ``` 433 | 实际上会被改写成类似这样: 434 | ``` 435 | + (instancetype)createSark { 436 | id tmp = [self new]; 437 | return objc_autoreleaseReturnValue(tmp); // 代替我们调用autorelease 438 | } 439 | // caller 440 | id tmp = objc_retainAutoreleasedReturnValue([Sark createSark]) // 代替我们调用retain 441 | Sark *sark = tmp; 442 | objc_storeStrong(&sark, nil); // 相当于代替我们调用了release 443 | ``` 444 | 有了这个基础,ARC 可以使用一些优化技术。在调用 objc_autoreleaseReturnValue() 时,会在栈上查询 return address 以确定 return value 是否会被直接传给 objc_retainAutoreleasedReturnValue()。 如果没传,说明返回值不能直接从提供方发送给接收方,这时就会调用 autorelease。反之,如果返回值能顺利的从提供方传送给接收方,那么就会直接跳过 autorelease 过程,并且修改 return address 以跳过 objc_retainAutoreleasedReturnValue()过程,这样就跳过了整个 autorelease 和 retain的过程。 445 | > 核心思想:当返回值被返回之后,紧接着就需要被 retain 的时候,没有必要进行 autorelease + retain,直接什么都不要做就好了。 446 | 447 | 另外,当函数的调用方是非 ARC 环境时,ARC 还会进行更多的判断,在这里不再详述,详见 《黑幕背后的 Autorelease》。 448 | 449 | # 关于如何写一个检测循环引用的工具 450 | Instrument 为我们提供了 Allocations/Leaks 这样好用的工具用来检测 memory leak 的工具。如下是内存泄露的两种类型: 451 | - Leaked memory: Memory unreferenced by your application that cannot be used again or freed (also detectable by using the Leaks instrument). 452 | - Abandoned memory: Memory still referenced by your application that has no useful purpose. 453 | 454 | 其中 Leaks 工具主要用来检测 Leaked memory,在 MRC 时代 程序员会经常忘记写 release 方法导致内存泄露,在 ARC 时代这种已经不太常见。(ARC时代 主要的Leaked Memory 来自于底层 C 语言以及 一些由 C 写成的底层库,往往会因为忘记手工 free 而导致 leak )。 455 | 456 | Allocations 工具主要用来检测 Abandoned memory. 主要思路是在一个时间切片内检测对象的声明周期以观察内存是否会无限增长。通过 hook 掉 alloc,dealloc,retain,release 等方法,来记录对象的生命周期。 457 | 458 | # MLeaksFinder实现原理 459 | [MLeaksFinder 新特性](http://wereadteam.github.io/2016/07/20/MLeaksFinder2/) 460 | 461 | [MLLeaksFinder](https://blog.csdn.net/weixin_34417200/article/details/88017659) 462 | 463 | ## 参考资料 464 | - [Objective-C内存管理MRC与ARC](https://blog.csdn.net/fightingbull/article/details/8098133) 465 | - [10个Objective-C基础面试题,iOS面试必备](https://www.oschina.net/news/42288/10-objective-c-interview) 466 | - [黑幕背后的 Autorelease](http://blog.sunnyxx.com/2014/10/15/behind-autorelease/) 467 | - [Objective-C Autorelease Pool 的实现原理](http://blog.leichunfeng.com/blog/2015/05/31/objective-c-autorelease-pool-implementation-principle/) 468 | - [How does objc_retainAutoreleasedReturnValue work?](https://www.galloway.me.uk/2012/02/how-does-objc_retainautoreleasedreturnvalue-work/) 469 | - https://stackoverflow.com/questions/9784762/strong-weak-retain-unsafe-unretained-assign 470 | - https://stackoverflow.com/questions/29350634/ios-autoreleasepool-in-main-and-arc-alloc-release 471 | - https://stackoverflow.com/questions/6588211/why-do-the-ios-main-m-templates-include-a-return-statement-and-an-autorelease-po 472 | - https://stackoverflow.com/questions/2702548/if-the-uiapplicationmain-never-returns-then-when-does-the-autorelease-pool-get 473 | - https://stackoverflow.com/questions/6055274/use-autorelease-when-setting-a-retain-property-using-dot-syntax 474 | - https://stackoverflow.com/questions/17601274/arc-and-autorelease 475 | - https://stackoverflow.com/questions/8292060/arc-equivalent-of-autorelease 476 | - https://stackoverflow.com/questions/7906804/do-i-set-properties-to-nil-in-dealloc-when-using-arc 477 | - http://wereadteam.github.io/2016/02/22/MLeaksFinder/?from=singlemessage&isappinstalled=0 478 | - http://clang.llvm.org/docs/AutomaticReferenceCounting.html#arc-runtime-objc-loadweak -------------------------------------------------------------------------------- /docs/02-06、Runtime L12.md: -------------------------------------------------------------------------------- 1 | [TOC] 2 | 3 | # 什么是runtime? 4 | [苹果官方解答](https://developer.apple.com/documentation/objectivec/objective-c_runtime) 5 | [官方文档翻译](https://blog.csdn.net/coyote1994/article/details/52441513) 6 | 7 | [运行系统](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40008048) 8 | + runtime是一个运行时库,它为Objective-C语言的动态特性提供支持。OC语言中,尽可能的将对象的类别、调用方法,从编译链接时推迟到运行时,在OC代码被编译后,还需要runtime来配合执行编译后的代码。 9 | + runtime是一个运行时库,它为Objective-C语言的动态特性提供支持。OC语言中,尽可能的将对象的类别、调用方法,从编译链接时推迟到运行时,为了达到这个效果runtime起了很重要的作用(比如动态的创建类、生成类、给类添加属性方法)。 10 | 11 | --- 12 | 苹果官网: 13 | + 将尽可能多的决策从编译时和链接时推迟到运行时。 14 | + 充当着OC语言的操作系统,它使语言能够正常工作。 15 | --- 16 | 17 | + runtime是一个运行时库,他为OC语言的动态特性提供支持。可以动态的创建类、添加修改类的属性方法等,实现消息发送和消息转发。 18 | 19 | + runtime是一个运行时库,是由C、C++、汇编实现的API,他为OC语言的动态特性提供支持。调用的OC方法时,本质调用了Runtime 的API。 20 | 21 | 22 | + 运行时库:程序运行的时候所需要依赖的库 23 | 24 | 关键词:运行时库、OC语言的动态特性,动态的创建类、添加修改类的属性方法等。 25 | 26 | ## OC的动态特性: 27 | + OC的动态特性表现为了三个方面:动态类型、动态绑定、动态加载 28 | ### 动态类型 29 | + 动态类型,说简单点就是id类型。动态类型是跟静态类型相对的。像内置的明确的基本类型都属于静态类型(int、NSString等)。静态类型在 编译的时候就能被识别出来。所以,若程序发生了类型不对应,编译器就会发出警告。而动态类型就编译器编译的时候是不能被识别的,要等到运行时(run time),即程序运行的时候才会根据语境来识别。所以这里面就有两个概念要分清:编译时跟运行时。 30 | 31 | ### 动态绑定 32 | + 动态绑定(dynamic binding)貌似比较难记忆,但事实上很简单,只需记住关键词@selector/SEL即可。先来看看“函数”,对于其他一些静态语言,比如 c++,一般在编译的时候就已经将将要调用的函数的函数签名都告诉编译器了。静态的,不能改变。而在OC中,其实是没有函数的概念的,我们叫“消息机 制”,所谓的函数调用就是给对象发送一条消息。这时,动态绑定的特性就来了。OC可以先跳过编译,到运行的时候才动态地添加函数调用,在运行时才决定要调 用什么方法,需要传什么参数进去。这就是动态绑定,要实现他就必须用SEL变量绑定一个方法。最终形成的这个SEL变量就代表一个方法的引用。这里要注意 一点:SEL并不是C里面的函数指针,虽然很像,但真心不是函数指针。SEL变量只是一个整数,他是该方法的ID,@selector()就是取类方法的编号。以前的函数调用,是根据函数名,也就是 字符串去查找函数体。但现在,我们是根据一个ID整数来查找方法,整数的查找字自然要比字符串的查找快得多!所以,动态绑定的特定不仅方便,而且效率更 高。 33 | 34 | + 由于OC的动态特性,在OC中其实很少提及“函数”的概念,传统的函数一般在编译时就已经把参数信息和函数实现打包到编译后的源码中了,而在OC中最常使 用的是消息机制。调用一个实例的方法,所做的是向该实例的指针发送消息,实例在收到消息后,从自身的实现中寻找响应这条消息的方法 35 | 36 | ### 动态加载 37 | + 根据需求加载所需要的资源,这点很容易理解,对于iOS开发来说,基本就是根据不同的机型做适配。最经典的例子就是在Retina设备上加载@2x的图片,而在老一些的普通屏设备上加载原图。 38 | 39 | # Runtime用来干什么? 40 | + 在程序运行过程中,动态的创建类,动态添加、修改类的属性和方法 41 | + 遍历一个类中所有的成员变量、属性、以及所有方法 42 | + 消息传递、转发,方法交换 43 | 44 | # Runtime的应用 45 | ## 系统的应用 46 | + KVC、KVO 47 | 48 | ## 常见应用 49 | + 动态交换两个方法的实现(比如hock系统方法) 50 | + 获取对象的属性、私有属性 51 | + 通过runtime方法打印Ivar数组 52 | + 字典转模型(利用runtime遍历所有的属性或者成员变量,再利用KVC赋值) 53 | + 自动归档解档(NSKeyedArchiver归档、NSKeyedUnarchiver解档) 54 | + crash崩溃防护(利用消息转发机制)(数组、字典、Unrecognize Selector、字符串越界、KVO、通知) 55 | + 给系统类添加属性、方法(分类+关联对象,或者直接通过addIvar等方法) 56 | + 动态的创建类,NSClassFromString class<->字符串 (反射机制) 57 | 58 | # OC的消息机制 59 | + oc中的方法调用其实都是转换成了objc_msgSend函数的调用,给receriver(方法调用者)发送消息(selector方法) 60 | + objc_msgSend底层有三个阶段 61 | + 消息发送(当前类、父类中的查找) 62 | + 消息转发 63 | + 动态方法解析 64 | + 完整的消息转发机制 65 | 66 | ## 消息发送阶段(消息查找) 67 | + 先判断消息接受者是否为nil,如果为nil直接退出 68 | + 从消息接受者的cache(方法缓存)中查找,如果查到就直接调用 69 | + 再从消息接受者的class_rw_t(方法列表)中查找,如果查到就直接调用,并缓存到方法缓存列表中 70 | + 再从父类的缓存中查找,如果找到就直接调用,并缓存到消息接受者的缓存中 71 | + 再从父类的方法列表中查找,如果找到就直接调用,并缓存到消息接受者的缓存中 72 | + 重复以上操作,如果直到基类还没找到方法的实现,就进入动态方法解析阶段 73 | 74 | 75 | ## 消息转发机制 76 | 消息转发分为三个阶段,动态方法解析阶段,快速转发阶段,以及完整的消息转发阶段。 77 | 78 | [iOS Runtime 消息转发机制原理和实际用途](https://www.jianshu.com/p/fdd8f5225f0c) 79 | 80 | ![image](https://upload-images.jianshu.io/upload_images/1322408-8c20d8a1db5a2de8.jpg?imageMogr2/auto-orient/strip|imageView2/2/w/690/format/webp) 81 | 82 | ### 动态方法解析阶段 83 | + 先判断是之前是否动态解析过,如果有就进入消息转发阶段 84 | + 反之,调用```+resolveInstanceMethod:```或者```+resolveClassMethod:```方法来动态解析方法 85 | + 标记为已经动态解析,并重启消息发送阶段 86 | + 可以在其中为当前类,动态的添加方法实现 87 | 88 | ### 快速消息转发阶段 89 | + 调用```forwardingTargetForSelector:```方法,如果返回值不为nil,就调用```objc_msgSend(返回值,SEL)``` 90 | + 可以在其中返回一个可以处理该方法的对象(提供一个备用对象) 91 | 92 | ### 完整的消息转发机制 93 | + 调用```methodSignatureForSelector:```方法,在其中返回SEL方法的签名 94 | + 如果第一步返回值不为nil,调用```forwardInvocation:```方法,返回备用对象 95 | + 如果第二步返回值为空,调用```doesNotRecognizeSelector:```方法 96 | 97 | ## 浅谈iOS之weak底层实现原理 98 | weak是Runtime维护了一个hash(哈希)表,用于存储指向某个对象的所有weak指针。weak表其实是一个hash(哈希)表,Key是所指对象的地址,Value是weak指针的地址(这个地址的值是所指对象指针的地址)数组。 99 | 100 | ### weak释放为nil过程 101 | - weak被释放为nil,需要对对象整个释放过程了解,如下是对象释放的整体流程: 102 | - 1、调用objc_release 103 | - 2、因为对象的引用计数为0,所以执行dealloc 104 | - 3、在dealloc中,调用了_objc_rootDealloc函数 105 | - 4、在_objc_rootDealloc中,调用了object_dispose函数 106 | - 5、调用objc_destructInstance 107 | - 6、最后调用objc_clear_deallocating。 108 | 109 | 对象准备释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。 110 | 111 | [浅谈iOS之weak底层实现原理](https://blog.csdn.net/future_one/article/details/81606895) 112 | 113 | https://www.jianshu.com/p/f331bd5ce8f8 114 | 115 | https://blog.csdn.net/streamery/article/details/107653266 116 | 117 | # runtime的api 118 | ## 类相关的api 119 | + 动态创建一个类(参数:父类,类名,额外的内存空间) 120 | + Class objc_allocateClassPair(Class superclass,const char* name, size_t extraBytes) 121 | 122 | + 注册一个类(要在类注册之前添加成员变量) 123 | + void objc_registerClassPair(Class cls) 124 | + 销毁一个类 125 | + void objc_disposeClassPair(Class cls) 126 | + 获取isa指向的Class 127 | + Class object_getClass(id obj) 128 | + 设置isa指向的Class 129 | + Class object_setClass(id obj,Class cls) 130 | + 判断一个OC对象是否为Class 131 | + BOOL object_isClass(id obj) 132 | + 判断一个Class是否为元类 133 | + BOOL class_isMetaClass(Class cls) 134 | + 获取父类 135 | + Class class_getSuperclass(Class cls) 136 | 137 | ### 成员变量相关的api 138 | + 获取一个实例变量信息 139 | + Ivar class_getInstanceVariable(Class cls,const char *name) 140 | + 拷贝实例变量列表(最后需要调用free释放) 141 | + Ivar *class_copyIvarList(Class cls, unsigned int *outCount) 142 | + 设置和获取成员变量的值 143 | + void object_setIvar(id obj, Ivar ivar,id value) 144 | + id object_getIvar(id obj, Ivar ivar) 145 | + 动态添加成员变量(已经注册的类不能动态添加成员变量) 146 | + BOOL class_addIvar(Class cls, const char* name, size_t size,uint8_t alignment,const char *types) 147 | + 获取成员变量的相关信息 148 | + const char *ivar_getName(Ivar v) 149 | + const char *ivar_getTypeEncoding(Ivar v) 150 | 151 | ## 属性相关的api 152 | + 获取一个属性 153 | + objc_property_t class_getProperty(Class cls, const char *name) 154 | + 拷贝属性列表(最后需要调用free释放) 155 | + objc_property_t class_copyPropertyList(Class cls,unsigned int *outCount) 156 | + 动态添加属性 157 | + BOOL class_addProperty(Class cls,const char *name, const objc_property_attribute_t *attributes,unsigned int attributeCount) 158 | + 动态替换属性 159 | + void class_replaceProperty(Class cls,const char* name, const objc_property_attribute_t *attributes,unsigned int attributeCount) 160 | + 获取属性的一些信息 161 | + const char *property_getName(objc_property_t property) 162 | + const char *property_getAttributes(objc_property_t property) 163 | 164 | ## 方法相关的api 165 | + 获取一个实例方法 166 | + Method class_getInstanceMethod(Class cls,SEL name) 167 | + Method class_getClassMethod(Class cls,SEL name) 168 | + 方法实现相关操作 169 | + IMP clas_getMethodImplementation(Class cls, SEL name) 170 | + IMP method_setImplementation(Method m,IMP imp) 171 | + void method_exchangeImplementations(Method m1,Method m2) 172 | + 拷贝方法列表(最后需要调用free释放) 173 | + Method *class_copyMethodList(Class cls,unsigned int *outCount) 174 | + 动态添加方法 175 | + BOOL class_addMethod(Class cls,SEL name,IMP imp,const char *types) 176 | + 动态替换方法 177 | + IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types) 178 | + 获取方法的相关信息(带有copy的需要调用free去释放) 179 | + SEL method_getName(Method m) 180 | + IMP method_getImplementation(Method m) 181 | + const char *method_getTypeEncoding(Method m) 182 | + unsigned int method_getNumberOfArguments(Method m) 183 | + char *method_copyReturnType(Method m) 184 | + char *method_copyArgumentType(Method m,unsigned int index) 185 | + 选择器相关 186 | + const char *sel_getName(SEL sel) 187 | + SEL sel_registerName(const char *str) 188 | + 用block作为方法实现 189 | + IMP imp_implementationWithBlock(id block) 190 | + id imp_getBlock(IMP anImp) 191 | + BOOL imp_removeBlock(IMP anImp) 192 | 193 | # 常见数据结构 194 | ## Class的结构体 195 | ``` 196 | struct objc_class { 197 | Class isa; 198 | Class superclass; 199 | cache_t cache; // 方法缓存 200 | class_data_bits_t bits; // 用于获取具体的类信息 201 | } 202 | 203 | // bits & FAST_DATA_MASK 204 | struct class_rw_t { 205 | uint32_t flags; 206 | uint32_t version 207 | const class_ro_t *ro; 208 | method_list_t *methods; //方法列表 209 | property_list_t *properties; //属性列表 210 | const protocol_list_t *protocols; //协议列表 211 | Class firstSubclass; 212 | Class nextSiblingClass; 213 | char *demangledName; 214 | } 215 | 216 | struct class_ro_t { 217 | uint32_t flags; 218 | uint32_t instanceStart; 219 | uint32_t instanceSize; // instance对象占用的内存空间 220 | #ifdef __LP64__ 221 | uint32_t reserved; 222 | #endif 223 | const uint8_t *ivarLayout; 224 | const char * name; // 类名 225 | method_list_t * baseMethodList; 226 | protocol_list_t * baseProtocols; 227 | const ivar_list_t *ivars; //成员变量 228 | const uint8_t *weakIvarLayout; 229 | property_list_t *baseProperties; 230 | } 231 | ``` 232 | 233 | ## isa详解 234 | + arm64之前,isa是一个指针,存储着Class、Meta-Class对象的内存地址 235 | + arm64之后,isa被优化成了一个共用体(union)结构,使用位域存储更多信息 236 | ``` 237 | union isa_t { 238 | Class cls; 239 | uintptr_t bits; 240 | struct { 241 | uintptr_t nonpointer : 1; 242 | uintptr_t has_assoc : 1; 243 | uintptr_t has_cxx_dtor : 1; 244 | uintptr_t shiftcls : 33; 245 | uintptr_t magic : 6; 246 | uintptr_t weakly_referenced : 1; 247 | uintptr_t extra_rc : 19; 248 | } 249 | } 250 | ``` 251 | 252 | + nonpointer 253 | + 0,代表普通的指针,存储着Class、Meta-Class对象的内存地址 254 | + 1,代表优化过,使用位域存储更多的信息 255 | + has_assoc 256 | + 是否有设置过关联对象,如果没有,释放时会更快 257 | 258 | + has_cxx_dtor 259 | + 是否有C++析构函数(.cxx_destruct),如果没有,是否会更快 260 | 261 | + shiftcls 262 | + 存储着Class、Meta-Class对象的内存地址信息 263 | 264 | + magic 265 | + 用于在调试时分辨对象是否未完成初始化 266 | 267 | + weakly_referenced 268 | + 是否有被弱引用指向过,如果没有,释放时会更快 269 | 270 | + deallocting 271 | + 对象是否正在释放 272 | 273 | + extra_rc 274 | + 里面存储的值是引用计数器减一 275 | 276 | + has_sidetable_rc 277 | + 引用计数器是否过大,无法存储在isa中 278 | + 如果为1,那么引用计数会存储在一个叫SideTable的类的属性中 279 | 280 | ## class_rw_t 281 | class_rw_t里面的methods、properties、protocols是二维数组,是可读可写的,包含了类的初始内容、分类的内容 282 | ``` 283 | struct class_rw_t { 284 | method_array_t methods; 285 | property_array_t properties; 286 | protocol_array_t protocols; 287 | } 288 | ``` 289 | 290 | ## class_ro_t 291 | class_ro_t里面的baseMethodList、baseProtocols、ivars、baseProperties是一维数组,是只读的,包含了类的初始内容 292 | ``` 293 | struct class_ro_t { 294 | method_list_t *baseMethodList; 295 | protocol_list_t *baseProtocols; 296 | const ivar_list_t *ivars; 297 | property_list_t *baseProperties; 298 | } 299 | ``` 300 | 301 | 302 | ## 方法结构 303 | method_t是对方法/函数的封装 304 | ``` 305 | struct method_t { 306 | SEL name; // 函数名 307 | const char *types; // 编码(返回值类型、参数类型) 308 | IMP imp; // 指向函数的指针(函数地址) 309 | } 310 | ``` 311 | 312 | ## 方法缓存 313 | Class内部结构中有个方法缓存(cache_t),用散列表(哈希表)来缓存曾经调用过的方法,可提高方法查找速度。 314 | ``` 315 | struct cache_t { 316 | struct bucket_t *_buckets; // 散列表 317 | mask_t _mask; // 散列表的长度-1 318 | mask_t _occupied; // 已经缓存的方法数量 319 | } 320 | 321 | struct bucket_t { 322 | cache_key_t _key; // SEL作为key 323 | IMP _imp; // 324 | } 325 | ``` 326 | 327 | ## 使用runtime Associate方法关联的对象,在什么时候释放么? 328 | 1. 调用 -release :引用计数变为零 329 | 对象正在被销毁,生命周期即将结束. 330 | 不能再有新的 __weak 弱引用,否则将指向 nil. 331 | 调用 [self dealloc] 332 | 333 | 2. 父类调用 -dealloc 334 | 继承关系中最直接继承的父类再调用 -dealloc 335 | 如果是 MRC 代码 则会手动释放实例变量们(iVars) 336 | 继承关系中每一层的父类 都再调用 -dealloc 337 | 338 | 3. NSObject 调 -dealloc 339 | 只做一件事:调用 Objective-C runtime 中object_dispose() 方法 340 | 341 | 4. 调用 object_dispose() 342 | 为 C++ 的实例变量们(iVars)调用 destructors 343 | 为 ARC 状态下的 实例变量们(iVars) 调用 -release 344 | 解除所有使用 runtime Associate方法关联的对象 345 | 解除所有 __weak 引用 346 | 调用 free() -------------------------------------------------------------------------------- /docs/02-07、Runloop L13.md: -------------------------------------------------------------------------------- 1 | + [深入理解RunLoop 郭曜源@ibireme](https://blog.ibireme.com/2015/05/18/runloop/) 2 | + [iOS线下分享《RunLoop》by 孙源@sunnyxx](https://v.youku.com/v_show/id_XODgxODkzODI0.html) 3 | + [RunLoop 学习](http://blog.qiji.tech/archives/9125) 4 | + [runloop 面试题](https://www.jianshu.com/p/00d7fe4d9a93) 5 | 6 | [TOC] 7 | 8 | # 什么是runloop 9 | + RunLoop 实际上就是一个对象,这个对象管理了其需要处理的事件和消息,并提供了一个入口函数来执行上面 Event Loop(事件循环) 的逻辑。线程执行了这个函数后,就会一直处于这个函数内部 “接受消息->等待->处理” 的循环中,直到这个循环结束(比如传入 quit 的消息),函数返回。 10 | 11 | 自己理解的解释 12 | + RunLoop是一个基于do-while循环实现的Event Loop(事件循环),用来不停的调度工作以及处理输入事件,比如Source,Timer,Observer等。当有任务时就完成任务,没有任务时就进入休眠状态,直到循环结束。 13 | 14 | 不错的一份答案 15 | + Run loops是线程相关的的基础框架的一部分。一个run loop就是一个事件处理的循环,用来不停的调度工作以及处理输入事件。其实内部就是do-while循环,这个循环内部不断地处理各种任务(比 如Source,Timer,Observer)。使用run loop的目的是让你的线程在有工作的时候忙于工作,而没工作的时候处于休眠状态。 16 | 17 | ## runloop和线程的关系 18 | + 线程和 RunLoop 之间是一一对应的,其关系是保存在一个全局的 Dictionary 里。 19 | + 子线程刚创建时并没有 RunLoop,如果你不主动获取,那它一直都不会有。 20 | + 子线程中,RunLoop 的创建是发生在第一次获取时,RunLoop 的销毁是发生在线程结束时。 21 | + 你只能在一个线程的内部获取其 RunLoop(主线程除外)。 22 | 23 | ## runloop的接口 24 | + CFRunLoopRef 25 | + 一个 RunLoop 包含若干个 Mode,每个 Mode 又包含若干个 Source/Timer/Observer。每次调用 RunLoop 的主函数时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode。如果需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入。这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响 26 | + CFRunLoopModeRef 27 | + CFRunLoopSourceRef 28 | + CFRunLoopSourceRef 是事件产生的地方。Source有两个版本:Source0 和 Source1。 29 | + Source0 只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件。 30 | + Source1 包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程,其原理在下面会讲到。 31 | + CFRunLoopTimerRef 32 | + CFRunLoopTimerRef 是基于时间的触发器,它和 NSTimer 是toll-free bridged 的,可以混用。其包含一个时间长度和一个回调(函数指针)。当其加入到 RunLoop 时,RunLoop会注册对应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调。 33 | + CFRunLoopObserverRef 34 | + CFRunLoopObserverRef 是观察者,每个 Observer 都包含了一个回调(函数指针),当 RunLoop 的状态发生变化时,观察者就能通过回调接受到这个变化。 35 | 36 | ## runloop的mode 37 | + kCFRunLoopDefaultMode/NSDefaultRunLoopMode 38 | + App的默认 Mode,通常主线程是在这个 Mode 下运行的。 39 | + UITrackingRunLoopMode 40 | + 界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响。 41 | + UIInitializationRunLoopMode 42 | + 在刚启动App时第进入的第一个Mode,启动完成后就不再使用 43 | + GSEventReceiveRunLoopMode 44 | + 接受系统事件的内部Mode,通常用不到 45 | + kCFRunLoopCommonModes/NSRunLoopCommonModes 46 | + 组合状态,这是一个占位的 Mode,没有实际作用。 47 | 48 | ## runloop内部逻辑 49 | ![image](http://blog.qiji.tech/wp-content/uploads/2016/04/RunLoop_1.png) 50 | 51 | + 1、通知Observer:即将进入Loop 52 | + 2、通知Observer:将要处理Timer 53 | + 3、通知Observer:将要处理Source0 54 | + 4、处理Source0 55 | + 5、如果有Source1,跳到第9步 56 | + 6、通知Observer:线程即将休眠 57 | + 7、休眠,等待唤醒 58 | + 8、通知Observer:线程刚被唤醒 59 | + 9、处理唤醒时收到的消息,之后跳回2 60 | + 10、通知Observer:即将退出Loop 61 | 62 | ``` 63 | { 64 | /// 1. 通知Observers,即将进入RunLoop 65 | /// 此处有Observer会创建AutoreleasePool: _objc_autoreleasePoolPush(); 66 | __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopEntry); 67 | do { 68 | 69 | /// 2. 通知 Observers: 即将触发 Timer 回调。 70 | __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeTimers); 71 | /// 3. 通知 Observers: 即将触发 Source (非基于port的,Source0) 回调。 72 | __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeSources); 73 | __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block); 74 | 75 | /// 4. 触发 Source0 (非基于port的) 回调。 76 | __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(source0); 77 | __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block); 78 | 79 | /// 6. 通知Observers,即将进入休眠 80 | /// 此处有Observer释放并新建AutoreleasePool: _objc_autoreleasePoolPop(); _objc_autoreleasePoolPush(); 81 | __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeWaiting); 82 | 83 | /// 7. sleep to wait msg. 84 | mach_msg() -> mach_msg_trap(); 85 | 86 | 87 | /// 8. 通知Observers,线程被唤醒 88 | __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopAfterWaiting); 89 | 90 | /// 9. 如果是被Timer唤醒的,回调Timer 91 | __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(timer); 92 | 93 | /// 9. 如果是被dispatch唤醒的,执行所有调用 dispatch_async 等方法放入main queue 的 block 94 | __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(dispatched_block); 95 | 96 | /// 9. 如果如果Runloop是被 Source1 (基于port的) 的事件唤醒了,处理这个事件 97 | __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__(source1); 98 | 99 | 100 | } while (...); 101 | 102 | /// 10. 通知Observers,即将退出RunLoop 103 | /// 此处有Observer释放AutoreleasePool: _objc_autoreleasePoolPop(); 104 | __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopExit); 105 | } 106 | ``` 107 | 108 | ## 思考: 109 | ### runloop什么时候退出 110 | + 1、超过设定的超时 111 | + 2、当前runloop中没有需要处理的timer、source 112 | + 3、接收到exit信号 113 | 114 | # runloop在系统中的应用 115 | ## 1、AutoreleasePool 116 | App启动后,苹果在主线程 RunLoop 里注册了两个 Observer,其回调都是 _wrapRunLoopWithAutoreleasePoolHandler()。 117 | 118 | 第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。 119 | 120 | 第二个 Observer 监视了两个事件: BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。 121 | 122 | 在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被 RunLoop 创建好的 AutoreleasePool 环绕着,所以不会出现内存泄漏,开发者也不必显示创建 Pool 了。 123 | 124 | ## 2、事件响应 125 | 苹果注册了一个 Source1 (基于 mach port 的) 用来接收系统事件,其回调函数为 __IOHIDEventSystemClientQueueCallback()。 126 | 127 | 当一个硬件事件(触摸/锁屏/摇晃等)发生后,首先由 IOKit.framework 生成一个 IOHIDEvent 事件并由 SpringBoard 接收。这个过程的详细情况可以参考这里。SpringBoard 只接收按键(锁屏/静音等),触摸,加速,接近传感器等几种 Event,随后用 mach port 转发给需要的App进程。随后苹果注册的那个 Source1 就会触发回调,并调用 _UIApplicationHandleEventQueue() 进行应用内部的分发。 128 | 129 | _UIApplicationHandleEventQueue() 会把 IOHIDEvent 处理并包装成 UIEvent 进行处理或分发,其中包括识别 UIGesture/处理屏幕旋转/发送给 UIWindow 等。通常事件比如 UIButton 点击、touchesBegin/Move/End/Cancel 事件都是在这个回调中完成的。 130 | 131 | ## 3、手势识别 132 | 当上面的 _UIApplicationHandleEventQueue() 识别了一个手势时,其首先会调用 Cancel 将当前的 touchesBegin/Move/End 系列回调打断。随后系统将对应的 UIGestureRecognizer 标记为待处理。 133 | 134 | 苹果注册了一个 Observer 监测 BeforeWaiting (Loop即将进入休眠) 事件,这个Observer的回调函数是 _UIGestureRecognizerUpdateObserver(),其内部会获取所有刚被标记为待处理的 GestureRecognizer,并执行GestureRecognizer的回调。 135 | 136 | 当有 UIGestureRecognizer 的变化(创建/销毁/状态改变)时,这个回调都会进行相应处理。 137 | 138 | ## 4、界面更新 139 | 当在操作 UI 时,比如改变了 Frame、更新了 UIView/CALayer 的层次时,或者手动调用了 UIView/CALayer 的 setNeedsLayout/setNeedsDisplay方法后,这个 UIView/CALayer 就被标记为待处理,并被提交到一个全局的容器去。 140 | 141 | 苹果注册了一个 Observer 监听 BeforeWaiting(即将进入休眠) 和 Exit (即将退出Loop) 事件,回调去执行一个很长的函数: 142 | _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv()。这个函数里会遍历所有待处理的 UIView/CAlayer 以执行实际的绘制和调整,并更新 UI 界面。 143 | 144 | ## 5、定时器 145 | NSTimer 其实就是 CFRunLoopTimerRef,他们之间是 toll-free bridged 的。一个 NSTimer 注册到 RunLoop 后,RunLoop 会为其重复的时间点注册好事件。例如 10:00, 10:10, 10:20 这几个时间点。RunLoop为了节省资源,并不会在非常准确的时间点回调这个Timer。Timer 有个属性叫做 Tolerance (宽容度),标示了当时间点到后,容许有多少最大误差。 146 | 147 | 如果某个时间点被错过了,例如执行了一个很长的任务,则那个时间点的回调也会跳过去,不会延后执行。就比如等公交,如果 10:10 时我忙着玩手机错过了那个点的公交,那我只能等 10:20 这一趟了。 148 | 149 | CADisplayLink 是一个和屏幕刷新率一致的定时器(但实际实现原理更复杂,和 NSTimer 并不一样,其内部实际是操作了一个 Source)。如果在两次屏幕刷新之间执行了一个长任务,那其中就会有一帧被跳过去(和 NSTimer 相似),造成界面卡顿的感觉。在快速滑动TableView时,即使一帧的卡顿也会让用户有所察觉。Facebook 开源的 AsyncDisplayLink 就是为了解决界面卡顿的问题,其内部也用到了 RunLoop,这个稍后我会再单独写一页博客来分析。 150 | 151 | ## 6、PerformSelecter 152 | 当调用 NSObject 的 performSelecter:afterDelay: 后,实际上其内部会创建一个 Timer 并添加到当前线程的 RunLoop 中。所以如果当前线程没有 RunLoop,则这个方法会失效。 153 | 154 | 当调用 performSelector:onThread: 时,实际上其会创建一个 Timer 加到对应的线程去,同样的,如果对应线程没有 RunLoop 该方法也会失效。 155 | 156 | ## 7、网络请求(现在的NSURLSession是否借助了runloop?) 157 | NSURLConnection 的工作过程也借助了runloop。使用 NSURLConnection 时,你会传入一个 Delegate,当调用了 [connection start] 后,这个 Delegate 就会不停收到事件回调。实际上,start 这个函数的内部会会获取 CurrentRunLoop,然后在其中的 DefaultMode 添加了4个 Source0 (即需要手动触发的Source)。CFMultiplexerSource 是负责各种 Delegate 回调的,CFHTTPCookieStorage 是处理各种 Cookie 的。 158 | 159 | 当开始网络传输时,我们可以看到 NSURLConnection 创建了两个新线程:com.apple.NSURLConnectionLoader 和 com.apple.CFSocket.private。其中 CFSocket 线程是处理底层 socket 连接的。NSURLConnectionLoader 这个线程内部会使用 RunLoop 来接收底层 socket 的事件,并通过之前添加的 Source0 通知到上层的 Delegate。 160 | 161 | 162 | ![image](https://blog.ibireme.com/wp-content/uploads/2015/05/RunLoop_network.png) 163 | 164 | 165 | NSURLConnectionLoader 中的 RunLoop 通过一些基于 mach port 的 Source 接收来自底层 CFSocket 的通知。当收到通知后,其会在合适的时机向 CFMultiplexerSource 等 Source0 发送通知,同时唤醒 Delegate 线程的 RunLoop 来让其处理这些通知。CFMultiplexerSource 会在 Delegate 线程的 RunLoop 对 Delegate 执行实际的回调。 166 | 167 | 168 | # 你用runloop实现了什么 169 | ## 使用runloop开启一个常驻子线程 170 | + AFNetworking2开启子线程,在后台线程接收 Delegate 回调 171 | ``` 172 | + (void)networkRequestThreadEntryPoint:(id)__unused object { 173 | @autoreleasepool { 174 | [[NSThread currentThread] setName:@"AFNetworking"]; 175 | NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; 176 | [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; 177 | [runLoop run]; 178 | } 179 | } 180 | ``` 181 | + 线上实时卡顿监控 182 | + NSRunLoop调用方法主要就是在kCFRunLoopBeforeSources(3. 通知 Observers: 即将触发 Source0回调)和kCFRunLoopBeforeWaiting(6. 通知Observers,即将进入休眠)之间,还有kCFRunLoopAfterWaiting之后,也就是如果我们发现这两个时间内耗时太长,那么就可以判定出此时主线程卡顿 183 | + [iOS线上实时卡顿监控](https://www.jianshu.com/p/890d1ba05f4c) 184 | -------------------------------------------------------------------------------- /docs/03-01、UIKit L14.md: -------------------------------------------------------------------------------- 1 | - UIApplication、VC、View生命周期 2 | - 界面相关的知识点 3 | 4 | # UIApplication、VC、View生命周期 5 | ## UIApplication的生命周期 6 | ``` 7 | // 应用程序启动完成的时候调用 8 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions: (NSDictionary *)launchOptions { 9 | return YES; 10 | } 11 | // 当我们应用程序即将失去焦点的时候调用 12 | - (void)applicationWillResignActive:(UIApplication *)application { 13 | NSLog(@"%s",__func__); 14 | } 15 | // 当我们应用程序完全进⼊后台的时候调用 16 | - (void)applicationDidEnterBackground:(UIApplication *)application{ 17 | NSLog(@"%s",__func__); 18 | } 19 | // 当我们应用程序即将进⼊前台的时候调用 20 | - (void)applicationWillEnterForeground:(UIApplication *)application { 21 | NSLog(@"%s",__func__);} 22 | // 当我们应用程序完全获取焦点的时候调用 23 | // 只有当一个应用程序完全获取到焦点,才能与用户交互. 24 | - (void)applicationDidBecomeActive:(UIApplication *)application { 25 | - NSLog(@"%s",__func__); 26 | } 27 | // 当我们应用程序即将关闭的时候调用 28 | - (void)applicationWillTerminate:(UIApplication *)application { 29 | NSLog(@"%s",__func__); 30 | } 31 | ``` 32 | 33 | ![iOS启动原理图](https://upload-images.jianshu.io/upload_images/2252551-4c489571cabf79b7.png?imageMogr2/auto-orient/strip|imageView2/2/w/990/format/webp) 34 | 35 | ## UIViewController 生命周期 36 | ``` 37 | #pragma mark --- life circle 38 | 39 | // 非storyBoard(xib或非xib)都走这个方法 40 | - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { 41 | NSLog(@"%s", __FUNCTION__); 42 | if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) { 43 | 44 | } 45 | return self; 46 | } 47 | 48 | // 如果连接了串联图storyBoard 走这个方法 49 | - (instancetype)initWithCoder:(NSCoder *)aDecoder { 50 | NSLog(@"%s", __FUNCTION__); 51 | if (self = [super initWithCoder:aDecoder]) { 52 | 53 | } 54 | return self; 55 | } 56 | 57 | // xib 加载 完成 58 | - (void)awakeFromNib { 59 | [super awakeFromNib]; 60 | NSLog(@"%s", __FUNCTION__); 61 | } 62 | 63 | // 加载视图(默认从nib) 64 | - (void)loadView { 65 | NSLog(@"%s", __FUNCTION__); 66 | self.view = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds]; 67 | self.view.backgroundColor = [UIColor redColor]; 68 | } 69 | 70 | //视图控制器中的视图加载完成,viewController自带的view加载完成 71 | - (void)viewDidLoad { 72 | NSLog(@"%s", __FUNCTION__); 73 | [super viewDidLoad]; 74 | } 75 | 76 | 77 | //视图将要出现 78 | - (void)viewWillAppear:(BOOL)animated { 79 | NSLog(@"%s", __FUNCTION__); 80 | [super viewWillAppear:animated]; 81 | } 82 | 83 | // view 即将布局其 Subviews 84 | - (void)viewWillLayoutSubviews { 85 | NSLog(@"%s", __FUNCTION__); 86 | [super viewWillLayoutSubviews]; 87 | } 88 | 89 | // view 已经布局其 Subviews 90 | - (void)viewDidLayoutSubviews { 91 | NSLog(@"%s", __FUNCTION__); 92 | [super viewDidLayoutSubviews]; 93 | } 94 | 95 | //视图已经出现 96 | - (void)viewDidAppear:(BOOL)animated { 97 | NSLog(@"%s", __FUNCTION__); 98 | [super viewDidAppear:animated]; 99 | } 100 | 101 | //视图将要消失 102 | - (void)viewWillDisappear:(BOOL)animated { 103 | NSLog(@"%s", __FUNCTION__); 104 | [super viewWillDisappear:animated]; 105 | } 106 | 107 | //视图已经消失 108 | - (void)viewDidDisappear:(BOOL)animated { 109 | NSLog(@"%s", __FUNCTION__); 110 | [super viewDidDisappear:animated]; 111 | } 112 | 113 | //出现内存警告 //模拟内存警告:点击模拟器->hardware-> Simulate Memory Warning 114 | - (void)didReceiveMemoryWarning { 115 | NSLog(@"%s", __FUNCTION__); 116 | [super didReceiveMemoryWarning]; 117 | } 118 | 119 | // 视图被销毁 120 | - (void)dealloc { 121 | NSLog(@"%s", __FUNCTION__); 122 | } 123 | ``` -------------------------------------------------------------------------------- /docs/03-02、事件处理 L15.md: -------------------------------------------------------------------------------- 1 | [TOC] 2 | 3 | # 事件分类 4 | 对于 iOS 设备用户来说,他们操作设备的方式主要有三种:触摸屏幕、晃动设备、通过遥控设施控制设备。对应的事件类型有以下三种: 5 | 6 | 1. 触屏事件(Touch Event) 7 | 2. 运动事件(Motion Event) 8 | 3. 远端控制事件(Remote-Control Event) 9 | 10 | # 响应者链 11 | 当发生事件响应时,必须知道由谁来响应事件。在 iOS 中,由响应者链来对事件进行响应。 12 | 13 | 所有事件响应的类都是 UIResponder 的子类,响应者链是一个由不同对象组成的层次结构,其中的每个对象将依次获得响应事件消息的机会。当发生事件时,事件首先被发送给第一响应者,第一响应者往往是事件发生的视图,也就是用户触摸屏幕的地方。事件将沿着响应者链一直向下传递,直到被接受并做出处理。一般来说,第一响应者是个视图对象或者其子类对象,当其被触摸后事件被交由它处理,如果它不处理,事件就会被传递给它的视图控制器对象 ViewController(如果存在),然后是它的父视图(superview)对象(如果存在),以此类推,直到顶层视图。接下来会沿着顶层视图(top view)到窗口(UIWindow 对象)再到程序(UIApplication 对象)。如果整个过程都没有响应这个事件,该事件就被丢弃。一般情况下,在响应者链中只要由对象处理事件,事件就停止传递。 14 | 15 | 一个典型的事件响应路线如下: 16 | 17 | ``` 18 | First Responser --> The Window --> The Application --> nil(丢弃) 19 | ``` 20 | 我们可以通过 [responder nextResponder] 找到当前 responder 的下一个 responder,持续这个过程到最后会找到 UIApplication 对象。 21 | 22 | 通常情况下,我们在 First Responder (一般也就是用户当前触控的 View )这里就会响应请求,进入下面的事件分发机制。 23 | 24 | # 事件分发 25 | 第一响应者(First responder)指的是当前接受触摸的响应者对象(通常是一个 UIView 对象),即表示当前该对象正在与用户交互,它是响应者链的开端。响应者链和事件分发的使命都是找出第一响应者。 26 | 27 | iOS 系统检测到手指触摸 (Touch) 操作时会将其打包成一个 UIEvent 对象,并放入当前活动 Application 的事件队列,单例的 UIApplication 会从事件队列中取出触摸事件并传递给单例的 UIWindow 来处理,UIWindow 对象首先会使用 hitTest:withEvent:方法寻找此次 Touch 操作初始点所在的视图(View),即需要将触摸事件传递给其处理的视图,这个过程称之为 hit-test view。 28 | 29 | hitTest:withEvent:方法的处理流程如下: 30 | 31 | 1. 首先调用当前视图的 pointInside:withEvent: 方法判断触摸点是否在当前视图内; 32 | 2. 若返回 NO, 则 hitTest:withEvent: 返回 nil,若返回 YES, 则向当前视图的所有子视图 (subviews) 发送 hitTest:withEvent: 消息,所有子视图的遍历顺序是从最顶层视图一直到到最底层视图,即从 subviews 数组的末尾向前遍历,直到有子视图返回非空对象或者全部子视图遍历完毕; 33 | 3. 若第一次有子视图返回非空对象,则 hitTest:withEvent: 方法返回此对象,处理结束; 34 | 4. 如所有子视图都返回空,则 hitTest:withEvent: 方法返回自身 (self)。 35 | 36 | 一个示例性的代码实现如下: 37 | 38 | 39 | ``` 40 | - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{ 41 | UIView *touchView = self; 42 | if ([self pointInside:point withEvent:event] && 43 | (!self.hidden) && 44 | self.userInteractionEnabled && 45 | (self.alpha >= 0.01f)) { 46 | 47 | for (UIView *subView in self.subviews) { 48 | [subview convertPoint:point fromView:self]; 49 | UIView *subTouchView = [subView hitTest:subPoint withEvent:event]; 50 | if (subTouchView) { 51 | touchView = subTouchView; 52 | break; 53 | } 54 | } 55 | } else { 56 | touchView = nil; 57 | } 58 | 59 | return touchView; 60 | } 61 | ``` 62 | # 说明 63 | 1. 如果最终 hit-test 没有找到第一响应者,或者第一响应者没有处理该事件,则该事件会沿着响应者链向上回溯,如果 UIWindow 实例和 UIApplication 实例都不能处理该事件,则该事件会被丢弃; 64 | 2. hitTest:withEvent: 方法将会忽略隐藏 (hidden=YES) 的视图,禁止用户操作 (userInteractionEnabled=NO) 的视图,以及 alpha 级别小于 0.01(alpha<0.01)的视图。如果一个子视图的区域超过父视图的 bound 区域(父视图的 clipsToBounds 属性为 NO,这样超过父视图 bound 区域的子视图内容也会显示),那么正常情况下对子视图在父视图之外区域的触摸操作不会被识别, 因为父视图的 pointInside:withEvent: 方法会返回 NO, 这样就不会继续向下遍历子视图了。当然,也可以重写 pointInside:withEvent: 方法来处理这种情况。 65 | 3. 我们可以重写 hitTest:withEvent: 来达到某些特定的目的。 66 | 67 | [CYLTabBarController](https://github.com/ChenYilong/CYLTabBarController)是一个支持自定义 Tab 控件的开源项目。在 TabBar 当中,为了支持 TabBar 按钮大小超过 TabBar Frame 范围时也可以响应,它的实现就是重载了 hitTest 方法: 68 | 69 | 70 | ``` 71 | /* 72 | * 73 | Capturing touches on a subview outside the frame of its superview 74 | * 75 | */ 76 | - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event 77 | { 78 | if (!self.clipsToBounds && !self.hidden && self.alpha > 0) { 79 | for (UIView *subview in self.subviews.reverseObjectEnumerator) { 80 | CGPoint subPoint = [subview convertPoint:point fromView:self]; 81 | UIView *result = [subview hitTest:subPoint withEvent:event]; 82 | if (result != nil) { 83 | return result; 84 | } 85 | } 86 | } 87 | return nil; 88 | } 89 | ``` 90 | 91 | 可以看到和上面的示例代码的差距,主要就在于取消了 pointInside 函数的检测,让我们可以捕获到当前 Frame 范围以外的子 View 的触控事件。 92 | 93 | # 参考资料 94 | - 转载于 https://hit-alibaba.github.io/interview/iOS/Cocoa-Touch/Event-Handling.html 95 | - [CocoaTouch 事件处理流程](https://www.cnblogs.com/snake-hand/p/3178070.html) 96 | - [模拟UIView的hitTest:方法和pointInside:方法的实现](http://blog.sina.com.cn/s/blog_59fb90df0101ab26.html) 97 | -------------------------------------------------------------------------------- /docs/03-03、动画 L16.md: -------------------------------------------------------------------------------- 1 | [TOC] 2 | 3 | # Core Animation 4 | 注:示例中部分代码的完整版可以在[这里](https://github.com/yixiangboy/IOSAnimationDemo)找到。 5 | 6 | ## UIView Animation 7 | ### 简单动画 8 | 对于 UIView 上简单的动画,iOS 提供了很方便的函数: 9 | ``` 10 | + animateWithDuration:animations: 11 | ``` 12 | 第一个参数是动画的持续时间,第二个参数是一个 block,在 animations block 中对 UIView 的属性进行调整,设置 UIView 动画结束后最终的效果,iOS 就会自动补充中间帧,形成动画。 13 | 14 | 可以更改的属性有: 15 | - frame 16 | - bounds 17 | - center 18 | - transform 19 | - alpha 20 | - backgroundColor 21 | - contentStretch 22 | 23 | 这些属性大都是 View 的基本属性,下面是一个例子,这个例子中的动画会同时改变 View 的 frame,backgroundColor 和 alpha : 24 | 25 | ``` 26 | [UIView animateWithDuration:2.0 animations:^{ 27 | myView.frame = CGRectMake(50, 200, 200, 200); 28 | myView.backgroundColor = [UIColor blueColor]; 29 | myView.alpha = 0.7; 30 | }]; 31 | ``` 32 | 其中有一个比较特殊的 transform 属性,它的类型是 CGAffineTransform,即 2D 仿射变换,这是个数学中的概念,用一个三维矩阵来表述 2D 图形的矢量变换。用 transform 属性对 View 进行: 33 | 34 | - 旋转 35 | - 缩放 36 | - 其他自定义 2D 变换 37 | 38 | iOS 提供了下面的函数可以创建简单的 2D 变换: 39 | - CGAffineTransformMakeScale 40 | - CGAffineTransformMakeRotation 41 | - CGAffineTransformMakeTranslation 42 | 43 | 例如下面的代码会将 View 缩小至原来的 1/4 大小: 44 | ``` 45 | [UIView animateWithDuration:2.0 animations:^{ 46 | myView.transform = CGAffineTransformMakeScale(0.5, 0.5); 47 | }]; 48 | ``` 49 | 50 | ### 调节参数 51 | 完整版的 animate 函数其实是这样的: 52 | 53 | ``` 54 | + animateWithDuration:delay:options:animations:completion: 55 | ``` 56 | 57 | 可以通过 delay 参数调节让动画延迟产生,同时还一个 options 选项可以调节动画进行的方式。可用的 options 可分为两类: 58 | 59 | #### 控制过程 60 | 例如 UIViewAnimationOptionRepeat 可以让动画反复进行, UIViewAnimationOptionAllowUserInteraction 可以让允许用户对动画进行过程中同 View 进行交互(默认是不允许的) 61 | 62 | #### 控制速度 63 | 动画的进行速度可以用速度曲线来表示(参考这里),提供的选项例如 UIViewAnimationOptionCurveEaseIn 是先慢后快,UIViewAnimationOptionCurveEaseOut 是先快后慢。 64 | 65 | 不同的选项直接可以通过“与”操作进行合并,同时使用,例如: 66 | ``` 67 | UIViewAnimationOptionRepeat | UIViewAnimationOptionAllowUserInteraction 68 | ``` 69 | 70 | ### 关键帧动画 71 | 上面介绍的动画中,我们只能控制开始和结束时的效果,然后由系统补全中间的过程,有些时候我们需要自己设定若干关键帧,实现更复杂的动画效果,这时候就需要关键帧动画的支持了。下面是一个示例: 72 | 73 | ``` 74 | [UIView animateKeyframesWithDuration:2.0 delay:0.0 options:UIViewKeyframeAnimationOptionRepeat | UIViewKeyframeAnimationOptionAutoreverse animations:^{ 75 | [UIView addKeyframeWithRelativeStartTime:0.0 relativeDuration:0.5 animations:^{ 76 | self.myView.frame = CGRectMake(10, 50, 100, 100); 77 | }]; 78 | [UIView addKeyframeWithRelativeStartTime: 0.5 relativeDuration:0.3 animations:^{ 79 | self.myView.frame = CGRectMake(20, 100, 100, 100); 80 | }]; 81 | [UIView addKeyframeWithRelativeStartTime:0.8 relativeDuration:0.2 animations:^{ 82 | self.myView.transform = CGAffineTransformMakeScale(0.5, 0.5); 83 | }]; 84 | } completion:nil]; 85 | ``` 86 | 87 | 这个例子添加了三个关键帧,在外面的 animateKeyframesWithDuration 中我们设置了持续时间为 2.0 秒,这是真实意义上的时间,里面的 startTime 和 relativeDuration 都是相对时间。以第一个为例,startTime 为 0.0,relativeTime 为 0.5,这个动画会直接开始,持续时间为 2.0 X 0.5 = 1.0 秒,下面第二个的开始时间是 0.5,正好承接上一个结束,第三个同理,这样三个动画就变成连续的动画了。 88 | 89 | ### View 的转换 90 | iOS 还提供了两个函数,用于进行两个 View 之间通过动画换场: 91 | ``` 92 | + transitionWithView:duration:options:animations:completion: 93 | + transitionFromView:toView:duration:options:completion: 94 | ``` 95 | 需要注意的是,换场动画会在这两个 View 共同的父 View 上进行,在写动画之前,先要设计好 View 的继承结构。 96 | 97 | 同样,View 之间的转换也有很多选项可选,例如 UIViewAnimationOptionTransitionFlipFromLeft 从左边翻转,UIViewAnimationOptionTransitionCrossDissolve 渐变等等。 98 | 99 | ## CALayer Animation 100 | UIView 的动画简单易用,但是能实现的效果相对有限,上面介绍的 UIView 的几种动画方式,实际上是对底层 CALayer 动画的一种封装。直接使用 CALayer 层的动画方法可以实现更多高级的动画效果。 101 | 102 | 注意:使用 CALayer 动画之前,首先需要引入 QuartzCore.framework。 103 | 104 | ### 基本动画(CABasicAnimation) 105 | CABasicAnimation 用于创建一个 CALayer 上的基本动画效果,下面是一个例子: 106 | 107 | ``` 108 | CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position.x"]; 109 | animation.toValue = @200; 110 | animation.duration = 0.8; 111 | animation.repeatCount = 5; 112 | animation.beginTime = CACurrentMediaTime() + 0.5; 113 | animation.fillMode = kCAFillModeRemoved; 114 | [self.myView.layer addAnimation:animation forKey:nil]; 115 | ``` 116 | 117 | #### KeyPath 118 | 这里我们使用了 animationWithKeyPath 这个方法来改变 layer 的属性,可以使用的属性有很多,具体可以参考[这里](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreAnimation_guide/AnimatableProperties/AnimatableProperties.html)和[这里](https://www.cnblogs.com/pengyingh/articles/2379631.html)。其中很多属性在前面介绍的 UIView 动画部分我们也看到过,进一步验证了 UIView 的动画方法是对底层 CALayer 的一种封装。 119 | 120 | 需要注意的一点是,上面我们使用了 position 属性, layer 的这个 position 属性和 View 的 frame 以及 bounds 属性都不相同,而是和 Layer 的 anchorPoint 有关,可以由下面的公式计算得到: 121 | ``` 122 | position.x = frame.origin.x + 0.5 * bounds.size.width; 123 | position.y = frame.origin.y + 0.5 * bounds.size.height; 124 | ``` 125 | 126 | 关于 anchorPoint 和 position 属性的以及具体计算的原理可以参考[这篇文章](http://wonderffee.github.io/blog/2013/10/13/understand-anchorpoint-and-position/)。 127 | 128 | #### 属性 129 | CABasicAnimation 的属性有下面几个: 130 | - beginTime 131 | - duration 132 | - fromValue 133 | - toValue 134 | - byValue 135 | - repeatCount 136 | - autoreverses 137 | - timingFunction 138 | 139 | 可以看到,其中 beginTime,duration,repeatCount 等属性和上面在 UIView 中使用到的 duration,UIViewAnimationOptionRepeat 等选项是相对应的,不过这里的选项能够提供更多的扩展性。 140 | 141 | 需要注意的是 fromValue,toValue,byValue 这几个选项,支持的设置模式有下面几种: 142 | - 设置 fromValue 和 toValue:从 fromValue 变化到 toValue 143 | - 设置 fromValue 和 byValue:从 fromValue 变化到 fromValue + byValue 144 | - 设置 byValue 和 toValue:从 toValue - byValue 变化到 toValue 145 | - 设置 fromValue: 从 fromValue 变化到属性当前值 146 | - 设置 toValue:从属性当前值变化到 toValue 147 | - 设置 byValue:从属性当前值变化到属性当前值 + toValue 148 | 149 | 看起来挺复杂,其实概括起来基本就是,如果某个值不设置,就是用这个属性当前的值。 150 | 151 | 另外,可以看到上面我们使用的: 152 | ``` 153 | animation.toValue = @200; 154 | ``` 155 | 156 | 而不是直接使用 200,因为 toValue 之类的属性为 id 类型,或者像这样使用 @ 符号,或者使用: 157 | ``` 158 | animation.toValue = [NSNumber numberWithInt:200]; 159 | ``` 160 | 161 | 最后一个比较有意思的是 timingFunction 属性,使用这个属性可以自定义动画的运动曲线(节奏,pacing),系统提供了五种值可以选择: 162 | - kCAMediaTimingFunctionLinear 线性动画 163 | - kCAMediaTimingFunctionEaseIn 先快后慢 164 | - kCAMediaTimingFunctionEaseOut 先慢后快 165 | - kCAMediaTimingFunctionEaseInEaseOut 先慢后快再慢 166 | - kCAMediaTimingFunctionDefault 默认,也属于中间比较快 167 | 168 | 此外,我们还可以使用 [CAMediaTimingFunction functionWithControlPoints] 方法来自定义运动曲线,[这个网站](http://netcetera.org/camtf-playground.html)提供了一个将参数调节可视化的效果,关于动画时间系统的具体介绍可以参考这篇文章。 169 | 170 | ### 关键帧动画(CAKeyframeAnimation) 171 | 同 UIView 中的类似,CALayer 层也提供了关键帧动画的支持,CAKeyFrameAnimation 和 CABasicAnimation 都继承自 CAPropertyAnimation,因此它有具有上面提到的那些属性,此外,CAKeyFrameAnimation 还有特有的几个属性。 172 | 173 | #### values 和 keyTimes 174 | 使用 values 和 keyTimes 可以共同确定一个动画的若干关键帧,示例代码如下: 175 | ``` 176 | CAKeyframeAnimation *anima = [CAKeyframeAnimation animationWithKeyPath:@"transform.rotation"];//在这里@"transform.rotation"==@"transform.rotation.z" 177 | NSValue *value1 = [NSNumber numberWithFloat:-M_PI/180*4]; 178 | NSValue *value2 = [NSNumber numberWithFloat:M_PI/180*4]; 179 | NSValue *value3 = [NSNumber numberWithFloat:-M_PI/180*4]; 180 | anima.values = @[value1,value2,value3]; 181 | // anima.keyTimes = @[@0.0, @0.5, @1.0]; 182 | anima.repeatCount = MAXFLOAT; 183 | 184 | [_demoView.layer addAnimation:anima forKey:@"shakeAnimation"]; 185 | ``` 186 | 可以看到上面这个动画共有三个关键帧,如果没有指定 keyTimes 则各个关键帧会平分整个动画的时间(duration)。 187 | 188 | #### path 189 | 使用 path 属性可以设置一个动画的运动路径,注意 path 只对 CALayer 的 anchorPoint 和position 属性起作用,另外如果你设置了 path ,那么 values 将被忽略。 190 | ``` 191 | CAKeyframeAnimation *anima = [CAKeyframeAnimation animationWithKeyPath:@"position"]; 192 | UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(SCREEN_WIDTH/2-100, SCREEN_HEIGHT/2-100, 200, 200)]; 193 | anima.path = path.CGPath; 194 | anima.duration = 2.0f; 195 | [_demoView.layer addAnimation:anima forKey:@"pathAnimation"]; 196 | ``` 197 | 198 | ### 组动画(CAAnimationGroup) 199 | 组动画可以将一组动画组合在一起,所有动画对象可以同时运行,示例代码如下: 200 | ``` 201 | CAAnimationGroup *group = [[CAAnimationGroup alloc] init]; 202 | CABasicAnimation *animationOne = [CABasicAnimation animationWithKeyPath:@"transform.scale"]; 203 | 204 | animationOne.toValue = @2.0; 205 | animationOne.duration = 1.0; 206 | 207 | CABasicAnimation *animationTwo = [CABasicAnimation animationWithKeyPath:@"position.x"]; 208 | animationTwo.toValue = @400; 209 | animationTwo.duration = 1.0; 210 | 211 | [group setAnimations:@[animationOne, animationTwo]]; 212 | [self.myView.layer addAnimation:group forKey:nil]; 213 | ``` 214 | 需要注意的是,一个 group 组内的某个动画的持续时间(duration),如果超过了整个组的动画持续时间,那么多出的动画时间将不会被展示。例如一个 group 的持续时间是 5s,而组内一个动画持续时间为 10s ,那么这个 10s 的动画只会展示前 5s 。 215 | 216 | ### 切换动画(CATransition) 217 | CATransition 可以用于 View 或 ViewController 直接的换场动画: 218 | ``` 219 | self.myView.backgroundColor = [UIColor blueColor]; 220 | CATransition *trans = [CATransition animation]; 221 | trans.duration = 1.0; 222 | trans.type = @"push"; 223 | 224 | [self.myView.layer addAnimation:trans forKey:nil]; 225 | 226 | // 这句放在下面也可以 227 | // self.myView.backgroundColor = [UIColor blueColor]; 228 | ``` 229 | 为什么改变颜色放在前后都可以呢?具体的解释可以参考 SO 上的[这个回答](https://stackoverflow.com/questions/2233692/how-does-catransition-work)。简单来说就是动画和绘制之间并不冲突。 230 | 231 | ## 更高级的动画效果 232 | ### CADisplayLink 233 | CADisplayLink 是一个计时器对象,可以周期性的调用某个 selecor 方法。相比 NSTimer ,它可以让我们以和屏幕刷新率同步的频率(每秒60次)来调用绘制函数,实现界面连续的不停重绘,从而实现动画效果。 234 | 235 | 示例代码(修改自[这里](http://www.cocoachina.com/articles/11382)): 236 | 237 | ``` 238 | #import "BlockView.h" 239 | 240 | @implementation BlockView 241 | 242 | - (void)startAnimationFrom:(CGFloat)from To:(CGFloat)to 243 | { 244 | self.from = from; 245 | self.to = to; 246 | if (self.displayLink == nil) { 247 | self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(tick:)]; 248 | [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] 249 | forMode:NSDefaultRunLoopMode]; 250 | } 251 | } 252 | 253 | // 重复调用这个方法以重绘整个 View 254 | - (void)tick:(CADisplayLink *)displayLink 255 | { 256 | [self setNeedsDisplay]; 257 | } 258 | 259 | - (void)endAnimation 260 | { 261 | [self.displayLink invalidate]; 262 | self.displayLink = nil; 263 | } 264 | 265 | - (void)drawRect:(CGRect)rect 266 | { 267 | CALayer *layer = self.layer.presentationLayer; 268 | CGFloat progress = 1 - (layer.position.y - self.to) / (self.from - self.to); 269 | CGFloat height = CGRectGetHeight(rect); 270 | CGFloat deltaHeight = height / 2 * (0.5 - fabs(progress - 0.5)); 271 | CGPoint topLeft = CGPointMake(0, deltaHeight); 272 | CGPoint topRight = CGPointMake(CGRectGetWidth(rect), deltaHeight); 273 | CGPoint bottomLeft = CGPointMake(0, height); 274 | CGPoint bottomRight = CGPointMake(CGRectGetWidth(rect), height); 275 | UIBezierPath* path = [UIBezierPath bezierPath]; 276 | [[UIColor blueColor] setFill]; 277 | [path moveToPoint:topLeft]; 278 | [path addQuadCurveToPoint:topRight controlPoint:CGPointMake(CGRectGetMidX(rect), 0)]; 279 | [path addLineToPoint:bottomRight]; 280 | [path addQuadCurveToPoint:bottomLeft controlPoint:CGPointMake(CGRectGetMidX(rect), height - deltaHeight)]; 281 | [path closePath]; 282 | [path fill]; 283 | } 284 | 285 | @end 286 | ``` 287 | 288 | ### UIDynamicAnimator 289 | UIDynamicAnimator 是 iOS 7 引入的一个类,可以创建出具有物理仿真效果的动画,具体提供了下面几种物理仿真行为: 290 | 291 | UIGravityBehavior:重力行为 292 | UICollisionBehavior:碰撞行为 293 | UISnapBehavior:捕捉行为 294 | UIPushBehavior:推动行为 295 | UIAttachmentBehavior:附着行为 296 | UIDynamicItemBehavior:动力元素行为 297 | 示例代码如下(来自[这里](https://teehanlax.com/blog/introduction-to-uikit-dynamics/)) 298 | 299 | ``` 300 | self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view]; 301 | 302 | UIGravityBehavior* gravityBehavior = [[UIGravityBehavior alloc] initWithItems:@[self.myView]]; 303 | [self.animator addBehavior:gravityBehavior]; 304 | 305 | UICollisionBehavior* collisionBehavior = [[UICollisionBehavior alloc] initWithItems:@[self.myView]]; 306 | collisionBehavior.translatesReferenceBoundsIntoBoundary = YES; 307 | [self.animator addBehavior:collisionBehavior]; 308 | ``` 309 | 310 | 可以发现这段代码和我们之前写的动画代码有很大不同,在这里 behavior 是用于控制 View 行为的,我们做的操作是把各种不同的 behavior 加到 animator 中。这段代码实现了 View 因为“重力”原因“掉到”地上,落地的同时还有一个碰撞效果。 311 | 312 | ### CAEmitterLayer 313 | CAEmitterLayer 是 Core Animation 提供的一个粒子发生器系统,可以用于创建各种粒子动画,例如烟雾,焰火等效果。 314 | 315 | CAEmitterLayer 需要调节的参数很多,可以实现的效果也非常炫酷,具体可参考下面几个网址: 316 | - http://enharmonichq.com/tutorial-particle-systems-in-core-animation-with-caemitterlayer/#prettyPhoto/0/ 317 | - https://www.invasivecode.com/weblog/caemitterlayer-and-the-ios-particle-system-lets/?doing_wp_cron=1438657800.4759559631347656250000 318 | 319 | [LTMorphingLabel](https://github.com/lexrus/LTMorphingLabel) 这个项目使用 CAEmitterLayer 实现了各种高端炫酷掉渣天的效果,大家想学习的话可以去看看它的代码。 320 | 321 | ## 资料 322 | - https://zsisme.gitbooks.io/ios-/content/chapter6/catiledLayer.html 323 | - https://github.com/DevDragonLi/iOSInterviewsAndDevNotes/blob/master/iOSNote/iOSCoreAnimationNote.md 324 | - 本知识点转载于-[iOS核心动画高级技巧 -知识点记录](https://hit-alibaba.github.io/interview/iOS/Cocoa-Touch/Animation.html) 325 | -------------------------------------------------------------------------------- /docs/03-04、网络编程(空).md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pengwj/iOSInterview/e0918812d041cb9bd5fc0f5175b67bd60306899d/docs/03-04、网络编程(空).md -------------------------------------------------------------------------------- /docs/03-05、OC与WebView交互(空) L17.md: -------------------------------------------------------------------------------- 1 | [iOS开发中webview和OC交互](https://blog.csdn.net/weixin_40876113/article/details/82852709) 2 | 3 | -------------------------------------------------------------------------------- /docs/04-01、性能优化 L18.md: -------------------------------------------------------------------------------- 1 | [TOC] 2 | 3 | # iOS编译 4 | 当一个xcode工程build之后一般会执行如下几个步骤: 5 | + 预处理 6 | + 语法和语义分析 7 | + 生成代码和优化 8 | + 汇编 9 | + 链接 10 | 11 | # 性能优化 12 | ## 卡顿优化 13 | ### CPU优化 14 | 减少对象的创建和销毁、对象属性的调整、布局计算、文本的计算和排版、图片的格式转换和解码、图像的绘制。 15 | + 尽量用轻量级的对象,比如用不到事件处理的地方,可以考虑使用CALayer取代UIView 16 | + 不要频繁地调用 UIView 的相关属性,比如 frame、bounds、transform 等属性,尽量减少不必要的修改 17 | + 尽量提前计算好布局,在有需要时一次性调整对应的属性,不要多次修改属性 18 | + Autolayout会比直接设置frame消耗更多的CPU资源 19 | + 图片的size最好刚好跟UIImageView的size保持一致 20 | + 控制一下线程的最大并发数量 21 | + 尽量把耗时的操作放到子线程 22 | + 文本处理(尺寸计算、绘制) 23 | + 图片处理(解码、绘制) 24 | 25 | ### GPU优化 26 | + 尽量避免短时间内大量图片的显示,尽可能将多张图片合成一张进行显示 27 | + GPU能处理的最大纹理尺寸是4096*4096,一旦超过这个尺寸,就会占用CPU资源,所以纹理尽量小于这个尺寸(SDWebImage内部有相应处理,Kingfisher对此没有处理) 28 | + 尽量减少视图数量和层次 29 | + 减少透明的视图(alpha<1),不透明的就设置opaque为YES 30 | + 尽量避免出现离屏渲染 31 | 32 | ### 离屏渲染 33 | + 在OpenGL中,GPU有两种渲染方式 34 | + 当前屏幕渲染,在当前用于显示的屏幕缓冲区进行渲染操作 35 | + 离屏渲染,在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作 36 | 37 | + 离屏渲染消耗性能的原因 38 | + 需要创建新的缓冲区 39 | + 需要多次切换上下文 40 | 41 | + 哪些操作会触发离屏渲染 42 | + 光栅化 43 | + layer.shouldRasterize = YES 44 | + 遮罩 45 | + layer.mask 46 | + 圆角 47 | + 同时设置layer.masksToBounds = YES、layer.cornerRadius大于0 48 | + 可以通过coreGraphics绘制裁剪圆角 49 | + 阴影效果 50 | + 如果设置了layer.shadowPath就不会产生离屏渲染 51 | 52 | ## 网络优化 53 | + 优化I/O操作 54 | + 尽量不要频繁写入小数据,最好批量一次性写入 55 | + 读写大量重要数据时,考虑用dispatch_io,其提供了基于GCD的异步操作文件I/O的API。用dispatch_io系统会优化磁盘访问 56 | + 数据量比较大的,建议使用数据库(比如SQLite、CoreData) 57 | + 网络优化 58 | + 减少、压缩网络数据 59 | + 如果多次请求的结果是相同的,尽量使用缓存 60 | + 使用断点续传,否则网络不稳定时可能多次传输相同的内容 61 | + 批量传输,比如,下载视频流时,不要传输很小的数据包,直接下载整个文件或者一大块一大块地下载。如果下载广告,一次性多下载一些,然后再慢慢展示。如果下载电子邮件,一次下载多封,不要一封一封地下载 62 | + 网络不可用时,不要尝试执行网络请求 63 | + 设置合适的超时时间 64 | 65 | ## 耗电优化 66 | + 定位优化 67 | + 如果只是需要快速确定用户位置,最好用CLLocationManager的requestLocation方法。定位完成后,会自动让定位硬件断电 68 | + 如果不是导航应用,尽量不要实时更新位置,定位完毕就关掉定位服务 69 | + 尽量降低定位精度,比如尽量不要使用精度最高的kCLLocationAccuracyBest 70 | + 需要后台定位时,尽量设置pausesLocationUpdatesAutomatically为YES,如果用户不太可能移动的时候系统会自动暂停位置更新 71 | + 尽量不要使用startMonitoringSignificantLocationChanges,优先考虑startMonitoringForRegion: 72 | + 硬件检测优化 73 | + 用户移动、摇晃、倾斜设备时,会产生动作(motion)事件,这些事件由加速度计、陀螺仪、磁力计等硬件检测。在不需要检测的场合,应该及时关闭这些硬件 74 | 75 | ## 启动优化 76 | 按照不同的阶段 77 | + dyld 78 | + 减少动态库、合并一些动态库(定期清理不必要的动态库) 79 | + 减少Objc类、分类的数量、减少Selector数量(定期清理不必要的类、分类) 80 | + 减少C++虚函数数量 81 | + Swift尽量使用struct 82 | + runtime 83 | + 用+initialize方法和dispatch_once取代所有的__attribute__((constructor))、C++静态构造器、ObjC的+load 84 | 85 | + main 86 | + 在不影响用户体验的前提下,尽可能将一些操作延迟,不要全部都放在finishLaunching方法中 87 | + 按需加载 88 | 89 | + 二进制重拍 90 | + https://www.jianshu.com/p/3f9ed86a45cb 91 | 92 | ### dyld 93 | + dyld(dynamic link editor),Apple的动态链接器,可以用来装载Mach-O文件(可执行文件、动态库等) 94 | + 启动APP时,dyld所做的事情有 95 | + 装载APP的可执行文件,同时会递归加载所有依赖的动态库 96 | + 当dyld把可执行文件、动态库都装载完毕后,会通知Runtime进行下一步的处理 97 | 98 | ### runtime 99 | + 启动APP时,runtime所做的事情有 100 | + 调用map_images进行可执行文件内容的解析和处理 101 | + 在load_images中调用call_load_methods,调用所有Class和Category的+load方法 102 | + 进行各种objc结构的初始化(注册Objc类、初始化类对象等等) 103 | + 调用C++静态初始化器和__attribute__((constructor))修饰的函数 104 | + 到此为止,可执行文件和动态库中所有的符号(Class,Protocol,Selector,IMP,…)都已经按格式成功加载到内存中,被runtime 所管理 105 | 106 | ### main 107 | + APP的启动由dyld主导,将可执行文件加载到内存,顺便加载所有依赖的动态库 108 | + 并由runtime负责加载成objc定义的结构 109 | + 所有初始化工作结束后,dyld就会调用main函数 110 | + 接下来就是UIApplicationMain函数,AppDelegate的application:didFinishLaunchingWithOptions:方法 111 | 112 | ## 安装包瘦身 113 | 安装包瘦身,主要从减小可执行文件、资源入手。 114 | + 资源(图片、音频、视频等) 115 | + 采取无损压缩 116 | + 去除没有用到的资源(可以用脚本写工具,或者找相应的工具) 117 | + 可执行文件 118 | + 编译器优化 119 | + Strip Linked Product、Make Strings Read-Only、Symbols Hidden by Default设置为YES 120 | + 去掉异常支持,Enable C++ Exceptions、Enable Objective-C Exceptions设置为NO 121 | + Other C Flags添加-fno-exceptions 122 | + 利用AppCodehttps://www.jetbrains.com/objc/检测未使用的代码:菜单栏 -> Code -> Inspect Code 123 | + 编写LLVM插件检测出重复代码、未被调用的代码 124 | 125 | # Crash崩溃总结 126 | 127 | [如何定位Obj-C野指针随机Crash(一):先提高野指针Crash率](https://blog.csdn.net/tencent_bugly/article/details/46277055) 128 | 129 | [如何定位Obj-C野指针随机Crash(二):让非必现Crash变成必现](https://blog.csdn.net/Tencent_Bugly/article/details/46374401) 130 | 131 | [如何定位Obj-C野指针随机Crash(三):加点黑科技让Crash自报家门](https://note.youdao.com/) 132 | 133 | [Crash分析模型](https://juejin.cn/post/6896990367170691085) 134 | 135 | [ios网易大白Crash自动防护](https://blog.csdn.net/u014600626/article/details/105348360/) 136 | 137 | [iOS Crash不崩溃](https://juejin.cn/post/6844903688608153614) 138 | 139 | 140 | ## Crash崩溃 141 | + unrecognized selector crash 142 | + 方法找不到崩溃(可以讲一下之前被标记马甲包,为了上线改了不少方法名称) 143 | + unrecognized selector类型的crash在app众多的crash类型中占着比较大的成分,通常是因为一个对象调用了一个不属于它方法的方法导致的。 144 | + KVO crash 145 | + KVO的被观察者dealloc时仍然注册着KVO导致的crash(忘记移除观察者) 146 | + 添加KVO重复添加观察者或重复移除观察者(KVO注册观察者与移除观察者不匹配)导致的crash 147 | + NSNotification crash 148 | + 当一个对象添加了notification之后,如果dealloc的时候,仍然持有notification,就会出现NSNotification类型的crash。(忘记移除通知) 149 | + NSTimer crash 150 | + 在程序开发过程中,大家会经常使用定时任务,但使用NSTimer的 scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:接口做重复性的定时任务时存在一个问题:NSTimer会强引用target实例,所以需要在合适的时机invalidate定时器,否则就会由于定时器timer强引用target的关系导致target不能被释放,造成内存泄露,甚至在定时任务触发时导致crash。 crash的展现形式和具体的target执行的selector有关。 151 | 152 | + Container crash(数组越界,插nil等) 153 | + 常见的越界、插入nil等错误操作均会导致此类crash发生 154 | 155 | + NSString crash (字符串操作的crash) 156 | + 切割字符串越界 157 | + Bad Access crash (野指针) 158 | + 语音播放类,之前是用mrc实现的,会有提前释放的情况 159 | + UI not on Main Thread Crash (非主线程刷UI(机制待改善)) 160 | + 通过线程切换,让其在主线程刷新UI 161 | 162 | # 资料 163 | [iOS 性能优化的探索](https://www.jianshu.com/p/b8346c1a4145) 164 | 165 | [卡顿率降低50%!京东商城APP卡顿监控及优化实践](https://mp.weixin.qq.com/s/aJeAUAjcKOMvznDMGj2UUA) 166 | 167 | [开发iOS应用如何避免卡顿](https://blog.csdn.net/Tencent_Bugly/article/details/46650207) 168 | 169 | [App 启动性能优化](https://mp.weixin.qq.com/s/Kf3EbDIUuf0aWVT-UCEmbA) 170 | 171 | [iOS APP 性能检测](https://blog.csdn.net/Tencent_Bugly/article/details/78247589) 172 | 173 | [腾讯 Bugly : iOS Crash 跟踪方法](https://www.jikexueyuan.com/course/534.html) 174 | 175 | [iOS编译与app启动](https://www.jianshu.com/p/65901441903e) 176 | 177 | [前Apple工程师的性能优化建议](https://www.jianshu.com/p/15b716f2ac48) 178 | 179 | [iOS性能优化 — 一、crash监控及防崩溃处理 180 | ](https://www.jianshu.com/p/4c75deeb6b7a) 181 | 182 | [iOS性能优化 — 二、卡顿监控及处理 183 | ](https://www.jianshu.com/p/4872d24da37d) 184 | 185 | [iOS性能优化 — 三、安装包瘦身 186 | ](https://www.jianshu.com/p/369c909c1067) 187 | 188 | [iOS性能优化 — 四、内存泄露检测 189 | ](https://www.jianshu.com/p/f06f14800cf7) 190 | 191 | [iOS性能优化 — 五、App启动优化](https://www.jianshu.com/p/4483d63253ca) 192 | 193 | [优化 App 的启动时间](http://yulingtianxia.com/blog/2016/10/30/Optimizing-App-Startup-Time/) 194 | 195 | [ 196 | 深入剖析 iOS 性能优化](https://ming1016.github.io/2017/06/20/deeply-ios-performance-optimization/) -------------------------------------------------------------------------------- /docs/04-02、组件化 L19.md: -------------------------------------------------------------------------------- 1 | [iOS 组件化 —— 路由设计思路分析 @一缕殇流化隐半边冰霜](https://halfrost.com/ios_router/) 2 | 3 | [iOS:Cocoa编码规范 -[译]Coding Guidelines for Cocoa](https://www.cnblogs.com/clairvoyant/p/5340389.html) 4 | 5 | [蘑菇街 App 的组件化之路](https://limboy.me/2016/03/10/mgj-components/) 6 | 7 | [蘑菇街 App 的组件化之路·续](https://limboy.me/2016/03/14/mgj-components-continued/) 8 | 9 | [iOS应用架构谈 组件化方案](https://casatwy.com/iOS-Modulization.html) 10 | 11 | # 组件化 12 | ## 常见的组件化方案 13 | 路由的设计思路是从URLRoute ->Protocol-class ->Target-Action一步步的深入的过程。这也是逐渐深入本质的过程。 14 | 15 | ### URLRoute注册 16 | 首先URLRoute也许是借鉴前端Router和系统App内跳转的方式想出来的方法。它通过URL来请求资源。不管是H5,RN,Weex,iOS界面或者组件请求资源的方式就都统一了。URL里面也会带上参数,这样调用什么界面或者组件都可以。所以这种方式是最容易,也是最先可以想到的。 17 | 18 | #### 优点 19 | - 服务器可以动态的控制页面跳转 20 | - 可以统一处理页面出问题之后的错误处理 21 | 22 | #### 缺点 23 | - URL的map规则是需要注册的,它们会在load方法里面写。写在load方法里面是会影响App启动速度的 24 | - 大量的硬编码,URL链接里面关于组件和页面的名字都是硬编码,参数也都是硬编码 25 | - URL短连接散落在整个App四处,维护起来实在有点麻烦 26 | - 对于传递NSObject的参数,URL是不够友好的,它最多是传递一个字典。 27 | 28 | ### Protocol-class注册 29 | #### 优点 30 | - Protocol-Class方案没有硬编码 31 | 32 | #### 缺点 33 | - 每个Protocol都要向ModuleManager进行注册 34 | - 同时需要依赖ModuleManager和组件里面的页面或者组件 35 | - 组件方法的调用是分散在各处的,没有统一的入口,也就没法做组件不存在时或者出现错误时的统一处理。 36 | 37 | ### Target-Action注册 38 | #### 优点 39 | - 充分的利用Runtime的特性,无需注册 40 | - 依赖关系少,只有存在组件依赖Mediator这一层依赖关系 41 | - 统一了所有组件间调用入口 42 | - 具有一定的安全保证,对url中进行Native前缀进行验证 43 | 44 | #### 缺点 45 | - Target_Action在Category中将常规参数打包成字典,在Target处再把字典拆包成常规参数,造成了一部分的硬编码 46 | 47 | ## 最好的方案 48 | 最适合自己公司业务的方案才是最好的方案。分而治之,针对不同业务选择不同的方案才是最优的解决方案。如果非要笼统的采用一种方案,不同业务之间需要同一种方案,需要妥协牺牲的东西太多就不好了。 49 | 50 | ### 蘑菇街方案 51 | 蘑菇街在本地间调用同时采用了openURL方案和protocol - class方案,所以其实之前我指出蘑菇街本地间调用不能传递非常规参数和复杂参数是不对的,应该是蘑菇街在本地间调用时如果是普通参数,那就采用openURL,如果是非常规参数,那就采用protocol - class了 52 | 53 | ### CTMediator的方案 54 | 基于Mediator模式和Target-Action模式,中间采用了runtime来完成调用。这套组件化方案将远程应用调用和本地应用调用做了拆分,而且是由本地应用调用为远程应用调用提供服务。 55 | 56 | -------------------------------------------------------------------------------- /docs/04-03、音视频技术(空) L20.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pengwj/iOSInterview/e0918812d041cb9bd5fc0f5175b67bd60306899d/docs/04-03、音视频技术(空) L20.md -------------------------------------------------------------------------------- /docs/04-04、线上卡顿检测 L21.md: -------------------------------------------------------------------------------- 1 | # 线上实时卡顿监控 2 | 线上卡顿实时检测是利用runloop机制的一个实际应用 3 | 4 | ## 常见方案 5 | 下文中内容主要是runloop方案 6 | - FPS 7 | - ping 8 | - runloop 9 | - msgSend 10 | 11 | ## 原理 12 | - NSRunLoop调用方法处理任务,主要集中在kCFRunLoopBeforeSources(3. 通知 Observers: 即将触发 Source0回调)和kCFRunLoopBeforeWaiting(6. 通知Observers,即将进入休眠)之间,还有kCFRunLoopAfterWaiting之后,也就是如果我们发现这两个时间内耗时太长,那么就可以判定出此时主线程卡顿 13 | - 可以添加Observer到主线程RunLoop中,通过在子线程监听RunLoop状态切换的耗时,以达到监控卡顿的目的 14 | 15 | ### 监听时机 16 | 监听kCFRunLoopBeforeSources(3. 通知 Observers: 即将触发 Source0回调)和kCFRunLoopBeforeWaiting(6. 通知Observers,即将进入休眠)之间的时间。 17 | 18 | - 大部分导致卡顿的的方法是在kCFRunLoopBeforeSources和kCFRunLoopAfterWaiting之间,比如source0主要是处理App内部事件,App自己负责管理(出发),如UIEvent(Touch事件等,GS发起到RunLoop运行再到事件回调到UI)、CFSocketRef。 19 | 20 | ### 卡顿条件 21 | - iOS实时卡顿监控3 - 是设置连续5次超时50ms认为卡顿 22 | 23 | - 戴铭在 GCDFetchFeed4 中设置的是连续3次超时80ms认为卡顿的代码 24 | 25 | ### runloop运行流程 26 | ``` 27 | // 1.进入loop 28 | __CFRunLoopRun(runloop, currentMode, seconds, returnAfterSourceHandled) 29 |  30 | // 2.RunLoop 即将触发 Timer 回调。 31 | __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers); 32 | // 3.RunLoop 即将触发 Source0 (非port) 回调。 33 | __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources); 34 | // 4.RunLoop 触发 Source0 (非port) 回调。 35 | sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle) 36 | // 5.执行被加入的block 37 | __CFRunLoopDoBlocks(runloop, currentMode); 38 |  39 | // 6.RunLoop 的线程即将进入休眠(sleep)。 40 | __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting); 41 |  42 | // 7.调用 mach_msg 等待接受 mach_port 的消息。线程将进入休眠, 直到被下面某一个事件唤醒。 43 | __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort) 44 |  45 | // 进入休眠 46 |  47 | // 8.RunLoop 的线程刚刚被唤醒了。 48 | __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting 49 |  50 | // 9.如果一个 Timer 到时间了,触发这个Timer的回调 51 | __CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time()) 52 |  53 | // 10.如果有dispatch到main_queue的block,执行bloc 54 | __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg); 55 |  56 | // 11.如果一个 Source1 (基于port) 发出事件了,处理这个事件 57 | __CFRunLoopDoSource1(runloop, currentMode, source1, msg); 58 |  59 | // 12.RunLoop 即将退出 60 | __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit); 61 | ``` 62 | 63 | ### runloop的状态 64 | ``` 65 | /* Run Loop Observer Activities */ 66 | typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) { 67 | kCFRunLoopEntry = (1UL << 0), // 即将进入Loop 68 | kCFRunLoopBeforeTimers = (1UL << 1), //即将处理Timer 69 | kCFRunLoopBeforeSources = (1UL << 2), //即将处理Source 70 | kCFRunLoopBeforeWaiting = (1UL << 5), //即将进入休眠 71 | kCFRunLoopAfterWaiting = (1UL << 6), //刚从休眠中唤醒 72 | kCFRunLoopExit = (1UL << 7), //即将退出Loop 73 | kCFRunLoopAllActivities = 0x0FFFFFFFU //所有状态改变 74 | }; 75 | ``` 76 | 77 | 78 | 79 | # 资料 80 | [iOS使用RunLoop监控线上卡顿 81 | ](https://juejin.cn/post/6844903887397371912) 82 | 83 | [微信iOS卡顿监控系统](https://mp.weixin.qq.com/s/M6r7NIk-s8Q-TOaHzXFNAw) 84 | 85 | [Matrix-iOS 卡顿监控](https://mp.weixin.qq.com/s/gPZnR7sF_22KSsqepohgNg) 86 | 87 | [Matrix-iOS 内存监控](https://mp.weixin.qq.com/s/j454cHgba6bdQECiUR22eQ) 88 | 89 | [天罗地网? iOS卡顿监控实战](https://juejin.cn/post/6844904005437489165) 90 | 91 | [iOS 性能监控(1)——CPU、Memory、FPS](http://chuquan.me/2019/06/10/ios-performance-monitor-cpu-mem-fps/) 92 | 93 | [iOS 性能监控(2)——卡顿](http://chuquan.me/2019/06/17/ios-performance-monitor-caton/) 94 | 95 | [质量监控-卡顿检测](https://www.jianshu.com/p/ea36e0f2e7ae) 96 | 97 | [简单监测iOS卡顿的demo](https://www.jianshu.com/p/71cfbcb15842) 98 | 99 | [GCD信号量-dispatch_semaphore_t](https://www.jianshu.com/p/24ffa819379c) -------------------------------------------------------------------------------- /docs/04-05、无埋点技术(空) L22.md: -------------------------------------------------------------------------------- 1 | 2 | # 资料 3 | [iOS无埋点SDK实现思路](https://www.jianshu.com/p/f9fa983e78aa) -------------------------------------------------------------------------------- /docs/05-01、源码解读(空) L23.md: -------------------------------------------------------------------------------- 1 | https://github.com/huang303513/SourceCodeResearchAndExploration -------------------------------------------------------------------------------- /res/001.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pengwj/iOSInterview/e0918812d041cb9bd5fc0f5175b67bd60306899d/res/001.jpg -------------------------------------------------------------------------------- /res/002.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pengwj/iOSInterview/e0918812d041cb9bd5fc0f5175b67bd60306899d/res/002.jpg -------------------------------------------------------------------------------- /res/003.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pengwj/iOSInterview/e0918812d041cb9bd5fc0f5175b67bd60306899d/res/003.png -------------------------------------------------------------------------------- /res/004.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pengwj/iOSInterview/e0918812d041cb9bd5fc0f5175b67bd60306899d/res/004.png -------------------------------------------------------------------------------- /res/005.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pengwj/iOSInterview/e0918812d041cb9bd5fc0f5175b67bd60306899d/res/005.png -------------------------------------------------------------------------------- /res/006.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pengwj/iOSInterview/e0918812d041cb9bd5fc0f5175b67bd60306899d/res/006.png -------------------------------------------------------------------------------- /res/007.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pengwj/iOSInterview/e0918812d041cb9bd5fc0f5175b67bd60306899d/res/007.png -------------------------------------------------------------------------------- /res/008.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pengwj/iOSInterview/e0918812d041cb9bd5fc0f5175b67bd60306899d/res/008.png -------------------------------------------------------------------------------- /res/009.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pengwj/iOSInterview/e0918812d041cb9bd5fc0f5175b67bd60306899d/res/009.png -------------------------------------------------------------------------------- /res/010.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pengwj/iOSInterview/e0918812d041cb9bd5fc0f5175b67bd60306899d/res/010.png -------------------------------------------------------------------------------- /res/011.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pengwj/iOSInterview/e0918812d041cb9bd5fc0f5175b67bd60306899d/res/011.png -------------------------------------------------------------------------------- /res/012.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pengwj/iOSInterview/e0918812d041cb9bd5fc0f5175b67bd60306899d/res/012.png -------------------------------------------------------------------------------- /res/013.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pengwj/iOSInterview/e0918812d041cb9bd5fc0f5175b67bd60306899d/res/013.png -------------------------------------------------------------------------------- /res/014.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pengwj/iOSInterview/e0918812d041cb9bd5fc0f5175b67bd60306899d/res/014.png -------------------------------------------------------------------------------- /res/015.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pengwj/iOSInterview/e0918812d041cb9bd5fc0f5175b67bd60306899d/res/015.png -------------------------------------------------------------------------------- /res/016.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pengwj/iOSInterview/e0918812d041cb9bd5fc0f5175b67bd60306899d/res/016.png -------------------------------------------------------------------------------- /res/017.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pengwj/iOSInterview/e0918812d041cb9bd5fc0f5175b67bd60306899d/res/017.png -------------------------------------------------------------------------------- /res/018.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pengwj/iOSInterview/e0918812d041cb9bd5fc0f5175b67bd60306899d/res/018.jpg --------------------------------------------------------------------------------